이번 포스팅에서는 Python 의 Pandas 에서 함수를 적용할 때 사용하는 map(), applymap(), apply() 메소드에 대해서 알아보겠습니다. 

 

(1) map(): Series 에 대해 element-wise 로 함수 적용

(2) applymap(): DataFrame에 대해 element-wise 로 함수 적용

(3) apply(): DataFrame에 대해 행/열 (row/column) 로 함수 적용

 

 

Pandas 함수 적용하기: map, applymap, apply

 

 

(1) map(): Series 에 대해 element-wise 로 함수 적용

 

예제로 사용할 DataFrame을 만들어보겠습니다. 

 

import numpy as np
import pandas as pd

## making a sample pandas DataFrame
np.random.seed(1004)

df = pd.DataFrame(
    np.random.randn(4, 3), 
    columns=['x1', 'x2', 'x3'], 
    index=['A', 'B', 'C', 'D'])

print(df)
#          x1        x2        x3
# A  0.594403  0.402609 -0.805162
# B  0.115126 -0.753065 -0.784118
# C  1.461576  1.576076 -0.171318
# D -0.914482  0.860139  0.358802

 

 

 

map() 메소드는 pandas Series 에 대해 함수를 적용할 때 사용합니다. 아래 예제는 익명 함수 lambda 로 정의한 s_formater 함수를 map() 메소드로 Series에 적용해 본 것입니다. 

 

## pandas Series
df['x1']
# A    0.594403
# B    0.115126
# C    1.461576
# D   -0.914482
# Name: x1, dtype: float64


## map: applying an element-wise function for Series
s_formater = lambda x: '%.2f'% x

df['x1'].map(s_formater)
# A     0.59
# B     0.12
# C     1.46
# D    -0.91
# Name: x1, dtype: object

 

 

 

아래 예제에서는 Series의 인덱스를 키로 해서 매핑(mapping by index)하여 Series의 값을 변환한 것입니다. 

-------------------------------------------------------

s_1 인덱스 --> s_1 값 = s_2 인덱스  -->  s_2 값

-------------------------------------------------------

'a'             -->             0                    -->     'A'

'b'             -->             1                     -->     'B'

'c'             -->             2                     -->     'C'

-------------------------------------------------------

 

 

## 예제 pandas Series 만들기
s_1 = pd.Series({'a': 0, 'b': 1, 'c': 2})
s_2 = pd.Series({0: 'A', 1: 'B', 2: 'C'})

print(s_1)
# a    0
# b    1
# c    2
# dtype: int64

print(s_2)
# 0    A
# 1    B
# 2    C
# dtype: object


##-- 인덱스 키를 기준으로 매핑하기 (mapping by index on Series)
print(s_1.map(s_2))
# a    A
# b    B
# c    C
# dtype: object

 

 

 

(2) applymap(): DataFrame에 대해 element-wise 로 함수 적용

 

다음 예제는 applymap() 메소드를 사용해서 익명함수 lambda 로 정의한 s_formater 함수를 DataFrame 의 각 원소에 대해 적용한 것입니다. (map() 은 Series 대상 vs. applymap()은 DataFrame 대상 element-wise 함수 적용)

 

## applymap: applying an element-wise for DataFrame
s_formater = lambda x: '%.2f'% x

df_2 = df.applymap(s_formater)

print(df_2)
#       x1     x2     x3
# A   0.59   0.40  -0.81
# B   0.12  -0.75  -0.78
# C   1.46   1.58  -0.17
# D  -0.91   0.86   0.36

 

 

 

(3) apply(): DataFrame에 대해 행/열 (row/column) 로 함수 적용

 

아래 예제는 column 기준 (axis=0),  row 기준 (axis=1) 으로 익명함수 lambda로 정의한 (열 또는 행 기준, 최대값 - 최소값) 을 계산하는 함수를 apply() 메소드로 DataFrame에 대해 적용해본 것입니다. 

 

## apply: applying a function on row/column basis of a DataFrame
f = lambda x: x.max() - x.min()

## on column basis
df.apply(f, axis=0)

# x1    2.376058
# x2    2.329141
# x3    1.163964
# dtype: float64



## on row basis
df.apply(f, axis=1)

# A    1.399565
# B    0.899243
# C    1.747393
# D    1.774621
# dtype: float64

 

 

 

아래 예는 apply() 메소드를 써서 DataFrame에 행(row) 기준으로 최대값을 계산하는 익명함수 lambda로 정의한 함수를 적용해서 'x_max'라는 새로운 칼럼을 추가한 것입니다. 

 

## 새로운 칼럼 추가하기
df['x_max'] = df.apply(lambda x: x.max(), axis=1)

print(df)
#          x1        x2        x3     x_max
# A  0.594403  0.402609 -0.805162  0.594403
# B  0.115126 -0.753065 -0.784118  0.115126
# C  1.461576  1.576076 -0.171318  1.576076
# D -0.914482  0.860139  0.358802  0.860139


## equivalent
df['x_max'] = df.max(axis=1)

 

 

* Pandas DataFrame에서 여러개의 칼럼에 대해 그룹을 집계를 할 때 다른 집계 함수를 적용하는 방법은 https://rfriend.tistory.com/393 를 참고하세요. 

 

* Python의 익명 함수 lambda 에 대해서는 https://rfriend.tistory.com/366 를 참고하세요. 

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

탐색적 데이터 분석 및 데이터 전처리를 할 때 특정 행과 열을 기준으로 데이터를 선택(selection), 인덱싱(indexing), 슬라이싱(slicing)하는 하는 처리가 필요합니다. 이때 pandas 에서 사용할 수 있는 게 loc, iloc 인데요, 비슷해서 좀 헷갈리는 면이 있습니다. 

 

이번 포스팅에서는 Python pandas 의 DataFrame에서 데이터를 선택(selection), 인덱싱(indexing), 슬라이싱(slicing)할 때 사용하는 loc vs. iloc 를 비교해보겠습니다. 

 

- loc 는 레이블 기반 (label-based) 또는 블리언 배열(boolean array) 기반으로 데이터를 선택 

- iloc 는 정수 기반의 위치 (integer-based position) 또는 블리언 배열(boolean array) 기반으로 데이터를 선택

 

아래의 순서대로 loc vs. iloc 를 간단한 예를 들어 비교하면서 소개하겠습니다. 

(1) A Value

(2) A List or Array

(3) A Slice Object

(4) Conditions or Boolean Array

(5) A Callable Function

 

 

[ Pandas DataFrame의 데이터를 선택할 때 사용하는 loc vs. iloc ]

selecting data in pandas DataFrame using loc vs. iloc

 

 

먼저, index를 수기 입력해주고, 숫자형과 범주형 변수를 모두 가지는 간단한 예제 pandas DataFrame을 만들어보겠습니다. 

 

## creating a sample pandas DataFrame
import pandas as pd

df = pd.DataFrame({
    'name': ['park', 'choi', 'lee', 'kim', 'lim', 'yang'], 
    'gender': ['M', 'F', 'F', 'M', 'F', 'F'], 
    'age': [25, 29, 25, 31, 38, 30], 
    'grade': ['S', 'B', 'C', 'A', 'D', 'S'], 
    'tot_amt': [500, 210, 110, 430, 60, 680]}, 
    index=['c100', 'c101', 'c102', 'c103', 'c104', 'c105'])


print(df)
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500
# c101  choi      F   29     B      210
# c102   lee      F   25     C      110
# c103   kim      M   31     A      430
# c104   lim      F   38     D       60
# c105  yang      F   30     S      680

 

 

아래부터는 loc vs. iloc 코드 예시인데요, 모두 데이터를 선택하는 굉장히 간단한 코드들 이므로 설명은 생략합니다. 특이사항에 대해서만 간략히 부연설명 남겨요. 

 

(1) loc vs. iloc: A Value

 

한 개의 행 레이블(a single row label)로 loc 를 사용해서 값을 가져오면 pandas Series 를 반환하는 반면에, 리스트(list, [ ]) 안에 1개 행 레이블을 넣어서 loc 를 사용하면 pandas DataFrame을 반환합니다. 이것은 iloc 에 정수 위치를 넣어서 값을 가져올 때도 동일합니다. 

 

## (1) pandas.DataFrame.loc[]
## : Access a group of rows and colmns by label(s) or a boolean array

## (1-1) using a row label --> returns a pandas Series
df.loc['c100']
# name       park
# gender        M
# age          25
# grade         S
# tot_amt     500
# Name: c100, dtype: object



## (1-2) using a list of row label --> returns a pandas DataFrame
df.loc[['c100']]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500




## (2) pandas.DataFrame.iloc[]
## : Purely integer-location based indexing for selection by position.

## (2-1) A scalar of integer --> returns pandas Series
df.iloc[0]
# name       park
# gender        M
# age          25
# grade         S
# tot_amt     500
# Name: c100, dtype: object


## (2-2) A list of integer --> returns pandas DataFrame
df.iloc[[0]]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500

 

 

 

(2) loc vs. iloc: A List or Array

 

df.loc[['c100', 'c105']] 처럼 loc 에 레이블 리스트를 사용하면 해당 행의 다수의 열의 값을 가져오며, df.loc['c100', 'name'] 처럼 사용하면 레이블 기반의 해당 행(row)과 열(column)의 값을 가져옵니다. 

 

## (1) pandas.DataFrame.loc[]
## : Access a group of rows and colmns by label(s) or a boolean array

## (1-2) using multiple row labels
df.loc[['c100', 'c105']]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500
# c105  yang      F   30     S      680


## using row and column label
df.loc['c100', 'name']
# 'park'



## (2) pandas.DataFrame.iloc[]
## : Purely integer-location based indexing for selection by position.

## (2-2) A list or array of integers
df.iloc[[0, 1, 3, 5]]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500
# c101  choi      F   29     B      210
# c103   kim      M   31     A      430
# c105  yang      F   30     S      680

 

 

 

(3) loc vs. iloc: A Slice Object

 

