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

 

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

 

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

   (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 를 참고하세요. 


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

 

 

Posted by R Friend R_Friend

댓글을 달아 주세요