본문 바로가기
목차
파이썬

[pandas]결측값 채우기, 결측값 대체하기, 결측값 처리 (filling missing value, imputation of missing values) : df.fillna()

by 지각생 2022. 8. 20.
728x90

출처: https://rfriend.tistory.com/262 [R, Python 분석과 프로그래밍의 친구 (by R Friend):티스토리]

 

지난번 포스팅에서는 결측값 여부 확인, 결측값 개수 세기 등을 해보았습니다.

 

이번 포스팅에서는 결측값을 채우고 대체하는 다양한 방법들로서,

 

 - 결측값을 특정 값으로 채우기

   (replace missing valeus with scalar value)

 

 - 결측값을 앞 방향 혹은 뒷 방향으로 채우기

   (fill gaps forward or backward)

 

 - 결측값 채우는 회수를 제한하기

   (limit the amount of filling)

 

 - 결측값을 변수별 평균으로 대체하기

   (filling missing values with mean value per columns)

 

 - 결측값을 다른 변수의 값으로 대체하기

   (filling missing values with another columns' values)

 

등에 대해서 알아보도록 하겠습니다.  모델링에 들어가기 전에 결측값을 확인하고 결측값을 처리하는 절차가 반드시 필요하고 매우 중요한 부분입니다. 

 

 

 

 

 

 

데이터 작성

먼저 필요한 pandas, numpy 모듈을 불러오고, 결측값(missing values)을 포함하고 있는 DataFrame을 만들어보겠습니다.  결측값은 df.ix[x, y] 에 None 또는 np.nan 을 할당해주면 됩니다.

 



#%% missing value imputation, filling missing value
## importing modules


In [1]: import pandas as pd


In [2]: import numpy as np


## making DataFrame
In [3]: df = pd.DataFrame(np.random.randn(5, 3),
   ...: columns=['C1', 'C2', 'C3'])
   ...:


In [4]: df
Out[4]:
         C1        C2        C3
0 -0.905421 -0.228791 -0.850988
1  0.558819  0.564767  0.232641
2 -0.834515 -0.204626 -0.566917
3  0.242694 -0.317098 -0.673298
4 -0.497041 -0.301435 -1.265128





In [5]: df.ix[0, 0] = None


In [6]: df.ix[1, ['C1', 'C3']] = np.nan


In [7]: df.ix[2, 'C2'] = np.nan


In [8]: df.ix[3, 'C2'] = np.nan


In [9]: df.ix[4, 'C3'] = np.nan


In [10]: df
Out[10]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767       NaN
2 -0.834515       NaN -0.566917
3  0.242694       NaN -0.673298
4 -0.497041 -0.301435       NaN




 

 

  (1) 결측값을 특정 값으로 채우기 (replace missing values with scalar value)

: df.fillna(0)

 

결측값을 '0' 으로 대체해보겠습니다.

 



In [11]: df_0 = df.fillna(0)


In [12]: df_0
Out[12]:
         C1        C2        C3
0.000000 -0.228791 -0.850988
0.000000  0.564767  0.000000
2 -0.834515  0.000000 -0.566917
3  0.242694  0.000000 -0.673298
4 -0.497041 -0.301435  0.000000




 

 

 

이번에는 결측값을 'missing' 이라는 string 값으로 채워보겠습니다.

 



In [13]: df_missing = df.fillna('missing')


In [14]: df_missing
Out[14]:
C1 C2 C3
         C1        C2        C3
0   missing -0.228791 -0.850988
1   missing  0.564767   missing
2 -0.834515   missing -0.566917
3  0.242694   missing -0.673298
4 -0.497041 -0.301435   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') 를 사용하면 됩니다.

 



In [10]: df
Out[10]:
          C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767       NaN
2 -0.834515       NaN -0.566917
3  0.242694       NaN -0.673298
4 -0.497041 -0.301435       NaN

 
In [15]: df.fillna(method='ffill') # Fill values forward
Out[15]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767 -0.850988
2 -0.834515  0.564767 -0.566917
3  0.242694  0.564767 -0.673298
4 -0.497041 -0.301435 -0.673298





In [16]: df.fillna(method='pad') # Fill values forward
Out[16]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767 -0.850988
2 -0.834515  0.564767 -0.566917
3  0.242694  0.564767 -0.673298
4 -0.497041 -0.301435 -0.673298




 

 

 

다음은 (2-2) 결측값을 뒷 방향으로 채워나가기 위해 fillna(method='bfill') 혹은 fillna(method='backfill')을 사용한 예시의 결과입니다.

 



In [10]: df
Out[10]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767       NaN
2 -0.834515       NaN -0.566917
3  0.242694       NaN -0.673298
4 -0.497041 -0.301435       NaN

 
In [17]: df.fillna(method='bfill') # Fill values backward
Out[17]:
         C1        C2        C3
0 -0.834515 -0.228791 -0.850988
1 -0.834515  0.564767 -0.566917
2 -0.834515 -0.301435 -0.566917
3  0.242694 -0.301435 -0.673298
4 -0.497041 -0.301435       NaN





In [18]: df.fillna(method='backfill') # Fill values backward
Out[18]:
         C1        C2        C3
0 -0.834515 -0.228791 -0.850988
1 -0.834515  0.564767 -0.566917
2 -0.834515 -0.301435 -0.566917
3  0.242694 -0.301435 -0.673298
4 -0.497041 -0.301435       NaN




 

 

(3) 앞/뒤 방향으로 결측값 채우는 회수를 제한하기 (limit the amount of filling)


     : fillna(method='ffill', limit=number), fillna(method='bfill', limit=number)

 

앞 방향이나 뒷 방향으로 채워나갈 때 fillna(limit=1) 를 사용해서 결측값 채우는 '개수'를 '1'개로 한정해 보겠습니다.  시계열 데이터 분석할 때 유용하게 사용하는 기능 중의 하나입니다.  

 

아래 예에서 빨간색으로 밑줄 친 부분을 유심히 살펴보시면 이해하기 편하실 거예요.

 



In [10]: df
Out[10]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767       NaN
2 -0.834515       NaN -0.566917
3  0.242694       NaN -0.673298
4 -0.497041 -0.301435       NaN



In [19]: df.fillna(method='ffill', limit=1) # fill values forward with limit
Out[19]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767 -0.850988
2 -0.834515  0.564767 -0.566917
3  0.242694       NaN -0.673298
4 -0.497041 -0.301435 -0.673298





In [20]: df.fillna(method='bfill', limit=1) # fill values backward with limit
Out[20]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1 -0.834515  0.564767 -0.566917
2 -0.834515       NaN -0.566917
3  0.242694 -0.301435 -0.673298
4 -0.497041 -0.301435       NaN



 

 

 

  (4) 결측값을 변수별 평균으로 대체하기(filling missing values with mean per columns)

      : df.fillna(df.mean()), df.where(pd.notnull(df), df.mean(), axis='columns')

 



In [10]: df
Out[10]:
         C1        C2        C3
0       NaN -0.228791 -0.850988
1       NaN  0.564767       NaN
2 -0.834515       NaN -0.566917
3  0.242694       NaN -0.673298
4 -0.497041 -0.301435       NaN



# mean per columns
In [21]: df.mean()
Out[21]:
C1 -0.362954
C2 0.011514
C3 -0.697068
dtype: float64


# filling missing values with mean per columns
# way 1
In [22]: df.fillna(df.mean())
Out[22]:
         C1        C2        C3
0 -0.362954 -0.228791 -0.850988
1 -0.362954  0.564767 -0.697068
2 -0.834515  0.011514 -0.566917
3  0.242694  0.011514 -0.673298
4 -0.497041 -0.301435 -0.697068





# way 2
In [23]: df.where(pd.notnull(df), df.mean(), axis='columns')
Out[23]:
         C1        C2        C3
0 -0.362954 -0.228791 -0.850988
1 -0.362954  0.564767 -0.697068
2 -0.834515  0.011514 -0.566917
3  0.242694  0.011514 -0.673298
4 -0.497041 -0.301435 -0.697068



 

 

 

위의 예시는 각 칼럼의 평균으로 -> 각 칼럼의 결측값을 대체하는 방식이었습니다.

아래 예시는 'C1' 칼럼의 평균을 가지고 'C1', 'C2', 'C3' 칼럼의 결측값을 대체하는 방법입니다.

 



In [24]: df.mean()['C1']
Out[24]: -0.36295411360962238


In [25]: df.fillna(df.mean()['C1'])
Out[25]:
         C1        C2        C3
0 -0.362954 -0.228791 -0.850988
1 -0.362954  0.564767 -0.362954
2 -0.834515 -0.362954 -0.566917
3  0.242694 -0.362954 -0.673298
4 -0.497041 -0.301435 -0.362954



 

 

 

 

아래의 예시는 'C1'칼럼과 'C2' 칼럼에 대해서만 각 칼럼의 평균을 가지고 -> 각 칼럼에 있는 대체값을 대체하는 경우입니다.  좀 헷갈릴 수 있는데요, 위의 2개의 예시의 혼합 형태라고 보시면 되겠습니다.

('C3'의 NaN 값은 결측값 그대로 있습니다.)

 



In [26]: df.mean()['C1':'C2']
Out[26]:
C1   -0.362954
C2    0.011514
dtype: float64





In [27]: df.fillna(df.mean()['C1':'C2'])
Out[27]:
         C1        C2        C3
0 -0.362954 -0.228791 -0.850988
1 -0.362954  0.564767       NaN
2 -0.834515  0.011514 -0.566917
3  0.242694  0.011514 -0.673298
4 -0.497041 -0.301435       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' 칼럼의 값을 가져다가 결측값을 채워보겠습니다.

 



In [28]: df_2 = pd.DataFrame({'C1': [1, 2, 3, 4, 5],
    ...: 'C2': [6, 7, 8, 9, 10]})
    ...:


In [29]: df_2.ix[[1, 3], ['C2']] = np.nan


In [30]: df_2
Out[30]:
   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
In [31]: df_2['C2_New'] = np.where(pd.notnull(df_2['C2']) == True, df_2['C2'], df_2['C1'])


In [32]: df_2
Out[32]:
   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 long time
In [33]: for i in df_2.index:    ...:     if pd.notnull(df_2.ix[i, 'C2']) == True:
    ...:         df_2.ix[i, 'C2_New_2'] = df_2.ix[i, 'C2']
    ...:     else:
    ...:         df_2.ix[i, 'C2_New_2'] = df_2.ix[i, 'C1']
    ...:
    ...:


In [34]: df_2
Out[34]:
   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 를 참고하세요. 

출처: https://rfriend.tistory.com/262 [R, Python 분석과 프로그래밍의 친구 (by R Friend):티스토리]

728x90

댓글