loc 로 레이블 기반 슬라이싱을 하면 처음과 끝 레이블 값이 포함된 채로 값을 가져옵니다. 예를 들어, 아래의 df.loc['c100':'c103'] 은 'c100'과 'c103' 행도 모두 포함된 값을 가져왔습니다. 반면에 iloc 로 정수 위치 기반으로 슬라이싱을 하면 처음 정수 위치의 행은 가져오지만 마지막의 정수 위치의 행은 가져오지 않습니다. 

 

## (1) pandas.DataFrame.loc[]
## : Access a group of rows and colmns by label(s) or a boolean array

## (1-3) Slice with labels for row and single label for column.
df.loc['c100':'c103', 'name']
# c100    park
# c101    choi
# c102     lee
# c103     kim
# Name: name, dtype: object



## (2) pandas.DataFrame.iloc[]
## : Purely integer-location based indexing for selection by position.

## (2-3) slicing using integer
df.iloc[1:4]
#       name gender  age grade  tot_amt
# c101  choi      F   29     B      210
# c102   lee      F   25     C      110
# c103   kim      M   31     A      430

 

 

 

(4) loc vs. iloc: Conditions or Boolean Array

 

loc 의 레이블 기반 데이터 선택에는 조건문과 블리언 배열을 모두 사용할 수 있습니다. iloc 의 정수 기반 데이터 선택에는 블리언 배열을 사용할 수 있습니다. 

 

## (1) pandas.DataFrame.loc[]
## : Access a group of rows and colmns by label(s) or a boolean array

## (1-4-1) Conditional that returns a boolean Series

## Boolean list with the same length as the row axis
df['tot_amt'] > 400
# c100     True
# c101    False
# c102    False
# c103     True
# c104    False
# c105     True
# Name: tot_amt, dtype: bool


## Conditional that returns a boolean Series
df.loc[df['tot_amt'] > 400]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500
# c103   kim      M   31     A      430
# c105  yang      F   30     S      680


## Conditional that returns a boolean Series with column labels specified
df.loc[df['tot_amt'] > 400, ['name']]
#       name
# c100  park
# c103   kim
# c105  yang


## (1-4-2) A boolean array
df.loc[[True, False, False, True, False, True]]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500
# c103   kim      M   31     A      430
# c105  yang      F   30     S      680




## (2) pandas.DataFrame.iloc[]
## : Purely integer-location based indexing for selection by position.

## (2-4) With a boolean mask the same length as the index. 
## loc and iloc are interchangeable when labels are 0-based integers.
df.iloc[[True, False, False, True, False, True]]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500
# c103   kim      M   31     A      430
# c105  yang      F   30     S      680

 

 

 

(5) loc vs. iloc: A Callable Function

 

lambda 익명함수를 사용하여 호출 가능한 함수를 loc, iloc 에 사용해서 값을 선택할 수 있습니다. 

(df.iloc[lambda df: df['tot_amt'] > 400] 은 왜그런지 모르겠는데 에러가 나네요. -_-;)

 

## (1) pandas.DataFrame.loc[]
## : Access a group of rows and colmns by label(s) or a boolean array

## (1-5) Callable that returns a boolean Series
df.loc[lambda df: df['tot_amt'] > 400]
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500
# c103   kim      M   31     A      430
# c105  yang      F   30     S      680



## (2) pandas.DataFrame.iloc[]
## : Purely integer-location based indexing for selection by position.

## (2-7) Callable that returns a boolean Series
df.iloc[lambda x: x.index == 'c100']
#       name gender  age grade  tot_amt
# c100  park      M   25     S      500

 

 

 

 

[ Reference ]

* pandas DataFrame에서 행, 열 데이터 선택: https://rfriend.tistory.com/282 
* pandas loc: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.loc.html   
* pandas iloc: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.iloc.html   

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 샘플 크기가 다른 2개 이상의 집단에 대해 평균의 차이가 존재하는지를 검정하는 일원분산분석(one-way ANOVA)에 대해 scipy 모듈의 scipy.stats.f_oneway() 메소드를 사용해서 분석하는 방법(rfriend.tistory.com/638)을 소개하였습니다. 

 

이번 포스팅에서는 2개 이상의 집단에 대해 pandas DataFrame에 들어있는 여러 개의 숫자형 변수(one-way ANOVA for multiple numeric variables in pandas DataFrame) 별로 일원분산분석 검정(one-way ANOVA test)을 하는 방법을 소개하겠습니다. 

 

숫자형 변수와 집단 변수의 모든 가능한 조합을 MultiIndex 로 만들어서 statsmodels.api 모듈의 stats.anova_lm() 메소드의 모델에 for loop 순환문으로 변수를 바꾸어 가면서 ANOVA 검정을 하도록 작성하였습니다. 

 

 

 

먼저, 3개의 집단('grp 1', 'grp 2', 'grp 3')을 별로 'x1', 'x2', 'x3, 'x4' 의 4개의 숫자형 변수를 각각 30개씩 가지는 가상의 pandas DataFrame을 만들어보겠습니다. 이때 숫자형 변수는 모두 정규분포로 부터 난수를 발생시켜 생성하였으며, 'x3'와 'x4'에 대해서는 집단3 ('grp 3') 의 평균이 다른 2개 집단의 평균과는 다른 정규분포로 부터 난수를 발생시켜 생성하였습니다.  

 

아래의 가상 데이터셋은 결측값이 없이 만들었습니다만, 실제 기업에서 쓰는 데이터셋에는 혹시 결측값이 존재할 수도 있으므로 결측값을 없애거나 또는 결측값을 그룹 별 평균으로 대체한 후에 one-way ANOVA 를 실행하기 바랍니다. 

 

## Creating sample dataset
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# generate 90 IDs
id = np.arange(90) + 1

# Create 3 groups with 30 observations in each group.
from itertools import chain, repeat
grp = list(chain.from_iterable((repeat(number, 30) for number in [1, 2, 3])))

# generate random numbers per each groups from normal distribution
np.random.seed(1004)

# for 'x1' from group 1, 2 and 3
x1_g1 = np.random.normal(0, 1, 30)
x1_g2 = np.random.normal(0, 1, 30)
x1_g3 = np.random.normal(0, 1, 30)

# for 'x2' from group 1, 2 and 3
x2_g1 = np.random.normal(10, 1, 30)
x2_g2 = np.random.normal(10, 1, 30)
x2_g3 = np.random.normal(10, 1, 30)

# for 'x3' from group 1, 2 and 3
x3_g1 = np.random.normal(30, 1, 30)
x3_g2 = np.random.normal(30, 1, 30)
x3_g3 = np.random.normal(50, 1, 30) # different mean

x4_g1 = np.random.normal(50, 1, 30)
x4_g2 = np.random.normal(50, 1, 30)
x4_g3 = np.random.normal(20, 1, 30) # different mean

# make a DataFrame with all together
df = pd.DataFrame({'id': id, 
                   'grp': grp, 
                   'x1': np.concatenate([x1_g1, x1_g2, x1_g3]), 
                   'x2': np.concatenate([x2_g1, x2_g2, x2_g3]), 
                   'x3': np.concatenate([x3_g1, x3_g2, x3_g3]), 
                   'x4': np.concatenate([x4_g1, x4_g2, x4_g3])})
                   
df.head()
[Out] 

id	grp	x1	x2	x3	x4
0	1	1	0.594403	10.910982	29.431739	49.232193
1	2	1	0.402609	9.145831	28.548873	50.434544
2	3	1	-0.805162	9.714561	30.505179	49.459769
3	4	1	0.115126	8.885289	29.218484	50.040593
4	5	1	-0.753065	10.230208	30.072990	49.601211


df[df['grp'] == 3].head()
[Out] 

id	grp	x1	x2	x3	x4
60	61	3	-1.034244	11.751622	49.501195	20.363374
61	62	3	0.159294	10.043206	50.820755	19.800253
62	63	3	0.330536	9.967849	50.461775	20.993187
63	64	3	0.025636	9.430043	50.209187	17.892591
64	65	3	-0.092139	12.543271	51.795920	18.883919

 

 

 

가령, 'x3' 변수에 대해 집단별로 상자 그래프 (Box plot for 'x3' by groups) 를 그려보면, 아래와 같이 집단1과 집단2는 유사한 반면에 집단3은 평균이 차이가 많이 나게 가상의 샘플 데이터가 생성되었음을 알 수 있습니다. 

 

## Boxplot for 'x3' by 'grp'
plt.rcParams['figure.figsize'] = [10, 6]
sns.boxplot(x='grp', y='x3', data=df)
plt.show()

 

 

여러개의 변수에 대해 일원분산분석을 하기 전에, 먼저 이해를 돕기 위해 Python의 statsmodels.api 모듈의 stats.anova_lm() 메소드를 사용해서 'x1' 변수에 대해 집단(집단 1/2/3)별로 평균이 같은지 일원분산분석으로 검정을 해보겠습니다. 

 

    - 귀무가설(H0) : 집단1의 x1 평균 = 집단2의 x1 평균 = 집단3의 x1 평균

    - 대립가설(H1) : 적어도 1개 이상의 집단의 x1 평균이 다른 집단의 평균과 다르다. (Not H0)

 

# ANOVA for x1 and grp
import statsmodels.api as sm
from statsmodels.formula.api import ols

model = ols('x1 ~ grp', data=df).fit()
sm.stats.anova_lm(model, typ=1)
[Out]

df	sum_sq	mean_sq	F	PR(>F)
grp	1.0	0.235732	0.235732	0.221365	0.639166
Residual	88.0	93.711314	1.064901	NaN	NaN

 

일원분산분석 결과 F 통계량이 0.221365, p-value가 0.639 로서 유의수준 5% 하에서 귀무가설을 채택합니다. 즉, 3개 집단 간 x1의 평균의 차이는 없다고 판단할 수 있습니다. (정규분포 X ~ N(0, 1) 를 따르는 모집단으로 부터 무작위로 3개 집단의 샘플을 추출했으므로 차이가 없게 나오는게 맞겠습니다.)

 

 

