[데이터 사전 처리/누락 데이터 처리]

2022. 11. 28. 14:14

머신러닝 등 데이터 분석의 정확도는 분석 데이터의 품질에 의해 좌우된다.

데이터 품질을 높이기 위해서누락 데이터, 중복 데이터 등 오류를 수정하고 분석 목적에 맞게 변형하는 과정이 필요하다.

수집한 데이터를 분석에 적합하도록 사전 처리(Preprocessing, 전처리)하는 방법을 알아본다.

 

데이터프레임에는 원소 데이터 값이 종종 누락되는 경우가 있다.

데이터를 파일로 입력할 때 빠뜨리거나 파일 형식을 변환하는 과정에서 데이터가 소실되는 것이 주요 원인이다.

일반적으로 유효한 데이터 값이 존재하지 않는 누락 데이터를 NaN(Not a Number)으로 표시한다.

 

머신러닝 분석 모형에 데이터를 입력하기 전에 반드시 누락 데이터를 제거하거나 다른 적절한 값으로 대체하는 과정이 필요하다.

누락 데이터가 많아지면 데이터의 품질이 떨어지고, 머신러닝 분석 알고리즘을 왜곡하는 현상이 발생하기 때문이다.

 

 

목차

 

1. 누락 데이터 확인

2. 누락 데이터 제거

3. 누락 데이터 치환


누락 데이터 확인

 

Seaborn 라이브러리의 'titanic' 데이터셋을 사용한다.

# 예제 5-1(1)

import seaborn as sns

df = sns.load_dataset('titanic')
df.head()

 

예제 5-1(1) 출력 결과

head() 메소드로 첫 5행을 출력하면 'deck' 열에 NaN 값이 있다. 이 승객의 경우 몇 번 데크에 승객했는지 데이터가 없다는 뜻이다.

 

예제 5-1(1) info() 메소드 출력 결과

info() 메소드로 데이터프레임의 요약 정보를 출력하면 각 열에 속하는 데이터 중에서 유효한 값(NaN 값이 아닌)의 개수를 보여준다.

RangeIndex를 보면 각 열에 891개의 데이터가 있다.

그리고 'deck'열에는 203개의 유효한 범주형(category) 데이터가 있다.

따라서 'deck'열에 있는 누락 데이터가 688개라는 사실을 알 수 있다.

 

value_counts() 메소드를 이용하여 'deck' 열에 688개의 누락 데이터가 있는 것을 파악할 수도 있다.

# 예제 5-1(2)

import seaborn as sns

df = sns.load_dataset('titanic')

nan_deck = df['deck'].value_counts(dropna=False)
print(nan_deck)

단, 이 때 누락 데이터의 개수를 확인하려면 반드시 dropna=False 옵션을 사용한다.

그렇지 않으면 NaN 값을 제외하고 유효한 데이터의 개수만 구하기 때문이다.

 

예제 5-1(2) 출력 결과

 

누락 데이터를 찾는 직접적인 방법으로 isnull() 메소드notnull() 메소드가 있다.

isnull() 메소드누락 데이터이면 True를 반환하고, 유효한 데이터가 존재하면 False를 반환한다.

notnull() 메소드유효한 데이터가 존재하면 True를 반환하고, 누락 데이터면 False를 반환한다.

 

# 예제 5-1(3)

import seaborn as sns

df = sns.load_dataset('titanic')
print(df.head().isnull())

첫 5행의 원소들이 누락 데이터인지 여부를 isnull() 메소드를 적용하여 판별한다.

 

예제 5-1(3) 출력 결과

 

이번에는 notnull() 메소드를 사용해본다.

# 예제 5-1(4)

import seaborn as sns

df = (sns.load_dataset('titanic'))
print(df.head().notnull())

 

예제 5-1(4) 출력 결과

 

누락 데이터의 개수를 구할 때 isnull() 메소드 notnull() 메소드를 활용할 수 있다.

isnull() 메소드의 경우 반환되는 값이 참이면 1이고 거짓이면 0으로 판별한다.

따라서 isnull() 메소드를 실행하고 sum(axis=0) 메소드를 적용하면 참(1)의 합을 구한다.

이처럼 각 열의 누락 데이터 개수를 구할 수 있다.

 

# 예제 5-1(5)

import seaborn as sns

df = sns.load_dataset('titanic')
print(df.head().isnull().sum(axis=0))

 

예제 5-1(5) 출력 결과

 

 

누락 데이터 제거

 

누락 데이터가 들어 있는 열 또는 행을 삭제하는 방법을 알아본다.

열을 삭제하면 분석 대상이 갖는 특성(변수)를 제거하고,

행을 삭제하면 분석 대상의 관측값(레코드)를 제거하게 된다.

 

먼저 'titanic' 데이터셋의 각 열(변수)에 누락 데이터가 몇 개씩 포함되어 있는지 체크한다.

# 예제 5-2(1)

import seaborn as sns

df = sns.load_dataset('titanic')

missing_df = df.isnull()
for col in missing_df.columns:
    missing_count = missing_df[col].value_counts()
    
    try:
        print(col, ': ', missing_count[True])
    except:
        print(col, ': ', 0)

 

예제 5-2(1) 출력 결과

'age' 열에 177개, 'embarked' 열에 2개, 'deck' 열에 688개, 'embark_town' 열에 2개의 누락 데이터가 있다.

 

