[Python pandas] 결측값 채우기, 결측값 대체하기, 결측값 처리 (filling missing value, imputation of missing values) : df.fillna()
Python 분석과 프로그래밍/Python 데이터 전처리 2016. 12. 9. 23:28지난번 포스팅에서는 결측값 여부 확인, 결측값 개수 세기 등을 해보았습니다.
이번 포스팅에서는 결측값을 채우고 대체하는 다양한 방법들로서,
(1) 결측값을 특정 값으로 채우기 (replace missing valeus with scalar value)
(2) 결측값을 앞 방향 혹은 뒷 방향으로 채우기 (fill gaps forward or backward)
(3) 결측값 채우는 회수를 제한하기 (limit the amount of filling)
(4) 결측값을 변수별 평균으로 대체하기 (filling missing values with mean values per columns)
(5) 결측값을 다른 변수의 값으로 대체하기 (filling missing values with another columns' values)
등에 대해서 알아보도록 하겠습니다.
모델링에 들어가기 전에 결측값을 확인하고 결측값을 처리하는 절차가 반드시 필요하고 매우 중요한 부분입니다.
먼저 필요한 pandas, numpy 모듈을 불러오고, 결측값(missing values)을 포함하고 있는 DataFrame을 만들어보겠습니다. 결측값은 None 또는 np.nan 을 할당해주면 됩니다.
import numpy as np
import pandas as pd
## making a sample DataFrame
df = pd.DataFrame(np.random.randn(5, 3),
columns=['C1', 'C2', 'C3'])
df
# C1 C2 C3
# 0 -1.245527 -0.332396 -1.310678
# 1 -2.007546 -0.747846 -0.704082
# 2 -2.237326 -1.065539 1.086993
# 3 -1.179056 1.372350 0.388981
# 4 1.070266 -1.545383 0.996416
## replacing some values with None
df.loc[0, 'C1'] = np.nan
df.loc[1, 'C1'] = np.nan
df.loc[1, 'C3'] = np.nan
df.loc[2, 'C2'] = np.nan
df.loc[3, 'C2'] = np.nan
df.loc[4, 'C3'] = np.nan
df
# C1 C2 C3
# 0 NaN 0.893384 2.388055
# 1 NaN 0.012155 NaN
# 2 0.099263 NaN -1.330312
# 3 2.468724 NaN 1.046972
# 4 -1.157316 1.465829 NaN
(1) 결측값을 특정 값으로 채우기 (replace missing values with scalar value) : df.fillna(0)
결측값을 '0' 으로 대체해보겠습니다.
## (1) 결측값을 특정 값으로 채우기
## (1-1) 결측값을 숫자 0 으로 채우기
df_0 = df.fillna(0)
print(df_0)
# C1 C2 C3
# 0 0.000000 0.893384 2.388055
# 1 0.000000 0.012155 0.000000
# 2 0.099263 0.000000 -1.330312
# 3 2.468724 0.000000 1.046972
# 4 -1.157316 1.465829 0.000000
이번에는 결측값을 'missing' 이라는 string 값으로 채워보겠습니다.
## (1-2) 결측값을 문자열 'missing'으로 채우기
df_missing = df.fillna('missing')
print(df_missing)
# C1 C2 C3
# 0 missing 0.893384 2.38805
# 1 missing 0.0121551 missing
# 2 0.0992629 missing -1.33031
# 3 2.46872 missing 1.04697
# 4 -1.15732 1.46583 missing
(2) 결측값을 앞 방향 혹은 뒷 방향으로 채우기 (fill gaps forward or backward)
: fillna(method='ffill' or 'pad'), fillna(method='bfill' or 'backfill')
(2-) 결측값을 앞 방향으로 채워나가려면(fill gaps forward) fillna(method='ffill') 혹은 fillna(method='pad') 를 사용하면 됩니다.
## (2) 결측값을 앞 방향 혹은 뒷 방향으로 채우기
print(df)
# C1 C2 C3
# 0 NaN 0.893384 2.388055
# 1 NaN 0.012155 NaN
# 2 0.099263 NaN -1.330312
# 3 2.468724 NaN 1.046972
# 4 -1.157316 1.465829 NaN
## (2-1) 결측값을 위에서 아래 방향으로 채우기 (forward filling)
df.fillna(method='ffill')
#df.fillna(method='pad') # or equivalently
# C1 C2 C3
# 0 NaN 0.893384 2.388055
# 1 NaN 0.012155 2.388055
# 2 0.099263 0.012155 -1.330312
# 3 2.468724 0.012155 1.046972
# 4 -1.157316 1.465829 1.046972
(2-2) 결측값을 뒷 방향으로 채워나가기 위해 fillna(method='bfill') 혹은 fillna(method='backfill')을 사용
## (2-2) 결측값을 아래에서 위 방향으로 채우기 (backward filling)
print(df)
# C1 C2 C3
# 0 NaN 0.893384 2.388055
# 1 NaN 0.012155 NaN
# 2 0.099263 NaN -1.330312
# 3 2.468724 NaN 1.046972
# 4 -1.157316 1.465829 NaN
df.fillna(method='bfill')
#df.fillna(method='backfill') # or equivalently
# C1 C2 C3
# 0 0.099263 0.893384 2.388055
# 1 0.099263 0.012155 -1.330312
# 2 0.099263 1.465829 -1.330312
# 3 2.468724 1.465829 1.046972
# 4 -1.157316 1.465829 NaN
(3) 앞/뒤 방향으로 결측값 채우는 회수를 제한하기 (limit the amount of filling)
: fillna(method='ffill', limit=number), fillna(method='bfill', limit=number)
앞 방향이나 뒷 방향으로 채워나갈 때 fillna(limit=1) 를 사용해서 결측값 채우는 '개수'를 '1'개로 한정해 보겠습니다.
시계열 데이터 분석할 때 유용하게 사용하는 기능 중의 하나입니다.
## (3) 앞/뒤 방향으로 결측값 채우는 회수를 제한하기 (limit the amount of filling)
df.fillna(method='ffill', limit=1) # fill values forward with limit
# C1 C2 C3
# 0 NaN 0.893384 2.388055
# 1 NaN 0.012155 2.388055
# 2 0.099263 0.012155 -1.330312
# 3 2.468724 NaN 1.046972
# 4 -1.157316 1.465829 1.046972
df.fillna(method='bfill', limit=1) # fill values backward with limit
# C1 C2 C3
# 0 NaN 0.893384 2.388055
# 1 0.099263 0.012155 -1.330312
# 2 0.099263 NaN -1.330312
# 3 2.468724 1.465829 1.046972
# 4 -1.157316 1.465829 NaN
(4) 결측값을 변수별 평균으로 대체하기(filling missing values with mean per columns)
: df.fillna(df.mean()), df.where(pd.notnull(df), df.mean(), axis='columns')
## (4) 결측값을 변수별 평균으로 대체하기
## mean values per columns
df.mean()
# C1 0.470224
# C2 0.790456
# C3 0.701572
# dtype: float64
## filling missing values with mean per columns
df.fillna(df.mean())
#df.where(pd.notnull(df), df.mean(), axis='columns') # or equivalently
# C1 C2 C3
# 0 0.470224 0.893384 2.388055
# 1 0.470224 0.012155 0.701572
# 2 0.099263 0.790456 -1.330312
# 3 2.468724 0.790456 1.046972
# 4 -1.157316 1.465829 0.701572
위의 예시는 각 칼럼의 평균으로 -> 각 칼럼의 결측값을 대체하는 방식이었습니다.
아래 예시는 'C1' 칼럼의 평균을 가지고 'C1', 'C2', 'C3' 칼럼의 결측값을 대체하는 방법입니다.
## 'C1' 칼럼의 평균을 가지고 'C1', 'C2', 'C3' 칼럼의 결측값을 대체하는 방법
df.mean()['C1']
# 0.4702236218547502
df.fillna(df.mean()['C1'])
# C1 C2 C3
# 0 0.470224 0.893384 2.388055
# 1 0.470224 0.012155 0.470224
# 2 0.099263 0.470224 -1.330312
# 3 2.468724 0.470224 1.046972
# 4 -1.157316 1.465829 0.470224
아래의 예시는 'C1'칼럼과 'C2' 칼럼에 대해서만 각 칼럼의 평균을 가지고 -> 각 칼럼에 있는 대체값을 대체하는 경우입니다. 좀 헷갈릴 수 있는데요, 위의 2개의 예시의 혼합 형태라고 보시면 되겠습니다.
('C3'의 NaN 값은 결측값 그대로 있습니다.)
## 'C1'칼럼과 'C2' 칼럼에 대해서만 각 칼럼의 평균을 가지고 -> 각 칼럼에 있는 결측값을 대체
## mean values of 'C1', 'C2'
df.mean()['C1':'C2']
# C1 0.470224
# C2 0.790456
# dtype: float64
## filling the missing value of 'C1', 'C2' with each mean values
df.fillna(df.mean()['C1':'C2'])
# C1 C2 C3
# 0 0.470224 0.893384 2.388055
# 1 0.470224 0.012155 NaN
# 2 0.099263 0.790456 -1.330312
# 3 2.468724 0.790456 1.046972
# 4 -1.157316 1.465829 NaN
(5) 결측값을 다른 변수의 값으로 대체하기
(filling missing values with another columns' values)
두가지 방법이 있는데요, 먼저 np.where()와 pd.notnumm() 를 사용해서 np.where(pd.notnull(df['C2']) == True, df['C2'], df['C1']) 처럼 'C2' 칼럼에서 결측값이 없으면 'C2' 칼럼의 값을 그대로 사용하고, 'C2'칼럼에 결측값이 있으면 'C1' 칼럼의 값을 가져다가 결측값을 채워보겠습니다.
## (5) 결측값을 다른 변수의 값으로 대체하기
## (filling missing values with another columns' values)
df_2 = pd.DataFrame({
'C1': [1, 2, 3, 4, 5],
'C2': [6, 7, 8, 9, 10]})
## put NaNs as an example
df_2.loc[[1, 3], ['C2']] = np.nan
df_2
# C1 C2
# 0 1 6.0
# 1 2 NaN
# 2 3 8.0
# 3 4 NaN
# 4 5 10.0
## making new column by filling missing values with another column's value
# way 1 : by np.where => quick
df_2['C2_New'] = np.where(pd.notnull(df_2['C2']) == True,
df_2['C2'], df_2['C1'])
df_2
# C1 C2 C2_New
# 0 1 6.0 6.0
# 1 2 NaN 2.0
# 2 3 8.0 8.0
# 3 4 NaN 4.0
# 4 5 10.0 10.0
아래의 loop programming은 위와 동일한 결과를 반환하지만 시간은 훨~씬 오래 걸린다는 점 유의하시구요, 그냥 '아, 이렇게도 할 수 있구나...' 정도로만 참고하시기 바랍니다.
(당연히, 위의 np.where()와 pd.notnull() 을 사용하는 것이 속도도 빠르고 코드도 짧고 쉽기 때문에 추천)
## way 2 : by loop programming => takes a long time
for i in df_2.index:
if pd.notnull(df_2.loc[i, 'C2']) == True:
df_2.loc[i, 'C2_New_2'] = df_2.loc[i, 'C2']
else:
df_2.loc[i, 'C2_New_2'] = df_2.loc[i, 'C1']
df_2
# C1 C2 C2_New C2_New_2
# 0 1 6.0 6.0 6.0
# 1 2 NaN 2.0 2.0
# 2 3 8.0 8.0 8.0
# 3 4 NaN 4.0 4.0
# 4 5 10.0 10.0 10.0
결측값을 그룹별 평균으로 대체하기는 http://rfriend.tistory.com/402 를 참고하세요.
DataFrame 내 여러개의 칼럼별로 서로 다른 결측값 대체 전략을 사용하는 방법은 https://rfriend.tistory.com/542 를 참고하세요.
결측값을 선형회귀모형 추정값으로 대체하는 방법은 rfriend.tistory.com/636 를 참고하세요.
많은 도움 되었기를 바랍니다.