한개의 변수에 대한 일원분산분석하는 방법을 알아보았으니, 다음으로는 3개 집단별로 여러개의 연속형 변수인 'x1', 'x2', 'x3', 'x4' 에 대해서 for loop 순환문으로 돌아가면서 일원분산분석을 하고, 그 결과를 하나의 DataFrame에 모아보도록 하겠습니다. 

 

(1) 먼저, 일원분산분석을 하려는 모든 숫자형 변수와 집단 변수에 대한 가능한 조합의 MultiIndex 를 생성해줍니다. 

 

# make a multiindex for possible combinations of Xs and Group
num_col = ['x1','x2', 'x3', 'x4']
cat_col =  ['grp']
mult_idx = pd.MultiIndex.from_product([num_col, cat_col],
                                   names=['x', 'grp'])

print(mult_idx)
[Out]
MultiIndex([('x1', 'grp'),
            ('x2', 'grp'),
            ('x3', 'grp'),
            ('x4', 'grp')],
           names=['x', 'grp'])
           

 

 

(2) for loop 순환문(for x, grp in mult_idx:)으로 model = ols('{} ~ {}'.format(x, grp) 의 선형모델의  y, x 부분의 변수 이름을 바꾸어가면서 sm.stats.anova_lm(model, typ=1) 로 일원분산분석을 수행합니다. 이렇게 해서 나온 일원분산분석 결과 테이블을 anova_tables.append(anova_table) 로 순차적으로 append 해나가면 됩니다.  

 

# ANOVA test for multiple combinations of X and Group
import statsmodels.api as sm
from statsmodels.formula.api import ols

anova_tables = []
for x, grp in mult_idx:
    model = ols('{} ~ {}'.format(x, grp), data=df).fit()
    anova_table = sm.stats.anova_lm(model, typ=1)
    anova_tables.append(anova_table)

df_anova_tables = pd.concat(anova_tables, keys=mult_idx, axis=0)

df_anova_tables
[Out]

df	sum_sq	mean_sq	F	PR(>F)
x1	grp	grp	1.0	0.235732	0.235732	0.221365	6.391661e-01
Residual	88.0	93.711314	1.064901	NaN	NaN
x2	grp	grp	1.0	0.448662	0.448662	0.415853	5.206912e-01
Residual	88.0	94.942885	1.078896	NaN	NaN
x3	grp	grp	1.0	6375.876120	6375.876120	259.202952	5.779374e-28
Residual	88.0	2164.624651	24.598007	NaN	NaN
x4	grp	grp	1.0	13760.538009	13760.538009	256.515180	8.145953e-28
Residual	88.0	4720.684932	53.644147	NaN	NaN

anova tables

 

 

만약 특정 변수에 대한 일원분산분석 결과만을 조회하고 싶다면, 아래처럼 DataFrame의 MultiIndex 에 대해 인덱싱을 해오면 됩니다. 가령, 'x3' 에 대한 집단별 평균 차이 여부를 검정한 결과는 아래처럼 인덱싱해오면 됩니다. 

 

## Getting values of 'x3' from ANOVA tables
df_anova_tables.loc[('x3', 'grp', 'grp')]
[Out]

df         1.000000e+00
sum_sq     6.375876e+03
mean_sq    6.375876e+03
F          2.592030e+02
PR(>F)     5.779374e-28
Name: (x3, grp, grp), dtype: float64

 

 

F 통계량과 p-value 에 대해서 조회하고 싶으면 위의 결과에서 DataFrame 의 칼럼 이름으로 선택해오면 됩니다. 

 

# F-statistic
df_anova_tables.loc[('x3', 'grp', 'grp')]['F']
[Out]
259.2029515179077


# P-value
df_anova_tables.loc[('x3', 'grp', 'grp')]['PR(>F)']
[Out]
5.7793742588216585e-28

 

 

 

MultiIndex 를 인덱싱해오는게 좀 불편할 수 도 있는데요, 이럴 경우  df_anova_tables.reset_index() 로  MultiIndex 를 칼럼으로 변환해서 사용할 수도 있습니다. 

# resetting index to columns
df_anova_tables_2 = df_anova_tables.reset_index().dropna()


df_anova_tables_2
[Out]

level_0	level_1	level_2	df	sum_sq	mean_sq	F	PR(>F)
0	x1	grp	grp	1.0	0.235732	0.235732	0.221365	6.391661e-01
2	x2	grp	grp	1.0	0.448662	0.448662	0.415853	5.206912e-01
4	x3	grp	grp	1.0	6375.876120	6375.876120	259.202952	5.779374e-28
6	x4	grp	grp	1.0	13760.538009	13760.538009	256.515180	8.145953e-28

 

 

Greenplum DB에서 PL/Python (또는 PL/R)을 사용하여 여러개의 숫자형 변수에 대해 일원분산분석을 분산병렬처리하는 방법(one-way ANOVA in parallel using PL/Python on Greenplum DB)은 rfriend.tistory.com/640 를 참고하세요. 

 

 

[reference] 

* ANOVA test using Python statsmodels
 
: https://www.statsmodels.org/stable/generated/statsmodels.stats.anova.anova_lm.html

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요! :-)

 

728x90
반응형
Posted by Rfriend
,

이전 포스팅의  rfriend.tistory.com/262 에서는 Python pandas DataFrame의 결측값을 fillna() 메소드를 사용해서 특정 값으로 채우거나 평균으로 대체하는 방법을 소개하였습니다. 

 

이번 포스팅에서는 Python pandas DataFrame 의 결측값을 선형회귀모형(linear regression model) 을  사용하여 예측/추정하여 채워넣는 방법을 소개하겠습니다. (물론, 아래의 동일한 방법을 사용하여 선형회귀모형 말고 다른 통계, 기계학습 모형을 사용하여 예측/추정한 값으로 대체할 수 있습니다.)

 

(1) 결측값을 제외한 데이터로부터 선형회귀모형 훈련하기

    (training, fitting a linear regression model using non-missing values)

(2) 선형회귀모형으로 부터 추정값 계산하기 (prediction using linear regression model)

(3) pandas 의 fillna() 메소드 또는  numpy의  np.where()  메소드를 사용해서 결측값인 경우 선형회귀모형 추정값으로 대체하기 (filling missing values using the predicted values by linear regression model)

 

fill missing values of pandas DataFrame using predicted values by machine learning model

 

아래에는 예제로 사용할 데이터로 전복(abalone) 공개 데이터셋을 읽어와서 1행~3행의 'whole_weight' 칼럼 값을 결측값(NA) 으로 변환해주었습니다. 

import pandas as pd
import numpy as np

# read abalone dataset from website
abalone = pd.read_csv("http://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data", 
                 header=None, 
                 names=['sex', 'length', 'diameter', 'height', 
                          'whole_weight', 'shucked_weight', 
                          'viscera_weight', 'shell_weight', 'rings'])
                          

# get 10 observations as an example
df = abalone.copy()[:10]


# check missing values : no missing value at all
pd.isnull(df).sum()
# sex               0
# length            0
# diameter          0
# height            0
# whole_weight      0
# shucked_weight    0
# viscera_weight    0
# shell_weight      0
# rings             0
# dtype: int64


# insert NA values as an example
df.loc[0:2, 'whole_weight'] = np.nan

df
# sex	length	diameter	height	whole_weight	shucked_weight	viscera_weight	shell_weight	rings
# 0	M	0.455	0.365	0.095	NaN	0.2245	0.1010	0.150	15
# 1	M	0.350	0.265	0.090	NaN	0.0995	0.0485	0.070	7
# 2	F	0.530	0.420	0.135	NaN	0.2565	0.1415	0.210	9
# 3	M	0.440	0.365	0.125	0.5160	0.2155	0.1140	0.155	10
# 4	I	0.330	0.255	0.080	0.2050	0.0895	0.0395	0.055	7
# 5	I	0.425	0.300	0.095	0.3515	0.1410	0.0775	0.120	8
# 6	F	0.530	0.415	0.150	0.7775	0.2370	0.1415	0.330	20
# 7	F	0.545	0.425	0.125	0.7680	0.2940	0.1495	0.260	16
# 8	M	0.475	0.370	0.125	0.5095	0.2165	0.1125	0.165	9
# 9	F	0.550	0.440	0.150	0.8945	0.3145	0.1510	0.320	19

 

 

 

(1) 결측값을 제외한 데이터로부터 선형회귀모형 훈련하기  (training, fitting a linear regression model using non-missing values)

 

 pandas 패키지의 dropna() 메소드를 이용해서 결측값이 포함된 행을 제거한 후의 설명변수  ' diameter', 'height', 'shell_weight' 를 'X' DataFrame 객체로 만들고, ' whole_weight' 를 종속변수  'y' Series로 만든 후에,  sklearn의  linear_model.LinearRegression() 메소드로   lin_reg.fit(X, y) 로 선형회귀모형을 적합하였습니다. 

 

# initiate sklearn's linear regression
from sklearn import linear_model

lin_reg = linear_model.LinearRegression()


# X and y after excluding missing values
X = df.dropna(axis=0)[['diameter', 'height', 'shell_weight']] 
y = df.dropna(axis=0)['whole_weight'] 


# fitting linear regression model using non-missing values
lin_reg_model = lin_reg.fit(X, y)

 

 

 

(2) 선형회귀모형으로 부터 추정값 계산하기 (prediction using linear regression model)

 

위의 (1)번에서 적합한 모델에 predict() 함수를 사용해서  'whole_weight'  의 값을 추정하였습니다. 

 

# Prediction
y_pred = lin_reg_model.predict(df.loc[:, ['diameter', 'height', 'shell_weight']])

y_pred
# array([0.54856977, 0.21868994, 0.69091523, 0.50734984, 0.19206521,
#        0.35618402, 0.80347213, 0.7804138 , 0.53164895, 0.85086606])

 

 

 

(3) pandas 의 fillna() 메소드 또는  numpy의  np.where()  메소드를 사용해서 결측값인 경우 선형회귀모형 추정값으로 대체하기 (filling missing values using the predicted values by linear regression model)

 

