본문 바로가기

Data Analysis

[Pandas] 자주 사용했던 기능들 정리 - 2. 결측치 처리

누군가가 이미 가공하여 결측치를 제거한 것이 아니라면, 분석에 사용하는 대다수의 데이터셋은 결측치를 포함한다. 결측치는 분석을 할 때에 분석 모델의 성능에 영향을 미치며, 일부 모델은 결측치가 있는 경우 에러를 출력하고 작동하지 않는다. 따라서 본격적인 분석에 앞서 결측치를 데이터의 성격이나 분석 목적에 맞게 처리하는 것이 필요하다. 

 

물론 scikit-learn에서는 결측치를 평균이나 최빈값, 중간값 등으로 편리하게 대체해주는 SimpleImputer도 제공한다.

그럼에도 데이터 분석가가 별도로 결측치를 처리해야 하는 상황들이 굉장히 많다. 

(가령, 결측치를 분석에서 제외하는 것이 더 적합하다는 판단이 드는 경우, 결측치가 특정한 의미를 가지고 있다고 판단되는 경우, 혹은 도메인 지식에 기반해 결측치를 분석에 사용할 수 있는 데이터로 변환시킬 수 있다고 판단되는 경우 등등..)

 

판다스에서 활용할 수 있는 결측치 처리 방식들을 정리해보자. 

 

1) 결측치 확인하기 - isnull()/isna()  ↔ natna()/notnull()

우선 임의의 값을 넣어 데이터프레임을 만든다. 

import numpy as np
import pandas as pd

df = pd.DataFrame({'Seoul': ['Asia', 6, 'Korea', None], 
                    'Paris':['Europe', None, 'France', 104],
                    'New York':['America', 3, 'US', 94],
                    'Tokyo':[np.NaN, 8, np.NaN, 22]})
                    
df.head()

데이터프레임을 보면 None, NaN 등으로 표시되어 있는 값들이 있는데, 이 값들이 바로 결측치이다.

(pandas에서는 결측치를 None과 NaN으로 표시한다. )

 

isnull() 혹은 isna()를 데이터프레임 뒤에 입력하면, 해당 행렬의 결측치 유무를 bool 방식으로 보여준다.

가령, 해당하는 행-렬의 값이 결측치이면 'True'를 출력한다.

df.isna()
# df.isnull()

 

같은 방식을 데이터프레임 전체가 아니라 특정 행이나 열에도 적용할 수 있다. 

df['Seoul'].isnull()
>> output:
0 False
1 False
2 False
3 True
Name: Seoul, dtype: bool

마찬가지로 iloc나 loc를 활용해 특정 행이나 열을 지정해 결측치를 확인할 수도 있다.

 

이러한 방식은 지금 예시처럼 데이터가 작으면 결측치를 한 눈에 확인할 수 있지만, 데이터가 클 경우 그렇게 효과적인 방법은 아니다. 이때 데이터가 가진 결측치를 특성 별로 한 번에 확인할 수 있는 방식이 df.isnull().sum()이다. 

df.isna().sum()
# df.isnull().sum()
>> output:
Seoul           1
Paris            1
New York     0
Tokyo           2
dtype :  int64

natna()와 notnull()는 이와 정반대의 기능을 제공한다. isnull()이 결측치인 경우 True를 출력했다면, notna()는 결측치인 경우 False를 출력한다.

df.notnull()
# df.notna()

notnull()isnull()과 정확히 반대의 결과를 출력하고 있는 모습을 확인할 수 있다.

 

2) 결측치 제거하기: dropna()

결측치를 확인할 때, 결측치가 분석에 필요 없는 데이터라고 판단되면 결측치를 제거할 수 있다. 이때 사용하는 메서드는 dropna()이며, 파라미터를 조정하여 다양한 방식으로 결측치를 제거할 수 있다.

 

판다스 공식문서를 보면 dropna에는 'axis', 'how', 'thresh', 'subset', 'inplace' 등의 파라미터가 있다. 

- axis : 0과 1의 값을 받으며, 디폴트 값은 0이다.

           0으로 설정한 경우, 결측치를 포함하는 인덱스를 제거한다. 

           1로 설정한 경우, 결측치를 포함하는 컬럼을 제거한다. 

