#05-Pandas(판다스) DataFrame의 복사(Copy)와 결측치(NaN values) 처리
Jan 23, 2021

이번 에피소드에서는 Pandas 데이터프레임(DataFrame) 의 복사(Copy)와 중요한 전처리 Task 중의 하나인 결측치 처리 에 대해서 알아보겠습니다.

DataFrame을 활용하여 새로운 Feature(특성)을 만들어 추가하거나, 필요 없는 컬럼을 제거했을 때 원본 DataFrame이 손상되었기 때문에 다시 파일로부터 불러와야하는 불상사가 생기게 됩니다. 그래서 저는 DataFrame의 복사 기능을 잘 활용합니다. (물론 용량이 큰 DataFrame은 복사 할 때마다 용량이 늘어나 RAM이 터질 수 있습니다.)

복사한 후 특성 추출을 해서 추가 EDA를 해보고 아니다 싶으면 버리는 거죠.

그리고, 결측치 처리 주제 또한 이번 에피소드에서 다루는 데요.

결측치 처리는 데이터 전처리 프로세스 중 가장 중요하다고 생각하는 프로세스 중 하나입니다.

결측치를 어떻게 처리해 주느냐에 따라 나중에 머신러닝 모델에 데이터를 주입하고 예측한 결과가 달라지는 경우도 많습니다.

모듈 import

from IPython.display import Image
import numpy as np
import pandas as pd
import seaborn as sns

데이터셋 로드

df = sns.load_dataset('titanic')
df.head()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 22.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True

컬럼(columns) 설명

  • survivied: 생존여부 (1: 생존, 0: 사망)
  • pclass: 좌석 등급 (1등급, 2등급, 3등급)
  • sex: 성별
  • age: 나이
  • sibsp: 형제 + 배우자 수
  • parch: 부모 + 자녀 수
  • fare: 좌석 요금
  • embarked: 탑승 항구 (S, C, Q)
  • class: pclass와 동일
  • who: 성별과 동일
  • adult_male: 성인 남자 여부
  • deck: 데크 번호 (알파벳 + 숫자 혼용)
  • embark_town: 탑승 항구 이름
  • alive: 생존여부 (yes, no)
  • alone: 혼자 탑승 여부

copy

DataFrame을 복제합니다. 복제한 DataFrame을 수정해도 원본에는 영향을 미치지 않습니다.

df.head()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 22.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True

copy()로 DataFrame을 복제합니다.

df_copy = df.copy()

id 값을 확인하면 두 DataFrame의 메모리 주소가 다름을 확인할 수 있습니다.

id(df), id(df_copy)
(140082615351616, 140082615351952)
df_copy.head()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 22.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True

df_copyage를 99999로 임의 수정하도록 하겠습니다.

df_copy.loc[0, 'age'] = 99999

수정사항이 반영된 것을 확인할 수 있습니다.

df_copy.head()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 99999.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True

하지만, 원본 DataFrame의 데이터는 변경되지 않고 그대로 남아 있습니다.

df.head()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 22.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True

결측치

결측치는 비어있는 데이터를 의미합니다.

결측치에 대한 처리는 매우 중요합니다.

결측치에 대한 처리를 해주려면 다음의 내용을 반드시 알아야 합니다.

  1. 결측 데이터 확인
  2. 결측치가 아닌 데이터 확인
  3. 결측 데이터 채우기
  4. 결측 데이터 제거하기

결측치 확인 - isnull(), isnan()

컬럼(column)별 결측치의 갯수를 확인하기 위해서는 sum() 함수를 붙혀주면 됩니다.

sum()은 Pandas의 통계 관련 함수이며, 통계 관련 함수는 추후에 더 자세히 알아볼 예정입니다.

isnull()

df.isnull().sum()
survived         0
pclass           0
sex              0
age            177
sibsp            0
parch            0
fare             0
embarked         2
class            0
who              0
adult_male       0
deck           688
embark_town      2
alive            0
alone            0
dtype: int64

isna()

isnull() 과 동작이 완전 같습니다. 편한 것으로 써주세요. (심지어 도큐먼트도 같습니다)