(방법 1)  pandas  의  fillna()  메소드를 사용해서  'whole_weight' 값이 결측값인 경우에는  위의 (2)번에서 선형회귀모형을 이용해 추정한 값으로 대체를 합니다. 이때  'y_pred' 는  2D numpy array 형태이므로, 이를 flatten() 메소드를 사용해서  1D array 로 바꾸어주고, 이를  pd.Series() 메소드를 사용해서 Series 데이터 유형으로 변환을 해주었습니다.   inplace=True 옵션을 사용해서 df DataFrame 내에서 결측값이 선형회귀모형 추정값으로 대체되고 나서 저장되도록 하였습니다. 

 

(방법 2)  numpy의 where() 메소드를 사용해서,  결측값인 경우  (즉,  isnull() 이 True)  pd.Series(y_pred.flatten()) 값을 가져옥, 결측값이 아닌 경우 기존 값을 가져와서  'whole_weight' 에 값을 할당하도록 하였습니다. 

 

(방법 3) for loop 을 돌면서 매 행의  'whole_weight' 값이 결측값인지 여부를 확인 후,  만약  결측값이면 (isnull() 이 True 이면) 위의 (1)에서 적합된 회귀모형에 X값들을 넣어줘서 예측을 해서 결측값을 채워넣는 사용자 정의함수를 만들고 이를  apply() 함수로 적용하는 방법도 생각해볼 수는 있으나, 데이터 크기가 큰 경우  for loop 연산은 위의 (방법 1), (방법 2) 의   vectorized operation 대비 성능이 많이 뒤떨어지므로 소개는 생략합니다. 

 

## filling missing values using predicted values by a linear regression model

## -- (방법 1) pd.fillna() methods
df['whole_weight'].fillna(pd.Series(y_pred.flatten()), inplace=True)


## -- (방법 2) np.where()
df['whole_weight'] = np.where(df['whole_weight'].isnull(), 
                              pd.Series(y_pred.flatten()), 
                              df['whole_weight'])
                              
## results
df
# sex	length	diameter	height	whole_weight	shucked_weight	viscera_weight	shell_weight	rings
# 0	M	0.455	0.365	0.095	0.548570	0.2245	0.1010	0.150	15
# 1	M	0.350	0.265	0.090	0.218690	0.0995	0.0485	0.070	7
# 2	F	0.530	0.420	0.135	0.690915	0.2565	0.1415	0.210	9
# 3	M	0.440	0.365	0.125	0.516000	0.2155	0.1140	0.155	10
# 4	I	0.330	0.255	0.080	0.205000	0.0895	0.0395	0.055	7
# 5	I	0.425	0.300	0.095	0.351500	0.1410	0.0775	0.120	8
# 6	F	0.530	0.415	0.150	0.777500	0.2370	0.1415	0.330	20
# 7	F	0.545	0.425	0.125	0.768000	0.2940	0.1495	0.260	16
# 8	M	0.475	0.370	0.125	0.509500	0.2165	0.1125	0.165	9
# 9	F	0.550	0.440	0.150	0.894500	0.3145	0.1510	0.320	19

 

 

많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요! :-)

 

728x90
반응형
Posted by Rfriend
,

이전 포스팅에서는 무작위(확률, 임의) 표본 추출과 관련하여,

- numpy.random() 메소드를 이용하여 확률분포별 확률 표본 추출, 난수 생성: https://rfriend.tistory.com/284

- 그룹별 무작위 표본 추출: https://rfriend.tistory.com/407

- 기계학습을 위한 Train, Test 데이터셋 분할: https://rfriend.tistory.com/519

- 층화 무작위 추출을 통한 Train, Test 데이터셋 분할: https://rfriend.tistory.com/520

방법에 대하여 소개하였습니다.



이번 포스팅에서는 Python pandas 모듈의 DataFrame.sample() 메소드를 사용해서 DataFrame으로 부터 무작위 (확률, 임의) 표본 추출 (random sampling) 하는 방법을 소개하겠습니다.


(1) DataFrame으로 부터 특정 개수의 표본을 무작위로 추출하기 (number)

(2) DataFrame으로 부터 특정 비율의 표본을 무작위로 추출하기 (fraction)

(3) DataFrame으로 부터 복원 무작위 표본 추출하기 (random sampling with replacement)

(4) DataFrame으로 부터 가중치를 부여하여 표본 추출하기 (weights)