- how : any와 all로 설정할 수 있으며, 디폴트 값은 'any'이다. 

            any로 설정한 경우, 행(또는 열)에 하나의 결측치만 포함되어 있어도 그 행(또는 열)을 제거한다.

            all로 설정한 경우, 행(또는 열)의 모든 데이터가 결측치인 경우에만 그 행(또는 열)을 제거한다.

- thresh : 정수값으로 설정할 수 있으며, 선택적으로 사용할 수 있는 optional 파라미터이다. 

            axis를 기준으로 행(또는 열)에 결측치가 지정한 숫자 이상 존재한다면, 해당 행(또는 열)을 제거하지 않는다.

           가령, 'axis=1', 'thresh = 3'로 설정했다면 위의 예시에서 'Tokyo' 열은 삭제되지만, 다른 열은 삭제되지 않는다.

- subset : column 값을 가진 리스트로 설정할 수 있으며, 선택적으로 사용할 수 있는 optional 파라미터이다.

            subset을 지정하게 되면, 결측치 제거가 데이터프레임 전체에서 수행되는 것이 아닌 지정한 열에서만

            수행된다. 

- inplace : bool(True/False)로 설정할 수 있으며, 디폴트 값은 'False'이다.

             True로 설정한 경우, 어떤 값도 리턴하지 않고 변경된 값을 원래 데이터프레임에 반영한다. 

             가령, df1=df로 데이터프레임을 복사한 후, df1에 대해 dropna(inplace=True)로 결측치를 제거하면

             df1에서는 아무 값도 출력되지 않고, 변경된 결과가 df에 반영된다. 

 

보통 subset을 많이 사용했던 것 같기에, subset을 활용해서 'Tokyo' 열에서 결측치가 포함된 행을 제거해보자.

df = df.dropna(axis=0, subset=['Tokyo'])
df.head()

 

3) 결측치 채우기 : fillna(), replace()

결측치를 단순히 제거하는 것이 아니라, 적절한 값을 채워서 분석에 활용하는 방법도 있다. 

 

fillna()

fillna()는 괄호 안에 지정한 값으로 결측치를 변환한다. 괄호 안에는 특정 정수나 평균, 최빈값 등을 입력할 수도 있고, 'method' 인수에서 제공하는 다음과 같은 옵션으로 결측값을 대체할 수도 있다. 

- backfill : 결측값이 바로 아래 있는 값으로 대체.

- bfill : 결측값이 바로 아래 있는 값으로 대체.

- pad : 결측값이 바로 위에 있는 값으로 대체 

- ffill : 결측값이 바로 위에 있는 값으로 대체

 

먼저, 'Tokyo' 칼럼에 있는 결측값을 0으로 fillna(0)을 사용해 변경해보자.

df['Tokyo'] = df['Tokyo'].fillna(0)
df.head()

'Tokyo'의 결측값이 모두 0으로 바뀐 것을 확인할 수 있다.

 

다음으로는 method 인수를 사용해보자. 먼저 'bfill'('backfill')을 사용해 그 아래 있는 값으로 결측치를 변경해 보자.

df['Paris'] = df['Paris'].fillna(method='bfill')
# df['Paris'].fillna(method='backfill')과 동일
df.head()

'Paris' 열에 있던 결측값이 아래 있는 'France'로 대체된 것을 확인할 수 있다.

 

다음은 bfill 대신 'pad'나 'ffill'로 설정한 경우이다. 

df['Paris'] = df['Paris'].fillna(method='pad')
# df['Paris'].fillna(method='ffill')과 동일
df.head()

'bfill'과는 반대로, 'Paris'의 결측값이 위에 있는 'Europe'으로 변경되었다.

 

replace()

replace()는 지정한 값을 다른 값으로 변경해주는 메서드로, 결측값을 지정한 후 변경할 값을 입력하면 결측치를 변경하는 데에 사용할 수 있다. 

df['Tokyo'] = df['Tokyo'].replace(np.NaN, 1)
df.head()

'Tokyo' 열의 결측치(np.NaN)를 1로 변경한 결과가 나타난다.