df.isna().sum()
survived         0
pclass           0
sex              0
age            177
sibsp            0
parch            0
fare             0
embarked         2
class            0
who              0
adult_male       0
deck           688
embark_town      2
alive            0
alone            0
dtype: int64

DataFrame 전체 결측 데이터의 갯수를 합산하기 위해서는 sum()을 두 번 사용하면 됩니다.

df.isnull().sum().sum()
869

결측치가 아닌 데이터 확인 - notnull()

notnull()isnull()과 정확히 반대 개념입니다.

df.notnull().sum()
survived       891
pclass         891
sex            891
age            714
sibsp          891
parch          891
fare           891
embarked       889
class          891
who            891
adult_male     891
deck           203
embark_town    889
alive          891
alone          891
dtype: int64

결측 데이터 필터링

isnull() 함수가 결측 데이터를 찾는 boolean index 입니다.

즉, loc에 적용하여 조건 필터링을 걸 수 있습니다.

df.loc[df['age'].isnull()]
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
5 0 3 male NaN 0 0 8.4583 Q Third man True NaN Queenstown no True
17 1 2 male NaN 0 0 13.0000 S Second man True NaN Southampton yes True
19 1 3 female NaN 0 0 7.2250 C Third woman False NaN Cherbourg yes True
26 0 3 male NaN 0 0 7.2250 C Third man True NaN Cherbourg no True
28 1 3 female NaN 0 0 7.8792 Q Third woman False NaN Queenstown yes True
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
859 0 3 male NaN 0 0 7.2292 C Third man True NaN Cherbourg no True
863 0 3 female NaN 8 2 69.5500 S Third woman False NaN Southampton no False
868 0 3 male NaN 0 0 9.5000 S Third man True NaN Southampton no True
878 0 3 male NaN 0 0 7.8958 S Third man True NaN Southampton no True
888 0 3 female NaN 1 2 23.4500 S Third woman False NaN Southampton no False

177 rows × 15 columns

결측치 채우기 - fillna()

fillna()를 활용하면 결측치에 대하여 일괄적으로 값을 채울 수 있습니다.

# 원본을 copy하여 df1 변수에 
df1 = df.copy()
df1.tail()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
886 0 2 male 27.0 0 0 13.00 S Second man True NaN Southampton no True
887 1 1 female 19.0 0 0 30.00 S First woman False B Southampton yes True
888 0 3 female NaN 1 2 23.45 S Third woman False NaN Southampton no False
889 1 1 male 26.0 0 0 30.00 C First man True C Cherbourg yes True
890 0 3 male 32.0 0 0 7.75 Q Third man True NaN Queenstown no True

888번 index의 결측치가 700으로 채워진 것을 확인할 수 있습니다.

df1['age'].fillna(700).tail()
886     27.0
887     19.0
888    700.0
889     26.0
890     32.0
Name: age, dtype: float64
df1['age'] = df1['age'].fillna(700)
df1.tail()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
886 0 2 male 27.0 0 0 13.00 S Second man True NaN Southampton no True
887 1 1 female 19.0 0 0 30.00 S First woman False B Southampton yes True
888 0 3 female 700.0 1 2 23.45 S Third woman False NaN Southampton no False
889 1 1 male 26.0 0 0 30.00 C First man True C Cherbourg yes True
890 0 3 male 32.0 0 0 7.75 Q Third man True NaN Queenstown no True

카테고리 형 데이터을 채워주기 위해서는 다음과 같은 과정을 거쳐야 합니다.

이미 카테고리가 추가된 'A'나 'B'는 바로 fillna() 할 수 있습니다.

df1['deck'].fillna('A')
0      A
1      C
2      A
3      C
4      A
      ..
886    A
887    B
888    A
889    C
890    A
Name: deck, Length: 891, dtype: category
Categories (7, object): [A, B, C, D, E, F, G]

하지만, 없는 카테고리로 채워주고자 할 때는 먼저 add_categories로 카테고리를 추가한 후 채워야 합니다.

# add_categories (카테고리 추가)
# cat은 category의 지정자
df1['deck'].cat.add_categories('No Data').fillna('No Data')
0      No Data
1            C
2      No Data
3            C
4      No Data
        ...   
886    No Data
887          B
888    No Data
889          C
890    No Data
Name: deck, Length: 891, dtype: category
Categories (8, object): [A, B, C, D, E, F, G, No Data]