(5) DataFrame으로 부터 칼럼에 대해 무작위 표본 추출하기 (axis=1, axis='column)

(6) DataFrame으로 부터 특정 칼럼에 대해 무작위 표본 추출한 결과를 numpy array로 할당하기



[ pandas DataFrame에서 무작위 (확률) 표본 추출하기: pandas.DataFrame.sample() ]



  (1) DataFrame으로 부터 특정 개수의 표본을 무작위(확률)로 추출하기 (number)


예제로 사용할 4개의 관측치와 3개의 칼럼을 가진 pandas DataFrame을 만들어보겠습니다.

(참조 [1] 의 pandas tutorial 코드 사용하였습니다.)



import pandas as pd

df = pd.DataFrame({'num_legs': [2, 4, 8, 0],
                   'num_wings': [2, 0, 0, 0],
                   'num_specimen_seen': [10, 2, 1, 8]},
                  index=['falcon', 'dog', 'spider', 'fish'])

df


num_legsnum_wingsnum_specimen_seen
falcon2210
dog402
spider801
fish008

 



DataFrame.sample() 메소드의 n 매개변수를 사용해서 특정 개수 (number)의 표본을 무작위로 추출할 수 있습니다. 그리고 random_state 매개변수는 무작위(확률) 표본 추출을 위한 난수(random number)를 생성할 때 초기값(seed number) 로서, 재현가능성(reproducibility)을 위해서 설정해줍니다.


아래 예에서는 총 4개 관측치 중에서 2개의 관측치 (n=2) 를 무작위 표본 추출해보았습니다. Index를 기준으로 n 개수 만큼 표본을 추출해서 모든 칼럼의 값을 pandas DataFrame 자료구조로 반환합니다.



df.sample(n=2, # number of items from axis to return.
          random_state=1004) # seed for random number generator for reproducibility



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (2) DataFrame으로 부터 특정 비율의 표본을 무작위로 추출하기 (fraction)


DataFrame으로 부터 특정 비율(fraction)으로 무작위 표본 추출을 하고 싶으면 frac 매개변수에 0~1 사이의 부동소수형(float) 값을 입력해주면 됩니다.



df.sample(frac=0.5, # fraction of axis items to return.
          random_state=1004)



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 



만약 비복원 추출 모드 (replace = False, 기본 설정) 에서 frac 값이 1을 초과할 경우에는 "ValueError: Replace has to be set to 'True' when upsampling the population 'frac' > 1." 이라는 에러가 발생합니다. 왜냐하면 모집단의 표본 개수 (100%, frac=1) 보다 더 많은 표본을 비복원 추출로는 할 수 없기 때문입니다. (복원 추출의 경우 동일한 관측치를 다시 표본 추출할 수 있으므로 frac > 1 인 경우도 가능함.)



## ValueError: Replace has to be set to `True` when upsampling the population `frac` > 1.
df.sample(frac=1.5,
          random_state=1004)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-45-2fcc4494d7ae> in <module>
----> 1 df.sample(frac=1.5, # fraction of axis items to return. 
      2           random_state=1004)

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5326             n = 1
   5327         elif frac is not None and frac > 1 and not replace:
-> 5328             raise ValueError(
   5329                 "Replace has to be set to `True` when "
   5330                 "upsampling the population `frac` > 1."

ValueError: Replace has to be set to `True` when upsampling the population `frac` > 1.

 



만약 DataFrame.sample() 메소드에서 표본 개수 n 과 표본추출 비율 frac 을 동시에 설정하게 되면 "ValueError: Please enter a value for 'frac' OR 'n', not both" 에러가 발생합니다. n 과 frac 둘 중에 하나만 입력해야 합니다.



## parameter 'n' and 'frac' cannot be used at the same time.
## ValueError: Please enter a value for `frac` OR `n`, not both
df.sample(n=2, frac=0.5)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-b31ebc150882> in <module>
      1 ## parameter 'n' and 'frac' cannot be used at the same time.
      2 ## ValueError: Please enter a value for `frac` OR `n`, not both
----> 3 df.sample(n=2, frac=0.5)

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5335             n = int(round(frac * axis_length))
   5336         elif n is not None and frac is not None:
-> 5337             raise ValueError("Please enter a value for `frac` OR `n`, not both")
   5338 
   5339         # Check for negative sizes

ValueError: Please enter a value for `frac` OR `n`, not both

 




  (3) DataFrame으로 부터 복원 무작위 표본 추출하기

      (random sampling with replacement)


한번 추출한 표본을 다시 모집단에 되돌려 넣고 추출하는 방법을 복원 추출법 (sampling with replacement) 이라고 합니다. 복원 추출법을 사용하면 동일한 표본이 중복해서 나올 수 있습니다.


DataFrame.sample() 메소드에서는 repalce=True 로 설정하면 복원 추출을 할 수 있습니다. 많은 경우 한번 추출된 표본은 되돌려 놓지 않고 표본을 추출하는 비복원 추출(sampling without replacement)을 사용하며, 기본 설정은 replace=False 입니다.



## replace=True: random sampling with replacement
df.sample(n=8, # or equivalently: frac=2
          replace=True, # random sampling with replacement
          random_state=1004)



num_legsnum_wingsnum_specimen_seen
spider801
fish008
fish008
dog402
fish008
fish008
fish008
spider801

 



만약 비복원 추출 모드 (replace=False) 에서 원본 DataFrame 의 관측치 개수 (행의 개수) 보다 많은 수의 표본을 무작위 추출하고자 한다면 "ValueError: Cannot take a larger sample than population when 'replace=False'" 에러 메시지가 발생합니다.  모집단이 가지고 있는 관측치 수보다 더 많은 수의 표본을 중복이 없는 "비복원 추출"로는 불가능하기 때문입니다.

(복원추출(sampling with replacement, replace=True) 모드 에서는 동일한 표본을 중복 추출이 가능하므로 모집단 관측치 수보다 많은 수의 표본 추출이 가능함.)



## ValueError: Cannot take a larger sample than population when 'replace=False'
df.sample(n=8,
          replace=False # random sampling without replacement
)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-42-40c76bd4c271> in <module>
      1 ## replace=True: random sampling with replacement
----> 2 df.sample(n=8, # or equivalently: frac=2
      3           replace=False # random sampling without replacement
      4 )

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5343             )
   5344 
-> 5345         locs = rs.choice(axis_length, size=n, replace=replace, p=weights)
   5346         return self.take(locs, axis=axis)
   5347 

mtrand.pyx in numpy.random.mtrand.RandomState.choice()

ValueError: Cannot take a larger sample than population when 'replace=False'

 




  (4) DataFrame으로 부터 가중치를 부여하여 표본 추출하기 (weights)


만약에 DataFrame 내의 특정 칼럼의 값을 기준으로 가중치를 부여하여 무작위 표본 추출을 하고 싶다면 DataFrame.sample() 메소드의 weights 매개변수에 가중치로 사용할 칼럼 이름을 설정해주면 됩니다.


아래 예에서는 df DataFrame의 'num_specimen_seen' 칼럼의 값이 크면 클수록 표본으로 뽑힐 확률이 더 크도록 가중치(weights)를 부여해보았습니다. 아니나 다를까, 'num_specimen_seen' 값이 10, 8 인 falcon, fish가 표본으로 추출이 되었네요. 

(물론, 표본추출 시행을 계속 하다보면 num_specimen_seen 값이 1인 spider나 2인 dog 도 표본으로 뽑히는 때가 오긴 올겁니다. 다만, num_specimen_seen 값의 가중치로 인해 표본 추출될 확률이 낮아 상대적으로 작은 빈도로 추출이 되겠지요.)



## Using a DataFrame column as weights.
## Rows with larger value in the num_specimen_seen column are more likely to be sampled.
df.sample(n=2,
          weights='num_specimen_seen'

)



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (5) DataFrame으로 부터 칼럼에 대해 무작위 표본 추출하기 (axis=1, axis='column)


위의 (1) ~ (4) 까지는 axis=0, 즉 Index 에 대해서 무작위 표본 추출을 해서 전체 칼럼의 값을 반환하였습니다.


DataFrame.sample() 메소드의 axis 매개변수를 axis=1, 또는 axis='column' 으로 설정을 해주면 여러개의 칼럼에 대해서 무작위로 표본 추출을 해서 전체 행(all rows, random sampled columns) 을 반환합니다. (이런 요건의 분석은 그리 많지는 않을것 같습니다만, 이런 기능도 있다는 정도로만 알아두면 되겠습니다.)



## Axis to sample: by column
df.sample(n=2,
          random_state=1004,
          axis=1) # or equivalently, axis='column'



num_legsnum_wings
falcon22
dog40
spider80
fish00

 



axis 매개변수의 기본 설정은 대부분의 분석 요건에 해당하는 Index 기준의 무작위 표본 추출인 axis=0 (or, axis='index') 입니다.



## Axis to sample: by index
df.sample(n=2,
          random_state=1004,
          axis=0) # or equivalently, axis='index', default



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (6) DataFrame으로 부터 특정 칼럼에 대해 무작위 표본 추출한 결과를

       numpy array로 할당하기


만약 DataFrame의 여러개의 칼럼 중에서 특정 하나의 칼럼에 대해서만 무작위 표본 추출을 하고 싶다면 DataFrame['column_name'] 형식으로 먼저 Series 로 특정 칼럼의 값을 가져오고, 이에 대해서 sample() 메소드를 사용하면 됩니다.



## Sampling only for a column
df['num_legs'].sample(n=2, random_state=1004)


[Out] 
falcon 2 fish 0 Name: num_legs, dtype: int64

 



df['num_specimen_seen'].sample(n=2, random_state=1004)


[Out]
falcon 10 fish 8 Name: num_specimen_seen, dtype: int64

 



이렇게 DataFrame으로 부터 특정 하나의 칼럼 값을 Series 로 인덱싱해와서 무작위 표본 추출을 하면, 역시 그 결과 객체의 데이터 유형도 Series 입니다.



## Assigning sampling results as Series
samp_Series = df['num_legs'].sample(n=2)
type(samp_Series)


[Out] pandas.core.series.Series

 



만약, DataFrame으로 부터 특정 하나의 칼럼 값 Series 로 부터의 무작위 표본 추출 결과를 Numpy Array로 할당해서 결과를 가져오고 싶다면 numpy.array() 로 Series 를 array 로 변환해주면 됩니다.



## Assigning sampling results as numpy array
import numpy as np
samp_array = np.array(df['num_legs'].sample(n=2))
type(samp_array)

[Out] numpy.ndarray


samp_array

[Out] array([0, 2])




[ Reference ]

* pandas.DataFrame.sample: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html



이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)




728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 pandas 모듈의 DataFrame.iterrows(),  DataFrame.iteritems(), DataFrame.itertuples() 의 메소드 3총사와 for loop 반복문 활용하여 pandas DataFrame 자료의 행, 열, (행, 열) 튜플에 대해서 순환 반복 (for loop iteration) 하여 자료를 반환하는 방법을 소개하겠습니다.


(1) pd.DataFrame.iterrows() : 행에 대해 순환 반복
    (Iterate over DataFrame rows as (index, Series) pairs.)

(2) pd.DataFrame.iteritems() : 열에 대해 순환 반복
    (Iterate over DataFrame (column name, Series) pairs.)

(3) pd.DataFrame.itertuples() : 이름이 있는 튜플 (인덱스, 행, 열) 에 대해 순환 반복

    (Iterate over DataFrame rows as namedtuples)



[ Pandas DataFrame의 행, 열, (행, 열) 튜플 순환 반복 ]





  (1) DataFrame.iterrows() : 행에 대해 순환 반복
      (Iterate over DataFrame rows as (index, Series) pairs.)


먼저 pandas 모듈을 importing 하고, 예제로 사용할 2개의 칼럼과 인덱스를 가진 간단한 DataFrame을 만들어보겠습니다.



import pandas as pd


df = pd.DataFrame(
    {'price': [100, 200, 300],
     'weight': [20.3, 15.1, 25.9]},
    index=['idx_a', 'idx_b', 'idx_c'])

df


priceweight
idx_a10020.3
idx_b20015.1
idx_c30025.9




이제 DataFrame.iterrows() 메소드와 for loop 반복문을 사용해서 행(row)에 대해서 순환하면서 인덱스 이름과 각 행별 칼럼별 데이터를 출력해보겠습니다.



## DataFrame.iterrows()
for idx, row in df.iterrows():
    print("** index name:", idx)
    print(row)
    print("------"*5)


[Out]
** index name: idx_a price 100.0 weight 20.3 Name: idx_a, dtype: float64 ------------------------------ ** index name: idx_b price 200.0 weight 15.1 Name: idx_b, dtype: float64 ------------------------------ ** index name: idx_c price 300.0 weight 25.9 Name: idx_c, dtype: float64 ------------------------------



DataFrame에 여러개의 칼럼이 있고, 이중에서 특정 칼럼에 대해서만 행을 순회하면서 행별 특정 칼럼의 값을 반복해서 출력하고 싶으면 row['column_name'] 또는 row[position_int] 형식으로 특정 칼럼의 이름이나 위치 정수를 넣어주면 됩니다.



## accessing to column of each rows by indexing
for idx, row in df.iterrows():
    print(idx)
    print(row['price']) # or print(row[0])
    print("-----")


[Out]
idx_a 100.0 ----- idx_b 200.0 ----- idx_c 300.0 -----



DataFrame.iterrows() 메소드는 결과물로 (index, Series) 짝(pairs)을 반환합니다. 따라서 원본 DataFrame에서의 데이터 유형일 보존하지 못하므로 행별 Series 에서는 데이터 유형이 달라질 수 있습니다.


가령, 예제의 DataFrame에서 'price' 칼럼의 데이터 유형은 '정수형(integer64)' 인데 반해, df.iterrows() 로 반환된 'row['price']'의 데이터 유형은 '부동소수형(float64)'으로 바뀌었습니다.



## DataFrame.iterrows() returns a Series for each row,
## it does not preserve dtypes across the rows.
print('Data type of df price:', df['price'].dtype) # int
print('Data type of row price:', row['price'].dtype) # float


[Out]
Data type of df price: int64 Data type of row price: float64





  (2) DataFrame.iteritems() : 열에 대해 순환 반복
      (Iterate over DataFrame (column name, Series) pairs.)


위의 (1)번이 DataFrame의 행(row)에 대해 순환 반복을 했다면, 이번에는 pandas DataFrame의 열(column)에 대해 iteritems() 메소드와 for loop 문을 사용해 순환 반복(iteration) 하면서 '칼럼 이름 (column name)' 과 '행별 값 (Series for each row)' 을 짝으로 하여 출력해 보겠습니다.



df


priceweight
idx_a10020.3
idx_b20015.1
idx_c30025.9



for col, item in df.iteritems():
    print("** column name:", col)
    print(item) # = print(item, sep='\n')
    print("-----"*5)


[Out]
** column name: price idx_a 100 idx_b 200 idx_c 300 Name: price, dtype: int64 ------------------------- ** column name: weight idx_a 20.3 idx_b 15.1 idx_c 25.9 Name: weight, dtype: float64 -------------------------




만약 DataFrame.iteritems() 와 for loop 문으로 열(column)에 대해 순환 반복하여 각 행(row)의 값을 출력하는 중에 특정 행만을 출력하고 싶으면 '행의 위치 정수(position index of row)'나 '행의 인덱스 이름 (index name of row)' 으로 item 에서 인덱싱해주면 됩니다.



for col, item in df.iteritems():
    print(col)
    print(item[0]) # = print(item['idx_a'])


[Out]
price 100 weight 20.3





  (3) DataFrame.itertuples() : 이름이 있는 튜플 (인덱스, 행, 열) 에 대해 순환 반복

    (Iterate over DataFrame rows as namedtuples)


위의 (1) 번의 DataFrame.iterrows() 에서는 DataFrame의 행(row)에 대해 순환 반복, (2) 번의 DataFrame.iteritems() 에서는 열(column, item)에 대해 순환 반복하였습니다. 반면에, 경우에 따라서는 (인덱스, 행, 열) 의 튜플 묶음 단위로 순환 반복을 하고 싶을 때 DataFrame.itertuples() 메소드를 사용할 수 있습니다.


각 행과 열에 대해서 순환 반복하면서 값을 가져오고, 이를 zip() 해서 묶어주는 번거로운 일을 DataFrame.itertuples() 메소드는 한번에 해주니 알아두면 매우 편리한 메소드입니다.


아래의 예는 DataFrame.itertuples() 메소드와 for loop 문을 사용해서 'df' DataFrame의 이름있는 튜플인 namedtuple (Index, row, column) 에 대해서 순환 반복하면서 출력을 해보겠습니다.



df


priceweight
idx_a10020.3
idx_b20015.1
idx_c30025.9



for row in df.itertuples():
    print(row)


[Out] 
Pandas(Index='idx_a', price=100, weight=20.3) Pandas(Index='idx_b', price=200, weight=15.1) Pandas(Index='idx_c', price=300, weight=25.9)



만약 인덱스를 포함하고 싶지 않다면 index=False 로 매개변수를 설정해주면 됩니다.



## By setting the indx=False, we can remove the index as the first element of the tuple.
for row in df.itertuples(index=False):
    print(row)


[Out] 
Pandas(price=100, weight=20.3) Pandas(price=200, weight=15.1) Pandas(price=300, weight=25.9)



DataFrame.itertuples() 메소드가 이름있는 튜플(namedtuples)을 반환한다고 했는데요, name 매개변수로 튜플의 이름을 부여할 수도 있습니다. 아래 예에서는 name='Product' 로 해서 튜플에 'Product'라는 이름을 부여해보았습니다.



## Setting a custom name for the yielded namedtuples.
for row in df.itertuples(name='Product'):
    print(row)


[Out]
Product(Index='idx_a', price=100, weight=20.3) Product(Index='idx_b', price=200, weight=15.1) Product(Index='idx_c', price=300, weight=25.9)



DataFrame.iterrows() 는 (index, Series) 짝을 반환하다보니 원본 DataFrame의 데이터 유형을 보존하지 못한다고 했는데요, DataFrame.itertuples() 는 원본 DataFrame의 데이터 유형을 그대로 보존합니다.


아래 예에서 볼 수 있듯이 df['price']의 데이터 유형과 df.itertuples()의 결과의 row.price 의 데이터 유형이 둘 다 '정수(int64)'로 동일합니다.



## DataFrame.itertuples() preserves dtypes, returning namedtuples of the values.
print('Data type of df price:', df['price'].dtype) # int
print('Data type of row price:', type(row.price)) # int


[Out] 
Data type of df price: int64 Data type of row price: <class 'int'>



[Reference]

* DataFrame.iterrows(): https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html#pandas.DataFrame.iterrows

* DataFrame.iteritems(): https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iteritems.html

* DataFrame.itertuples(): https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.itertuples.html#pandas.DataFrame.itertuples


이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요! :-)