전체 891명의 승객 중에서 688명의 데크 데이터가 누락되어 있다.

누락 데이터가 차지하는 비율이 매우 높기 때문에 'deck' 열의 누락 데이터를 삭제하여 분석에서 제외하는 것이 의미가 있다.

 

dropna() 메소드thresh=500 옵션을 적용하여, NaN 값을 500개 이상 갖는 모든 열을 삭제한다.

# 예제 5-2(2)

import seaborn as sns

df = sns.load_dataset('titanic')

df_thresh = df.dropna(axis=1, thresh=500)
print(df_thresh.columns)

 

예제 5-2(2) 출력 결과

 

891명의 승객 중에서 177명은 나이에 대한 데이터가 없다.

승객의 나이가 데이터 분석의 중요한 변수라면, 나이 데이터가 없는 승객의 레코드(행)을 제거하는 것이 좋다.

dropna() 메소드subset'age' 열로 한정하면, 'age' 열의 행 중에서 NaN 값이 있는 모든 행(axis=0)을 삭제한다.

기본값으로 how='any' 옵션이 적용되는데, NaN 값이 하나라도 존재하면 삭제한다는 뜻이다.

how='all' 옵션으로 입력하면 모든 데이터가 NaN 값일 경우에만 삭제된다.

# 예제 5-2(3)

import seaborn as sns

df = sns.load_dataset('titanic')

df_age = df.dropna(subset=['age'], how='any', axis=0)
print(len(df_age))

 

예제 5-2(3) 출력 결과

 

 

누락 데이터 치환

 

데이터셋의 품질을 높일 목적으로 누락 데이터를 무작정 삭제해 버린다면 어렵게 수집한 데이터를 활용하지 못하게 된다.

데이터 분석의 정확도는 데이터의 품질 외에도 제공되는 데이터의 양에 의해 상당한 영향을 받는다.

따라서 데이터 중에서 일부가 누락되어 있더라도 나머지 데이터를 최대한 살려서 데이터 분석에 활용하는 것이 좋은 결과를 얻는 경우가 많다.

 

누락 데이터를 대체할 값으로는 데이터의 분포와 특성을 잘 나타낼 수 있는 평균값, 최빈값 등을 활용한다.

판다스 fillna() 메소드로 편리하게 처리할 수 있다.

fillna() 메소드는 새로운 객체를 반환하기 때문에 원본 객체를 변경하려면 inplace=True 옵션을 추가해야 한다.

 

평균(mean)으로 누락 데이터를 처리하는 방법을 우선 알아본다.

앞의 예제처럼 승객의 나이 데이터가 누락된 행을 제거하지 않고, 대신 'age' 열의 나머지 승객의 평균나이로 치환한다.

# 예제 5-3

import seaborn as sns

df = sns.load_dataset('titanic')

print(df['age'].head(10))
print('\n')

mean_age = df['age'].mean(axis=0)
df['age'].fillna(mean_age, inplace=True)

print(df['age'].head(10))

먼저 'age' 열에 들어 있는 값들의 평균을 계산하여 mean_age에 저장한다.

mean() 메소드를 적용하면 NaN을 제외하고 평균을 계산한다.

fillna() 메소드mean_age를 인자로 전달하면 NaN을 찾아서 mean_age값으로 치환한다.

 

예제 5-3 출력 결과

평균 대신 중간값을 사용하려면 위 코드에서 mean()median()으로 바꿔주기만 하면 된다.

 

승선도시를 나타내는 'embark_town' 열에 있는 NaN을 다른 값으로 바꿔본다.

승객들이 가장 많이 승선한 도시의 이름을 찾아서 NaN을 치환한다.

# 예제 5-4

import seaborn as sns

df = sns.load_dataset('titanic')

print(df['embark_town'][825:830])
print('\n')

most_freq = df['embark_town'].value_counts(dropna=True).idxmax()
print(most_freq)
print('\n')

df['embark_town'].fillna(most_freq, inplace=True)
print(df['embark_town'][825:830])

먼저 value_counts() 메소드를 사용하여 승선도시별 승객 수를 찾고, idxmax() 메소드로 가장 큰 값을 갖는 도시를 찾는다.

fillna()에 찾아놓은 가장 많은 등장 수를 가진 승선도시를 인자로 전달하여 NaN을 치환한다.

 

예제 5-4 출력 결과

 

데이터셋의 특성상 서로 이웃하고 있는 데이터끼리 유사성을 가질 가능성이 높다.

이럴 때는 앞이나 뒤에서 이웃하고 있는 값으로 치환해 주는 것이 좋다.

fillna() 메소드method='ffill' 옵션을 추가하면 NaN이 있는 행의 직전 행에 있는 값으로 바꿔준다.

method='bfill' 옵션을 사용하면 NaN이 있는 행의 바로 다음 행에 있는 값을 가지고 치환한다.

# 예제 5-5

import seaborn as sns

df = sns.load_dataset('titanic')

print(df['embark_town'][825:830])
print('\n')

df['embark_town'].fillna(method='ffill', inplace=True)
print(df['embark_town'][825:830])

 

예제 5-5 출력 결과


파이썬 머신러닝 판다스 데이터분석
저자 : 오승환
출판 : 정보문화사
발매 : 2019.06.05

 

BELATED ARTICLES

more