관리 메뉴

TEAM EDA

결측치 처리 (Missing Value) 본문

TEAM EDA /EDA 1기 ( 2018.03.01 ~ 2018.09.16 )

결측치 처리 (Missing Value)

김현우 2018. 11. 12. 15:38

NOTE: 대부분의 내용은  https://blog.naver.com/tjdudwo93/220976082118을 기반으로 Titanic 데이터에 실습을 적용하는 것으로 진행됩니다.

군밤고굼님의 설명에 따르면 결측치를 살펴보는 과정은 아래와 같은 과정으로 진행됩니다. 

 

1. 결측 데이터의 종류 

|

2. 결측값 유형 탐색하기 (표 만들기, 결측치간 상관관계)

|

3. 결측 데이터의 원인 및 각각의 원인에 따른 처리 방법론

|

4. 결측치 처리 방법 선택 ( 1. 합리적 접근법 )

|

5. 결측치 처리 방법 선택 ( 2. 완전제거법 )

|

6. 결측치 처리 방법 선택 ( 3. 다중대체 )

 

1. 결측 데이터의 종류 

데이터가 누란 된 이유를 이해하는 것은 나머지 데이터를 올바르게 처리하는 데 중요합니다. 결측 데이터의 종류를 살펴보는 이유는 결측치의 종류에 따라 다른 접근방법이 필요하고 데이터를 이해하는데 도움이 되기 때문입니다. 결측치의 종류라는 말이 생소하신 분이 많을텐데 결측치는 아래의 3가지로 분류됩니다. 
 

- 완전 무작위 결측(MCAR : Missing completely at random)

변수 상에서 발생한 결측치가 다른 변수들과 아무런 상관이 없는 경우 우리는 완전 무작위 결측(MCAR)이라고 부릅니다. 대부분의 결측치 처리 패키지가 MCAR을 가정으로 하고 있고 보통 우리가 생각하는 결측치라고 생각하시면 됩니다. 예를 들어, 데이터를 입력하는 사람이 깜빡하고 입력을 안했다든지 전산오류로 누락된 경우 등 입니다. 이러한 결측치는 보통 제거하거나 대규모 데이터 셋에서 단순 무작위 표본추출을 통해서 완벽한 데이터셋으로 만들 수 있습니다.

 

- 무작위 결측(MAR : Missing at random)

누락된 자료가 특정 변수와 관련되어 일어나지만, 그 변수의 결과는 관계가 없는 경우를 의미합니다. 그리고 누락이 전체 정보가 있는 변수로 설명이 될 수 있음을 의미합니다.(누락이 완전히 설명 될 수 있는 경우 발생) 예를 들어, 남성은 우울증 설문 조사에 기입 할 확률이 적지만 우울함의 정도와는 상관이 없는 경우입니다.  

 

- 비 무작위 결측(MNAR : Missing at not random)

위의 두가지 유형이 아닌 경우를 MNAR이라고 합니다. MNAR은 누락된 값(변수의 결과)이 다른 변수와 연관 있는 경우를 의미합니다. 위의 예시를 확장해서, 만약 남성이 우울증 설문 조사에 기입하는게 우울증의 정도와 관련이 있다면 이것은 MNAR입니다. 

 

MAR과 MNAR의 차이에 대한 다른 예시로 아래와 같은 예시가 있습니다. 

 

성별 (X)의 함수로 체중 (Y)을 모델링한다고 가정 해보십시오. 일부 응답자는 체중을 공개하지 않으므로 Y 값이 누락되었습니다. 결측치 종류에는 세 가지 메커니즘이 있습니다.
 
  • 일부 응답자가 귀하에게 체중을 말했고 다른 응답자는 체중을 말하지 않은 이유가 없습니다. 즉, Y가 누락 될 확률은 X 또는 Y와 관련이 없습니다. 이 경우 데이터가 무작위로 완전히 누락됩니다(MCAR)
  • 여성은 체중을 공개 할 가능성이 적습니다. 즉, Y가 누락 될 확률은 X의 값에만 의존합니다. 이러한 데이터는 무작위 결측이라고 합니다(MAR)
  • 무거운 (또는 가벼운) 사람들은 체중을 공개 할 가능성이 적습니다. 즉, Y가 누락 될 확률은 Y 자체의 관찰되지 않는 값에 달려 있습니다. 이러한 데이터는 비 무작위 결측이라고 합니다(MNAR)

 