728x90
반응형
Posted by Rfriend
,

pandas의 Series나 DataFrame 자료구조로 저장된 시계열 데이터에 대해서 이전 값 대비 현재 값의 변동율(change percentage)을 구하고 싶을 때 pandas 의 pct_change() 메소드를 사용하면 매우 편리하게 계산할 수 있습니다. 


이번 포스팅에서는 Python pandas 패키지의 pct_change() 메소드를 사용하여 


pandas Series에서

- (1) 이전 원소 대비 변동률 

       (Percentage change between the current and a prior element)

- (2) 이전 2개 원소 대비 변동률 

       (Percentage change between the current and 2 periods prior element)

- (3) 결측값을 이전 원소 값으로 대체 후 이전 원소 대비 변동률 

       (Percentage change between the current and a prior element after filling the missing values using the 'forward fill' method)


pandas DataFrame에서

- (4) 그룹별 이전 분기 대비 변동률 

      (Percentage change between the current and a prior quarter by Group)

- (5) 그룹별 전년 동분기 대비 변동률  

       (Percentage change between the current and a year before by Group)




* pandas의 pct_change() 메소드는 Series와 DataFrame 자료구조 모두에서 동일하게 사용 가능합니다. 



-- pandas Series 에서


  (1) 이전 원소 대비 변동률 

       (Percentage change between the current and a prior element)


pandas의 pct_change() 메소드는 기본 설정이 이전 원소 대비 현재 원소의 변동 비율(percentage change)을 계산해줍니다. 아래 pandas Series의 경우, 


첫번째 값은 이전 값이 없으므로 NaN

두번째 값의 첫번째 값 대비 변동률 = (20-10)/10 = 1.0

세번째 값의 두번째 값 대비 변동률 = (50-20)/20 = 1.5

네번째 값의 세번째 값 대비 변동률 = (55-50)/50 = 0.1

다섯번째 값의 네번째 값 대비 변동률 = (70-55)/55 = 0.27



In [1]: import pandas as pd


In [2]:

s = pd.Series([10, 20, 50, 55, 70])

s.pct_change()


Out[2]:

0 NaN

1 1.000000

2 1.500000

3 0.100000

4 0.272727

dtype: float64





  (2) 이전 2개 원소 대비 변동률 

       (Percentage change between the current and 2 periods prior element)


변동률을 구할 때 이전 값의 이동 기간을 periods 매개변수를 사용하면 자유롭게 설정해줄 수 있습니다. 가령, 위의 s Series 예에서 이전 2개 원소 대비 변동률은 s.pct_change(periods=2) 로 해주면 됩니다. 


첫번째와 두번째 값은 이전 2개 원소 값이 없으므로 NaN

세번째값의 이전 2개 원소 값 대비 변동률 = (50-10)/10 = 4.0

네번째값의 이전 2개 원소 값 대비 변동률 = (55-20)/20 = 1.75

다섯번째값의 이전 2개 원소 값 대비 변동률 = (70-50)/50 = 0.4



In [3]:

s = pd.Series([10, 20, 50, 55, 70])

s.pct_change(periods=2)


Out[3]:

0 NaN

1 NaN

2 4.00

3 1.75

4 0.40

dtype: float64





  (3) 결측값을 이전 원소 값으로 대체 후 이전 원소 대비 변동률 

       (Percentage change between the current and a prior element
        after filling the missing values using the 'forward fill' method
)


만약 데이터셋 안에 결측값(missing value)가 있다면 pct_change() 메소드에 pandas의 결측값 처리 매개변수를 그대로 차용하여 결측값을 처리한 후에 이전 원소 대비 변동률을 구할 수 있습니다. 


결측값을 처리하는 방법으로는, 

fill_method='ffill' or 'pad'       : 이전 값으로 결측값을 대체하여 채우기 (앞방향으로 채워나가기)

fill_method='bfill' or 'backfill'  : 이후 값으로 결측값을 대체하여 채우기 (뒤방향에서 채워나가기)



In [4]:

s2 = pd.Series([10, 20, 50, None, 70])

s2.pct_change(fill_method='ffill')


Out[4]:

0 NaN

1 1.0

2 1.5

3 0.0

4 0.4

dtype: float64

 




-- pandas DataFrame 에서


  (4) 그룹별 이전 분기 대비 변동률 

      (Percentage change between the current and a prior quarter by Group)


예제로 사용할 '제품(product)' 그룹을 가진 연도(year)/ 분기(quarter)  기간 별 판매량(sales) 칼럼으로 구성된 DataFrame을 만들어보겠습니다. 



In [5]:

# input data sale = pd.DataFrame( {'product': ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b'], 'year': [2018, 2018, 2018, 2018, 2019, 2019, 2019, 2019, 2020, 2020, 2020, 2020, 2018, 2018, 2018, 2018, 2019, 2019, 2019, 2019, 2020, 2020, 2020, 2020], 'quarter': [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], 'sales': [5, 6, 6, 8, 10, 20, 30, 40, 12, 25, 38, 50, 60, 65, 80, 95, 100, 125, 130, 140, 110, 130, 132, 144]})


sale

Out[5]:

product year quarter sales

0 a 2018 1 5

1 a 2018 2 6

2 a 2018 3 6

3 a 2018 4 8

4 a 2019 1 10

5 a 2019 2 20

6 a 2019 3 30

7 a 2019 4 40

8 a 2020 1 12

9 a 2020 2 25

10 a 2020 3 38

11 a 2020 4 50

12 b 2018 1 60

13 b 2018 2 65

14 b 2018 3 80

15 b 2018 4 95

16 b 2019 1 100

17 b 2019 2 125

18 b 2019 3 130

19 b 2019 4 140

20 b 2020 1 110

21 b 2020 2 130

22 b 2020 3 132

23 b 2020 4 144

 



이제 '제품(product)' 그룹별로 '이전 분기 대비 현재 분기의 변동율(change percentage between the current and the prior quarter)' 을 구해보겠습니다. 