통계값으로 채우기

df1 = df.copy()
df1.tail()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
886 0 2 male 27.0 0 0 13.00 S Second man True NaN Southampton no True
887 1 1 female 19.0 0 0 30.00 S First woman False B Southampton yes True
888 0 3 female NaN 1 2 23.45 S Third woman False NaN Southampton no False
889 1 1 male 26.0 0 0 30.00 C First man True C Cherbourg yes True
890 0 3 male 32.0 0 0 7.75 Q Third man True NaN Queenstown no True

평균으로 채우기

df1['age'].fillna(df1['age'].mean()).tail()
886    27.000000
887    19.000000
888    29.699118
889    26.000000
890    32.000000
Name: age, dtype: float64

중앙값으로 채우기

df1['age'].fillna(df1['age'].median()).tail()
886    27.0
887    19.0
888    28.0
889    26.0
890    32.0
Name: age, dtype: float64

최빈값으로 채우기

df1['deck'].mode()
0    C
Name: deck, dtype: category
Categories (7, object): [A, B, C, D, E, F, G]

최빈값(mode)으로 채울 때에는 반드시 0번째 index 지정하여 값을 추출한 후 채워야 합니다.

df1['deck'].mode()[0]
'C'
df1['deck'].fillna(df1['deck'].mode()[0]).tail()
886    C
887    B
888    C
889    C
890    C
Name: deck, dtype: category
Categories (7, object): [A, B, C, D, E, F, G]

NaN 값이 있는 데이터 제거하기 (dropna)

df1 = df.copy()
df1.tail()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
886 0 2 male 27.0 0 0 13.00 S Second man True NaN Southampton no True
887 1 1 female 19.0 0 0 30.00 S First woman False B Southampton yes True
888 0 3 female NaN 1 2 23.45 S Third woman False NaN Southampton no False
889 1 1 male 26.0 0 0 30.00 C First man True C Cherbourg yes True
890 0 3 male 32.0 0 0 7.75 Q Third man True NaN Queenstown no True

dropna()1개 라도 NaN 값이 있는 행은 제거할 수 있스빈다. (how='any')

df1.dropna()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
6 0 1 male 54.0 0 0 51.8625 S First man True E Southampton no True
10 1 3 female 4.0 1 1 16.7000 S Third child False G Southampton yes False
11 1 1 female 58.0 0 0 26.5500 S First woman False C Southampton yes True
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
871 1 1 female 47.0 1 1 52.5542 S First woman False D Southampton yes False
872 0 1 male 33.0 0 0 5.0000 S First man True B Southampton no True
879 1 1 female 56.0 0 1 83.1583 C First woman False C Cherbourg yes False
887 1 1 female 19.0 0 0 30.0000 S First woman False B Southampton yes True
889 1 1 male 26.0 0 0 30.0000 C First man True C Cherbourg yes True

182 rows × 15 columns

기본 옵션 값은 how=any로 설정되어 있으며, 다음과 같이 변경할 수 있습니다.

  • any: 1개 라도 NaN값이 존재시 drop
  • all: 모두 NaN값이 존재시 drop
df1.dropna(how='all')
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 22.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
886 0 2 male 27.0 0 0 13.0000 S Second man True NaN Southampton no True
887 1 1 female 19.0 0 0 30.0000 S First woman False B Southampton yes True
888 0 3 female NaN 1 2 23.4500 S Third woman False NaN Southampton no False
889 1 1 male 26.0 0 0 30.0000 C First man True C Cherbourg yes True
890 0 3 male 32.0 0 0 7.7500 Q Third man True NaN Queenstown no True

891 rows × 15 columns



관련 글 더보기

- #08-Pandas(판다스) Concat(연결), Merge(병합)

- #07-Pandas(판다스) Groupby와 Pivot table

- #06-Pandas(판다스) 데이터 전처리, 추가, 삭제, 데이터 type 변환

- #04-Pandas(판다스) 통계

- #03-Pandas(판다스) 데이터프레임(DataFrame) 조회, 정렬(sort), 조건필터(loc, iloc)

데이터 분석, 머신러닝, 딥러닝의 대중화를 꿈 꿉니다.