본문 바로가기
Python/Pandas

[2편] 데이터 프레임 살펴보기: Null 값의 처리와 제어

by 모두의 케빈 2022. 11. 13.

안녕하세요. 모두의 케빈입니다.

오늘은 데이터 프레임의 Null 값을 확인하고 이를 처리하는 방법에 대해 실습해보도록 하겠습니다.

 

■ 데이터 프레임의 Null 값 확인하기

 

실습을 위해 데이터 프레임을 불러오겠습니다. 데이터는 Kaggle에서 학생들의 점수(exams.csv) 파일을 사용했습니다. 

import pandas as pd

raw_data = pd.read_csv("exams.csv")
df = raw_data.iloc[:,[0,1,5,6,7]].head(100)       # 100개의 데이터만 사용
df.columns = ['성별','그룹','수학','국어','영어'] # 데이터 프레임의 컬럼명 재설정
df

 

 

 

 

 

 

 

 

 

 

호출한 데이터 프레임에 Null 값이 있는지 확인해보겠습니다.

 

info() : 데이터 프레임의 Null 값 확인하기

 

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100 entries, 0 to 99
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   성별      100 non-null    object
 1   그룹      100 non-null    object
 2   수학      100 non-null    int64 
 3   국어      100 non-null    int64 
 4   영어      100 non-null    int64 
dtypes: int64(3), object(2)
memory usage: 4.0+ KB

 

호출한 데이터 프레임에는 Null 값이 없네요. info() 함수 말고, 다른 방법으로도 Null 값을 확인할 수 있습니다.

 

isnull()과 isnull().sum()

 

isnull() 함수는 데이터 프레임의 개별 Cell에 적용되어 각각의 값이 Null 인지, 아닌지를 Boolean 타입으로 반환합니다.

df.isnull()
       성별	그룹	수학	국어	영어
0	False	False	False	False	False
1	False	False	False	False	False
2	False	False	False	False	False
3	False	False	False	False	False
4	False	False	False	False	False
...	...	...	...	...	...
95	False	False	False	False	False
96	False	False	False	False	False
97	False	False	False	False	False
98	False	False	False	False	False
99	False	False	False	False	False
100 rows × 5 columns

 

Boolean형에서 True는 1을 False는 0을 의미합니다. isnull() 함수를 통해 Boolean 타입의 데이터 프레임이 반환되면, 이를 세로 방향으로 모두 더해주면 Null 값의 개수를 확인할 수 있습니다. 아래 결과를 보면 Null 값이 하나도 없음을 알 수 있습니다.

df.isnull().sum()
# df.isnull().sum(axis=0)  // 위와 동일한 표현. Default가 axis = 0이다.
성별    0
그룹    0
수학    0
국어    0
영어    0
dtype: int64

 

 

데이터 프레임에 Null 값 채우기 : None 

 

실습을 위해 데이터 프레임에 Null 값을 채워보겠습니다. 데이터 프레임에서 Null 값은 None을 의미합니다. 아래 결과를 보면 Null 값이 잘 들어간 것으로 보입니다.

import random


# 랜덤으로 데이터 프레임에 Null 값 부여
for i in range(250):
    rand_row_num = random.randrange(0,100)
    rand_col_num = random.randrange(0,5)
    df.iloc[rand_row_num,rand_col_num] = None
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100 entries, 0 to 99
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   성별      61 non-null     object 
 1   그룹      70 non-null     object 
 2   수학      59 non-null     float64
 3   국어      61 non-null     float64
 4   영어      53 non-null     float64
dtypes: float64(3), object(2)
memory usage: 4.0+ KB

 