물론 이번 예제 데이터는 년(year)/ 분기(quarter) 를 기준으로 이미 정렬이 되어 있기는 합니다만, 정렬이 안되어 있는 경우도 있을 수 있으므로 명확하게 시간 기준으로 정렬될 수 있도록 sort_values(by=['year', 'quarter'] 로 명시적으로 먼저 정렬을 해주었습니다.  다음으로 groupby(['product']) 메소드로 '제품(product)' 별로 그룹을 분할(split) 하여 그룹별로 이후 연산이 이루어지도록 하였습니다. 마지막으로 sales.pct_change() 메소드로 '판매량(sales)' 칼럼에 대해 '이전대비 변동률(pct_change)'을 '제품' 그룹별로 구해주었습니다. 



In [6]:

sale['sales_pct_change_by_1q'] = sale.\ sort_values(['year', 'quarter']).\ groupby(['product']).\ sales.pct_change()


sale

Out[6]:

product year quarter sales pct_change_by_1q

0 a 2018 1 NaN

1 a 2018 2 6 0.200000

2 a 2018 3 6 0.000000

3 a 2018 4 8 0.333333

4 a 2019 1 10 0.250000

5 a 2019 2 20 1.000000

6 a 2019 3 30 0.500000

7 a 2019 4 40 0.333333

8 a 2020 1 12 -0.700000

9 a 2020 2 25 1.083333

10 a 2020 3 38 0.520000

11 a 2020 4 50 0.315789

12 b 2018 1 60 NaN

13 b 2018 2 65 0.083333

14 b 2018 3 80 0.230769

15 b 2018 4 95 0.187500

16 b 2019 1 100 0.052632

17 b 2019 2 125 0.250000

18 b 2019 3 130 0.040000

19 b 2019 4 140 0.076923

20 b 2020 1 110 -0.214286

21 b 2020 2 130 0.181818

22 b 2020 3 132 0.015385

23 b 2020 4 144 0.090909

 




  (5) 그룹별 전년 동분기 대비 변동률  

       (Percentage change between the current and a year before by Group)


만약 이전 분기가 아니라 '전년 동일 분기' 대비 변동률을 구하고 싶다면 pct_change(periods=4) 처럼 periods=4 매개변수를 설정해주어서 4분기 이전 (즉, 전년 동일 분기)의 값 대비 변동률을 구해주면 됩니다. (만약 월 단위로 데이터가 집계되어 있다면 pct_change(periods=12) 로 해주면 됩니다.)



In [7]:

 sale['pct_change_by_1y'] = sale.sort_values(['year', 'quarter']).\

  groupby(['product']).\

  sales.pct_change(periods=4)


In [8]: sale.sort_values(by=['product', 'quarter', 'year'])

Out[8]:

product year quarter sales pct_change_by_1q pct_change_by_1y

0 a 2018 1 5 NaN NaN

4 a 2019 1 10 0.250000 1.000000

8 a 2020 1 12 -0.700000 0.200000

1 a 2018 2 6 0.200000 NaN

5 a 2019 2 20 1.000000 2.333333

9 a 2020 2 25 1.083333 0.250000

2 a 2018 3 6 0.000000 NaN

6 a 2019 3 30 0.500000 4.000000

10 a 2020 3 38 0.520000 0.266667

3 a 2018 4 8 0.333333 NaN

7 a 2019 4 40 0.333333 4.000000

11 a 2020 4 50 0.315789 0.250000

12 b 2018 1 60 NaN NaN

16 b 2019 1 100 0.052632 0.666667

20 b 2020 1 110 -0.214286 0.100000

13 b 2018 2 65 0.083333 NaN

17 b 2019 2 125 0.250000 0.923077

21 b 2020 2 130 0.181818 0.040000

14 b 2018 3 80 0.230769 NaN

18 b 2019 3 130 0.040000 0.625000

22 b 2020 3 132 0.015385 0.015385

15 b 2018 4 95 0.187500 NaN

19 b 2019 4 140 0.076923 0.473684

23 b 2020 4 144 0.090909 0.028571

 



또는 아래 방법처럼 분기(quarter)/ 년(year) 를 기준으로 먼저 정렬을 해놓고, 그 다음에 제품/분기 그룹(groupby(['product', 'quarter']) 별로 판매량의 변동률(sales.pct_change())를 구해도 결과는 같습니다. 



# or equvalently

sale['pct_change_by_1y'] = sale.sort_values(by=['quarter', 'year']).\

    groupby(['product', 'quarter']).\

        sales.pct_change()


sale.sort_values(by=['product', 'quarter', 'year'])

 



이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python pandas 데이터프레임 안에 여러개의 칼럼 별로 결측값을 대체하는 방법(how to fill missing values per each columns)을 다르게 하는 방법을 소개하겠습니다. 




먼저 예제로 사용할 간단할, 각 칼럼별로 결측값을 포함하고 있는 pandas DataFrame을 만들어보겠습니다. 



import numpy as np

import pandas as pd


# Make a DataFrame with missing values

df = pd.DataFrame({'a': [1, 2, 3, np.nan, 5], 

                   'b': [30, 20, np.nan, 35, 32], 

                   'c': [0.2, np.nan, 0.5, 0.3, 0.4], 

                   'd': ['c1', np.nan, 'c3', 'c4', 'c5'], 

                   'e': [10, 11, np.nan, 13, 15]})

 

df

abcde
01.030.00.2c110.0
12.020.0NaNNaN11.0
23.0NaN0.5c3NaN
3NaN35.00.3c413.0
45.032.00.4c515.0




다음으로, 각 칼럼별로 결측값을 대체하는 방법을 Dictionary에 Key (칼럼 이름): Value (결측값 대체 방법/값) 로 정리해보겠습니다. 


[ 칼럼별 결측값 대체 방법(전략) ]

  • 칼럼 'a': 0 으로 결측값 대체
  • 칼럼 'b': 평균(mean)으로 결측값 대체
  • 칼럼 'c': 중앙값(median)으로 결측값 대체
  • 칼럼 'd': 'Unknown'으로 결측값 대체
  • 칼럼 'e': 결측값 보간 (interpolation)



missing_fill_val = {'a': 0, 

                    'b': df.b.mean(), 

                    'c': df.c.median(), 

                    'd': 'Unknown', 

                    'e': df.e.interpolate()}


print(missing_fill_val)

{'a': 0, 'c': 0.35, 'b': 29.25, 'e': 0    10.0
1    11.0
2    12.0
3    13.0
4    15.0
Name: e, dtype: float64, 'd': 'Unknown'}

 



이제 준비가 다 되었으니 df DataFrame의 각 칼럼별로 서로 다른 결측값 대체 전략을 사용하여 결측값을 채워보겠습니다.  fillna() 함수의 괄호 안에 위에서 정의한 Dictionary 를 넣어주면 끝입니다. 간단하지요? ^^



df2 = df.fillna(missing_fill_val)


df2


a
bcde
01.030.000.20c110.0
12.020.000.35Unknown11.0
23.029.250.50c312.0
30.035.000.30c413.0
45.032.000.40c515.0

 



만약 원본 df DataFrame 을 보존할 필요가 없이 바로 결측값을 채워넣기 해서 수정하고 싶으면 inplace=True 옵션을 설정해주면 됩니다. 



df.fillna(missing_fill_val, inplace=True)

abcde
01.030.000.20c110.0
12.020.000.35Unknown11.0
23.029.250.50c312.0
30.035.000.30c413.0
45.032.000.40c515.0

 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요.



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 (1) 날짜 TimeStamp (m개) 와 (2) 고객 ID (n개) 의 두 개 칼럼을 가진 DataFrame에서 고객ID 별로 중간 중간 날짜 TimeStamp 가 비어있는 경우 모두 '0'으로 채워서 모든 조합 (m * n 개) 을 MultiIndex로 가진 시계열 데이터 형태의 DataFrame을 만들어보겠습니다. 


이번 포스팅에서는 pandas DataFrame에서 index 설정 관련해서 MultiIndex(), set_index(), reindex(), reset_index() 등 index 설정과 재설정 관련된 여러가지 메소드가 나옵니다. 


말로만 설명을 들으면 좀 어려운데요, 아래의 전(Before) --> 후(After) DataFrame의 전처리 Output Image를 보면 이해하기가 쉽겠네요. (시계열 데이터 분석을 할 때 아래의 우측에 있는 것처럼 데이터 전처리를 미리 해놓아야 합니다.) 




먼저, 년-월-일 TimeStamp (ts), 고객 ID (id), 구매 금액 (amt)의 세개 칼럼으로 구성된 거래 데이터(tr)인, 예제로 사용할 간단한 pandas DataFrame을 만들어보겠습니다. 



import pandas as pd


tr = pd.DataFrame({

    'ts': ['2020-06-01', '2020-06-02', '2020-06-03', '2020-06-01', '2020-06-03'], 

    'id': [1, 1, 1, 2, 3], 

    'amt': [100, 300, 50, 200, 150]})


tr

tsidamt
02020-06-011100
12020-06-021300
22020-06-03150
32020-06-012200
42020-06-033150

 



다음으로, 거래 데이터(tr) DataFrame의 날짜(ts)와 고객ID(id)의 모든 조합(all combination)으로 구성된  Multi-Index 를 만들어보겠습니다. pd.MultiIndex.from_product((A, B)) 메소드를 사용하면 Cartesian Product 을 수행하여, 총 A의 구성원소 개수 * B의 구성원소 개수 종류 만큼의 MultiIndex 를 생성해줍니다. 위 예제의 경우 날짜(ts)에 '2020-06-01', '2020-06-02', '2020-06-03'의 3개 날짜가 있고, 고객ID(id) 에는 1, 2, 3 의 3개가 있으므로 Cartesian Product 을 하면 아래의 결과처럼 3 * 3 = 9 의 조합이 생성이 됩니다. 



date_id_idx = pd.MultiIndex.from_product((set(tr.ts), set(tr.id)))

date_id_idx

MultiIndex([('2020-06-01', 1),
            ('2020-06-01', 2),
            ('2020-06-01', 3),
            ('2020-06-02', 1),
            ('2020-06-02', 2),
            ('2020-06-02', 3),
            ('2020-06-03', 1),
            ('2020-06-03', 2),
            ('2020-06-03', 3)],
           )



이제 위에서 Cartesian Product으로 만든 TimeStamp(ts)와 고객ID(id)의 모든 조합으로 구성된 MultiIndex인 date_id_idx 를 사용하여 index를 재설정(reindex) 해보겠습니다. 이때 원래(Before)의 DataFrame에는 없었다가 date_id_idx 로 index를 재설정(reindex) 하면서 새로 생긴 행에 구매금액(amt) 칼럼에는 'NaN' 의 결측값이 들어가게 됩니다. 이로서 처음에 5개 행이었던 것이 이제 9개(3*3=9) 행으로 늘어났습니다. 



tr_tsformat = tr.set_index(['ts', 'id']).reindex(date_id_idx)

tr_tsformat

amt
2020-06-011100.0
2200.0
3NaN
2020-06-021300.0
2NaN
3NaN
2020-06-03150.0
2NaN
3150.0

 



날짜(ts)와 고객ID(id)의 MultiIndex로 reindex() 하면서 생긴 NaN 값을 '0'으로 채워넣기(fill_value=0)해서 새로 DataFrame을 만들어보겠습니다. 



tr_tsformat = tr.set_index(['ts', 'id']).reindex(date_id_idx, fill_value=0)

tr_tsformat

amt
2020-06-011100
2200
30
2020-06-021300
20
30
2020-06-03150
20
3150

 



만약 날짜(ts)와 고객ID(id)의 MultiIndex로 이루어진 위의 DataFrame에서 MultiIndex를 칼럼으로 변경하고 싶다면 reset_index() 함수를 사용하면 됩니다. 칼럼 이름은 애초의 DataFrame과 동일하게 ['ts', 'id', 'amt'] 로 다시 부여해주었습니다. 



tr_tsformat.reset_index(inplace=True)

tr_tsformat

level_0level_1amt
02020-06-011100
12020-06-012200
22020-06-0130
32020-06-021300
42020-06-0220
52020-06-0230
62020-06-03150
72020-06-0320
82020-06-033

150

 


tr_tsformat.columns = ['ts', 'id', 'amt']

tr_tsformat

tsidamt
02020-06-011100
12020-06-012200
22020-06-0130
32020-06-021300
42020-06-0220
52020-06-0230
62020-06-03150
72020-06-0320
82020-06-033150




참고로, pandas에서 ID는 없이 TimeStamp만 있는 일정한 주기의 시계열 데이터 Series, DataFrame 만들기는 https://rfriend.tistory.com/501 를 참고하세요. 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 Python pandas에서 resampling 중 Downsampling 으로 집계할 때에 왼쪽과 오른쪽 중에서 어느쪽을 포함(inclusive, closed)할 지와 어느쪽으로 라벨 이름(label)을 쓸지(https://rfriend.tistory.com/507)에 대해서 알아보았습니다. 


이번 포스팅에서는 pandas의 resampling 중 Upsampling으로 시계열 데이터 주기(frequency)를 변환(conversion) 할 때 생기는 결측값을 처리하는 두 가지 방법을 소개하겠습니다. 


(1) Upsampling 으로 주기 변환 시 생기는 결측값을 채우는 방법 (filling forward/backward)

(2) Upsampling 으로 주기 변환 시 생기는 결측값을 선형 보간하는 방법 (linear interpolation)






예제로 사용할 간단할 2개의 칼럼을 가지고 주기(frequency)가 5초(5 seconds)인 시계열 데이터 DataFrame을 만들어보겠습니다. 



import pandas as pd

import numpy as np


rng = pd.date_range('2019-12-31', periods=3, freq='5S')

rng

[Out]:

DatetimeIndex(['2019-12-31 00:00:00', '2019-12-31 00:00:05', '2019-12-31 00:00:10'], dtype='datetime64[ns]', freq='5S')


ts = pd.DataFrame(np.array([0, 1, 3, 2, 10, 3]).reshape(3, 2), 

                  index=rng

                  columns=['col_1', 'col_2'])

ts

[Out]:

col_1col_2
2019-12-31 00:00:0001
2019-12-31 00:00:0532
2019-12-31 00:00:10103




이제 pandas resample() 메소드를 사용해서 주기가 5초(freq='5S')인 원래 데이터를 주기가 1초(freq='1S')인 데이터로 Upsampling 변환을 해보겠습니다. 그러면 아래처럼 새로 생긴 날짜-시간 행에 결측값(missing value)이 생깁니다ㅣ  



ts_upsample = ts.resample('S').mean()

ts_upsample

[Out]:

col_1col_2
2019-12-31 00:00:000.01.0
2019-12-31 00:00:01NaNNaN
2019-12-31 00:00:02NaNNaN
2019-12-31 00:00:03NaNNaN
2019-12-31 00:00:04NaNNaN
2019-12-31 00:00:053.02.0
2019-12-31 00:00:06NaNNaN
2019-12-31 00:00:07NaNNaN
2019-12-31 00:00:08NaNNaN
2019-12-31 00:00:09NaNNaN
2019-12-31 00:00:1010.03.0

 



위에 Upsampling을 해서 생긴 결측값들을 (1) 채우기(filling), (2) 선형 보간(linear interpolation) 해보겠습니다. 



  (1) Upsampling 으로 주기 변환 시 생기는 결측값을 채우기 (filling missing values)


(1-1) 앞의 값으로 뒤의 결측값 채우기 (Filling forward)



# (1) filling forward

ts_upsample.ffill()

ts_upsample.fillna(method='ffill')

ts_upsample.fillna(method='pad')

[Out]:

col_1col_2
2019-12-31 00:00:000.01.0
2019-12-31 00:00:010.01.0
2019-12-31 00:00:020.01.0
2019-12-31 00:00:030.01.0
2019-12-31 00:00:040.01.0
2019-12-31 00:00:053.02.0
2019-12-31 00:00:063.02.0
2019-12-31 00:00:073.02.0
2019-12-31 00:00:083.02.0
2019-12-31 00:00:093.02.0
2019-12-31 00:00:1010.03.0





(1-2) 뒤의 값으로 앞의 결측값 채우기 (Filling backward)



# (2)filling backward

ts_upsample.bfill()

ts_upsample.fillna(method='bfill')

ts_upsample.fillna(method='backfill')

[Out]:

col_1col_2
2019-12-31 00:00:000.01.0
2019-12-31 00:00:013.02.0
2019-12-31 00:00:023.02.0
2019-12-31 00:00:033.02.0
2019-12-31 00:00:043.02.0
2019-12-31 00:00:053.02.0
2019-12-31 00:00:0610.03.0
2019-12-31 00:00:0710.03.0
2019-12-31 00:00:0810.03.0
2019-12-31 00:00:0910.03.0
2019-12-31 00:00:1010.03.0





(1-3) 특정 값으로 결측값 채우기



# (3)fill Missing value with '0'

ts_upsample.fillna(0)

[Out]:

col_1col_2
2019-12-31 00:00:000.01.0
2019-12-31 00:00:010.00.0
2019-12-31 00:00:020.00.0
2019-12-31 00:00:030.00.0
2019-12-31 00:00:040.00.0
2019-12-31 00:00:053.02.0
2019-12-31 00:00:060.00.0
2019-12-31 00:00:070.00.0
2019-12-31 00:00:080.00.0
2019-12-31 00:00:090.00.0
2019-12-31 00:00:1010.03.0

 




(1-4) 평균 값으로 결측값 채우기



# (4) filling with mean value

# mean per column

ts_upsample.mean()

[Out]:
col_1    4.333333
col_2    2.000000
dtype: float64


ts_upsample.fillna(ts_upsample.mean())

[Out]:

col_1col_2
2019-12-31 00:00:000.0000001.0
2019-12-31 00:00:014.3333332.0
2019-12-31 00:00:024.3333332.0
2019-12-31 00:00:034.3333332.0
2019-12-31 00:00:044.3333332.0
2019-12-31 00:00:053.0000002.0
2019-12-31 00:00:064.3333332.0
2019-12-31 00:00:074.3333332.0
2019-12-31 00:00:084.3333332.0
2019-12-31 00:00:094.3333332.0
2019-12-31 00:00:1010.0000003.0

 




(1-5) 결측값 채우는 행의 개수 제한하기



# (5) limit the number of filling observation

ts_upsample.ffill(limit=1)

[Out]:

col_1col_2
2019-12-31 00:00:000.01.0
2019-12-31 00:00:010.01.0
2019-12-31 00:00:02NaNNaN
2019-12-31 00:00:03NaNNaN
2019-12-31 00:00:04NaNNaN
2019-12-31 00:00:053.02.0
2019-12-31 00:00:063.02.0
2019-12-31 00:00:07NaNNaN
2019-12-31 00:00:08NaNNaN
2019-12-31 00:00:09NaNNaN
2019-12-31 00:00:1010.03.0
 




  (2) Upsampling 으로 주기 변환 시 생기는 결측값을 선형 보간하기 (linear interpolation)



# (6) Linear interpolation by values

ts_upsample.interpolate(method='values') # by default

[Out]:

col_1col_2
2019-12-31 00:00:000.01.0
2019-12-31 00:00:010.61.2
2019-12-31 00:00:021.21.4
2019-12-31 00:00:031.81.6
2019-12-31 00:00:042.41.8
2019-12-31 00:00:053.02.0
2019-12-31 00:00:064.42.2
2019-12-31 00:00:075.82.4
2019-12-31 00:00:087.22.6
2019-12-31 00:00:098.62.8
2019-12-31 00:00:1010.03.0



ts_upsample.interpolate(method='values').plot()





# (7) Linear interpolation by time

ts_upsample.interpolate(method='time')

[Out]:

col_1col_2
2019-12-31 00:00:000.01.0
2019-12-31 00:00:010.61.2
2019-12-31 00:00:021.21.4
2019-12-31 00:00:031.81.6
2019-12-31 00:00:042.41.8
2019-12-31 00:00:053.02.0
2019-12-31 00:00:064.42.2
2019-12-31 00:00:075.82.4
2019-12-31 00:00:087.22.6
2019-12-31 00:00:098.62.8
2019-12-31 00:00:1010.03.0

 



많은 도움이 되었기를 바랍니다. 

이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)


728x90
반응형
Posted by Rfriend
,