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

 

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

 

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

 

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

 

 

728x90
반응형
Posted by Rfriend
,