[Tip #1] 특정 열에서 Null 값이 어디 있는지 알고 싶다면 아래와 같은 방법을 응용해보세요. 

# Python의 Fancy Indexing 활용
df[df["그룹"].isnull()].head(10)
       성별	그룹	수학	국어	영어
3	male	None	NaN	78.0	68.0
4	male	None	78.0	73.0	NaN
11	female	None	NaN	NaN	NaN
13	None	None	74.0	74.0	NaN
14	male	None	81.0	87.0	NaN
22	None	None	71.0	69.0	73.0
25	male	None	51.0	NaN	NaN
26	female	None	NaN	60.0	NaN
28	female	None	74.0	NaN	NaN
31	None	None	78.0	74.0	73.0

 

[Tip #2] Null 값이 아닌 유효 값만 확인하는 방법은 아래와 같습니다.

# Null 값이 아닌 유효값만 데이터 프레임 형태로 반환
df.notnull()
df.notna()

# Null 값이 아닌 유효값의 개수 파악
df.notnull().sum()   
df.notna().sum()

 

 

 

 

dropna() : Null 값이 있는 행과 열을 제거

 

dropna() 함수는 결측 값이 있는 행 또는 열을 제거합니다. default는 행을 제거(axis=0)합니다.

df.dropna() # default는 axis = 0
df.dropna(axis=0) 
#df.dropna(axis=1) # 결측치가 있는 열 전체 제거. 추천하지 않음.
       성별	그룹	수학	국어	영어
12	male	group D	77.0	87.0	85.0
23	female	group B	44.0	55.0	54.0
29	female	group A	33.0	54.0	51.0
31	male	group A	78.0	74.0	73.0
49	male	group C	60.0	63.0	57.0
54	male	group D	57.0	57.0	47.0
56	female	group C	67.0	80.0	84.0
62	male	group E	69.0	67.0	62.0
87	male	group D	97.0	93.0	88.0

 

결측 값을 제거하는 기준을 조절할 수 있습니다. how를 사용하면 되는데요. any는 행 또는 열에 결측 값이 하나라도 있는 경우에 결측 행 또는 열을 제거합니다. dropna()의 default입니다. all은 행 또는 열에 결측 값이 모두 있는 경우에만 행 또는 열을 제거합니다.

df.dropna(how = "any") # default
df.dropna(how = "all")

 

특정 열에 대해서만 결측 값을 제거하도록 설정할 수 있습니다. subset을 사용하면 지정한 열에 대해서만 결측 값을 제거합니다.

# df.dropna(how = "any", axis = 0, subset =["성별"])
df.dropna(how = "any", axis = 0, subset =["성별", "그룹"]).head(10)
       성별	그룹	수학	국어	영어
1	female	group D	NaN	NaN	NaN
2	male	group E	NaN	60.0	50.0
4	male	group E	NaN	73.0	NaN
5	female	group D	63.0	77.0	NaN
11	female	group D	80.0	87.0	NaN
12	male	group D	77.0	87.0	85.0
14	male	group E	NaN	87.0	NaN
17	female	group C	54.0	62.0	NaN
22	male	group B	71.0	NaN	73.0
23	female	group B	44.0	55.0	54.0
​

 

thresh 옵션은 임계값 이라고도 불립니다. 행 또는 열에 결측 값이 몇 개 이상이어야만 제거하도록 설정할 수 있습니다. 

df.dropna(thresh = 2) # 결측 값이 2개 이상 있어야만 제거
      성별	그룹	수학	국어	영어
0	male	group A	NaN	67.0	63.0
1	female	group D	40.0	59.0	55.0
2	male	group E	59.0	60.0	50.0
3	male	group B	77.0	NaN	68.0
4	male	None	78.0	73.0	68.0
...	...	...	...	...	...
95	None	group C	NaN	63.0	67.0
96	female	None	95.0	NaN	NaN
97	male	group B	NaN	52.0	NaN
98	male	None	63.0	NaN	49.0
99	male	None	NaN	54.0	53.0
96 rows × 5 columns

 

 

fillna() : 결측 값을 지정한 조건으로 채우기

 

지금까지 결측 값을 제거하는 방법에 대해 알아봤다면 지금부터는 결측 값을 채우는 방법에 대해 알아보도록 하겠습니다. fillna() 함수를 사용하면 사용자가 지정한 옵션에 맞게 결측 값을 다른 값으로 대체합니다.

df.fillna(0).head(5) # 결측 값을 모두 0으로 대체
      성별	그룹	수학	국어	영어
0	male	group A	0.0	67.0	63.0
1	female	group D	40.0	59.0	55.0
2	male	group E	59.0	60.0	50.0
3	male	group B	77.0	0.0	68.0
4	male	0	78.0	73.0	68.0
5	female	0	0.0	77.0	0.0
6	female	group A	0.0	59.0	63.0
7	male	0	0.0	0.0	84.0

그런데 위의 결과를 보면 조금 이상합니다. 그룹 열은 값이 group A, B, C, D, E 등인데 결측 값을 0으로 채우게 되면 데이터를 훼손시키게 됩니다. 따라서 결측 값을 채울 때, 데이터의 특성을 고려해서 따로 채워줘야 할 필요성이 있습니다. 이럴 땐 어떻게 하면 될까요?

 

 

열 별로 결측 값을 다르게 채우고 싶은 경우, 아래와 같이 특정 열과 대체 값을 직접 맵핑해주면 됩니다. 수학 열은 수학 열의 평균으로, 국어 열은 국어 열의 중위 값으로, 영어 열은 0으로, 성별 열은 male, 그룹 열은 group A 값으로 결측 값을 대체해보겠습니다.

df.fillna({"수학":df["수학"].mean(), "국어":df["국어"].median(), "영어":0}, inplace = True)
df.fillna({"성별":"male","그룹":"group A"}, inplace = True)

# df.loc[:,"성별"].fillna("group A", inplace= True) 유사한 결과, 다른 접근법
성별	그룹	수학	국어	영어
0	female	group A	67.0	67.0	63.0
1	male	group D	65.5	67.0	31.0
2	male	group A	65.5	67.0	50.0
3	male	group B	77.0	78.0	31.0
4	male	group A	78.0	73.0	68.0
...	...	...	...	...	...
95	female	group C	65.5	63.0	67.0
96	male	group E	95.0	100.0	31.0
97	male	group A	65.5	52.0	49.0
98	male	group A	63.0	50.0	49.0
99	male	group A	60.0	67.0	53.0
100 rows × 5 columns

 

그 외 바로 이전 값이나 뒤의 값으로 대체하는 방법이 있습니다.

df.fillna(method = 'bfill') # 뒤의 값으로 대체
df.fillna(method = 'ffill') # 앞의 값으로 대체

 

 

긴 글 읽어주셔서 감사합니다. 궁금한 점은 댓글 남겨주세요. :)

댓글