출처: Ziad Taib(http://www.math.chalmers.se/Stat/Grundutb/GU/MSA650/S09/Lecture5.pdf)

 

더 자세한 내용은 위키피디아(https://en.wikipedia.org/wiki/Missing_data)를 참고하시기 바랍니다. 

 

 

2. 결측값 유형 탐색하기 

 

위에서 말했듯이 결측치의 종류를 확인하는것은 중요합니다. 그러한 과정을 탐색하기 위해 먼저 어떤 변수에 결측치가 있는지 부터 확인하도록 하겠습니다. 실습은 전에 했던 자료와 동일하게 Titanic으로 진행하겠습니다.

 

# 파이썬에서는 .isnull()을 통해서 결측치를 확인할 수 있습니다.

total = df_train.isnull().sum().sort_values(ascending=False)
percent = (df_train.isnull().sum()/df_train.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
percent_data = percent.head(20)
percent_data.plot(kind="bar", figsize = (8,6), fontsize = 10)
plt.xlabel("", fontsize = 20)
plt.ylabel("", fontsize = 20)
plt.title("Total Missing Value (%)", fontsize = 20)

 

[그림1] 각 변수의 결측치 비율

위의 [그림1]은 각 변수의 결측치 비율을 확인한 자료 입니다. 결측치는 총 3개의 변수 Cabin, Age, Embarked에서 발생하고 눈으로 보기에는 결측치끼리는 아무런 관련이 없어 보입니다. 이를 확인하기 위해 결측치 끼리의 상관관계를 뽑아보면 아래 [그림2]와 같습니다.

 

import missingno as msno
missingdata_df = df_train.columns[df_train.isnull().any()].tolist()
msno.heatmap(df_train[missingdata_df], figsize=(8,6))
plt.title("Correlation with Missing Values", fontsize = 20)
[그림2] 결측치끼리의 상관관계
 
[그림2]에서 확인할 수 있듯이 결측치끼리의 상관성은 없는것을 확인할 수 있습니다. Cabin의 경우는 결측치가 80%이상인데 Cabin의 의미를 생각하면 사물함의 번호를 의미합니다. 그래서 사물함이 없어서 NaN인 경우와 단순 누락으로 인한 NaN으로 판단하는게 옮다고 생각합니다. 

 

df_train.corr()

 

[그림3] 결측치 존재여부에 따른 상관관계 표

위의 hasCabin변수는 결측치는 0, 그렇지 않으면 1로 만든 변수입니다. 이를 다른변수와의 상관관계를 그려서 확인하면 Pclass와 Fare과 높은 관계를 가지고 있는것을 확인할 수 있습니다. 하지만 hasAge의 경우는 다른 변수와의 상관성이 낮습니다. 

 

 

data = pd.concat([df_train['Fare'], df_train['Embarked']], axis=1)
f, ax = plt.subplots(figsize=(8, 6))
fig = sns.boxplot(x='Embarked', y="Fare", data=data)

[그림4] 사물함의 존재 여부에 따른 Fare 값
[그림5] 사물함의 존재 여부에 따른 Pclass

위의 [그림4]와 [그림5]를 보면 사물함이 결측치가 아닐수록 Pcalss가 1에 가깝습니다. Pclass가 1인 사람은 거의 Cabin이 결측치가 아니고, Pclass가 3인 사람은 거의 Cabin의 결측치 입니다. 추가적으로 Fare과도 비교해보면 Cabin이 있는 사람의 Fare이 대체로 높은것을 확인할 수 있습니다. 이렇게 다른 변수와 연관있어서 결측치인 경우 우리는 MAR 유형이라고 합니다. 

 

Embarked의 경우 결측치가 단 두개밖에 없어서 굳이 유형을 분리할 이유가 없고 Age의 경우는 다른변수와는 관계가 없고, Age에 따라서 결측치가 생겼다고 판단하기 어렵기 때문에 둘다 MCAR으로 가정하고 결측치를 채워보도록 하겠습니다. 

 

 

3. 결측 데이터의 원인 및 각각의 원인에 따른 처리 방법론 

 

[그림1]에서 확인했듯이 결측치는 3개의 변수 Cabin, Age, Embarked에서 있었고 각각 결측치의 비율은 77%, 19.8%, 0.002% 입니다. 위에서 말했듯 Cabin의 경우 결측치가 생긴 이유는 사물함이 존재하지 않아서 일 가능성이 높고, Age와 Embarked는 MCAR 가정에 따라서 임의로 누락 되었을 가능성이 높습니다. Cabin의 경우 존재하지 않아서 결측치이니깐 위에서 hasCabin처럼 새로 변수를 만들어주는것이 좋고, Age와 Embarked의 경우는 다른 변수와의 관계를 통해서 결측치를 처리해주는것이 좋습니다. 

 

보통 결측치를 처리할 때는 hair et al.(2006)에 따르면 아래의 표와 같이 처리해준다고 합니다.

 

결측치 비율 

 처리 방법

 10% 미만

 제거 or 어떠한 방법이든지 상관없이 Imputation

 10% 이상 20% 미만

 hot deck , regression , model based method

 20% 이상

 model based method , regression

[표1] 결측치의 비율에 따른 처리 방법

 

위의 방법론에 따라 저희는 Age는 model based method를 이용하여 채워 넣고(다중대체 사용) Embarked는 다른 변수와의 관계을 이용하여 합리적 대체를 하겠습니다. 위의 실습자료에서는 제거의 경우는 적합하지 않아서 방법론만 설명하겠습니다.

 

 

4. 결측치 처리 방법 선택 (1. 합리적 접근법)

합리적 접근법은 변수들간의 관계를 이용해서 결측치를 채워넣는 방법을 의미합니다. Embarked의 결측치를 한번 살펴보도록 하겠습니다. 
 
df_train[df_train['Embarked'].isnull()]
 
[그림6] Embarked가 결측치인 관측치

 

Embarked가 결측치인 두 관측치는 Pclass, Fare이 동일한 것을 볼 수 있습니다. 이를 토대로 Pclass와 Fare Embarked간의 boxplot을 그려보도록 하겠습니다. 
 
[그림7] Pclass를 그룹으로 하는 Embarked와 Fare의 Boxplot
 
위의 [그림6]과 [그림7]을 같이 보면 Pclass와 Embarked 별로 Fare이 다른것을 한눈에 볼 수 있습니다. [그림6]에서 결측치 2개는 Pclass가 1에 Fare이 80이므로 Embarked C의 중앙값하고 동일함을 볼 수 있습니다. ( 그래프로 보면 80인지 헷갈리지만 실제 값으로 보면 거의 80 이었습니다. ) 
 
# 특정 행의 열을 채우는 방법 
df_train.loc[61, 'Embarked'] = 'S'
df_train.loc[829, 'Embarked'] = 'S'

# 특정 열의 결측치를 채우는 방법 
df_train['Embarked'] = df_train['Embarked'].fillna('S')
 
이렇게 다른 변수와의 관계를 이용해서 결측치를 채우는 방식을 합리적 접근법이라고 합니다.
 
5. 결측치 처리 방법 선택 (2. 완전 제거법)
다음은 완전 제거법에 대해서 알아보도록 하겠습니다. 완전 제거법은 결측치가 있는 행 자체를 지워버리는 방법입니다. 위의 Cabin처럼 결측치가 80%에 가까운 경우 그 변수 자체를 제거하는 방식으로 사용하거나 하나의 row에 결측치가 여러개일 경우에 사용합니다. 이런 방식은 편리하지만 결측치를 지우면서 데이터 자체의 편향(bias)이 생길 수 있어서 조심히 접근해야 하는 방법입니다. 
 
# 완전 제거법
# 결측치가 들어가 있는 행 제거 
a = df_train.dropna(axis = 0)

# 결측치가 들어가 있는 열 제거 
b = df_train.dropna(axis = 1)

# 특정 열을 대상으로 제거 
c = df_train[df_train['Cabin'].notnull()]

# print(a.shape, b.shape, c.shape)
# (183, 14) (891, 11) (204, 14)

 

6. 결측치 처리 방법 선택 (3. 다중 대체법)

가장 어려운 대체방법으로 하나의 관측치에 2개 이상의 결측치가 존재할 경우에 잘 들어맞는 결측치 처리 방법입니다. 다중 대체법의 종류는 여러가지 있지만 대표적으로 Mice, Amelia , MissForest, Hmisc, Mi를 이용합니다. 아래의 링크에 R을 이용한 방법론이 잘 설명되었으니 참고하시기 바랍니다. (https://www.analyticsvidhya.com/blog/2016/03/tutorial-powerful-packages-imputing-missing-values/)

 

이번에는 가장 대표적인 예 중 하나인 Mice를 이용해서 예측해보도록 하겠습니다. Mice를 사용하는 이유는 MCAR 결측치를 가정하였을 때 적합하고 Numeric변수와 Categorical변수들이 섞여있을 때에도 잘 작동하기 때문입니다. 근데 결측치 패키지는 python보다는 R에서 잘 작동하기 때문에 이 부분은 R로 작업하였습니다. 

 

[그림8] Mice를 이용하여 Age 결측치 채운 결과

 

 

다중 대체법은 위의 [그림8]처럼 Original Data와 대체법 이후의 Data가 동일한 분포를 가지도록 결측치를 채웁니다. 
[그림9] Mice의 Imputation steps

Mice의 경우 [그림9]처럼 진행 되고 이를 더 풀어서 설명하면 아래와 같습니다.

  1. 누락된값은 df에 있는 다른 모든변수를 사용하여 값을 예측하여 채워 넣는다.
  2. 누락된 자료가 채워진 완성된 데이터 세트를 여러개 만든다. (default = 5)
  3. 각각의 완성된 데이터 세트에 대해 with()를 사용하여 통계 모형을 순서대로 적용한다.
  4. pool() 함수를 사용하여 이들 각각의 분석결과를 하나로 통합한다
이러한 과정을 통해서 진행되는데 자세한 내용은 논문을 https://www.jstatsoft.org/article/view/v045i03/v45i03.pdf 통해서 확인하시기 바랍니다. 위의 과정을 통해 순차적으로 결측치를 처리한 내용은 [Kaggle] House Price: Advanced Regression Techniques (2)를 보시기 바랍니다. 

 

코드 

https://www.kaggle.com/chocozzz/titanic-tutorial-with-python?scriptVersionId=7296387

 

참고자료 

https://blog.naver.com/tjdudwo93/220976082118

https://www.jstatsoft.org/article/view/v045i03/v45i03.pdf 

http://www.math.chalmers.se/Stat/Grundutb/GU/MSA650/S09/Lecture5.pdf