누락값은 말 그대로 데이터에서 값을 가지지 않는 것으로 데이터가 없음을 의미한다.
데이터 분석에서 누락값은 생각 외로 많이 존재하기 때문에 이를 다루는 것이 매우 중요하다.
깨끗한 데이터를 만들기 위해 누락값의 처리 방법을 알아보자.
먼저 누락값을 눈에 보이게 확인해보자.
누락값은 비교할 값 자체가 존재하지 않기 때문에 비교 구문을 작성하면 다음과 같은 결과를 얻는다.
from numpy import NaN, NAN, nan
print(NaN==True)
print(NaN==False)
print(NaN==0)
print(NaN=='')
print(NaN==NaN)
False
False
False
False
False
어떤 값이 누락값인지 아닌지 판별해주는 isnull 메서드와 notnull 메서드가 존재한다.
사용방법은 다음과 같다.
import pandas as pd
print(pd.isnull(NaN)) # isnull 메서드를 이용해 누락값을 판별함.
print(pd.isnull(NAN))
print(pd.isnull(nan))
print(pd.notnull(42)) # notnull 메서드도 있음.
print(pd.notnull(NAN))
True
True
True
True
False
먼저 누락값이 언제 생기는지 확인해보자.
누락값이 있는 데이터를 연결하면 누락값이 발생하기도 한다.
앞장에서 사용한 visited와 survey 데이터에는 누락값(각각 1개, 2개)이 존재한다.
아래는 각각 visited와 survey이다.
ident site dated
0 619 DR-1 1927-02-08
1 622 DR-1 1927-02-10
2 734 DR-3 1939-01-07
3 735 DR-3 1930-01-12
4 751 DR-3 1930-02-26
5 752 DR-3 NaN
6 837 MSK-4 1932-01-14
7 844 DR-1 1932-03-22
taken person quant reading
0 619 dyer rad 9.82
1 619 dyer sal 0.13
2 622 dyer rad 7.80
3 622 dyer sal 0.09
4 734 pb rad 8.41
5 734 lake sal 0.05
6 734 pb temp -21.50
7 735 pb rad 7.22
8 735 NaN sal 0.06
9 735 NaN temp -26.00
10 751 pb rad 4.35
11 751 pb temp -18.50
12 751 lake sal 0.10
13 752 lake rad 2.19
14 752 lake sal 0.09
15 752 lake temp -16.00
16 752 roe sal 41.60
17 837 lake rad 1.46
18 837 lake sal 0.21
19 837 roe sal 22.50
20 844 roe rad 11.25
두 데이터를 연결할 때 concat에서 join='inner'로 지정하거나 merge를 사용하여 누락값을 처리하였다.
visited = pd.read_csv('../data/survey_visited.csv') # 누락값 1개.
survey = pd.read_csv('../data/survey_survey.csv') # 누락값 2개.
vs = visited.merge(survey, left_on='ident', right_on='taken') # 누락값 4개
print(vs)
ident site dated taken person quant reading
0 619 DR-1 1927-02-08 619 dyer rad 9.82
1 619 DR-1 1927-02-08 619 dyer sal 0.13
2 622 DR-1 1927-02-10 622 dyer rad 7.80
3 622 DR-1 1927-02-10 622 dyer sal 0.09
4 734 DR-3 1939-01-07 734 pb rad 8.41
5 734 DR-3 1939-01-07 734 lake sal 0.05
6 734 DR-3 1939-01-07 734 pb temp -21.50
7 735 DR-3 1930-01-12 735 pb rad 7.22
8 735 DR-3 1930-01-12 735 NaN sal 0.06
9 735 DR-3 1930-01-12 735 NaN temp -26.00
10 751 DR-3 1930-02-26 751 pb rad 4.35
11 751 DR-3 1930-02-26 751 pb temp -18.50
12 751 DR-3 1930-02-26 751 lake sal 0.10
13 752 DR-3 NaN 752 lake rad 2.19
14 752 DR-3 NaN 752 lake sal 0.09
15 752 DR-3 NaN 752 lake temp -16.00
16 752 DR-3 NaN 752 roe sal 41.60
17 837 MSK-4 1932-01-14 837 lake rad 1.46
18 837 MSK-4 1932-01-14 837 lake sal 0.21
19 837 MSK-4 1932-01-14 837 roe sal 22.50
20 844 DR-1 1932-03-22 844 roe rad 11.25
두 데이터에는 원래 총누락값이 3개였는데 연결한 후에는 4개로 오히려 개수가 늘어난 것을 확인할 수 있다.
이는 원래 NaN을 가지고 있던 ident열의 값이 752인 행의 값들이 survey의 taken 열의 값이 752인 4개의 행들과 연결되면서 생긴 누락값이다.
데이터프레임에 없는 열과 행 데이터를 입력하면 누락값이 발생하기도 한다.
num_legs = pd.Series({'goat': 4, 'amoeba': nan})
print(num_legs)
print(type(num_legs))
goat 4.0
amoeba NaN
dtype: float64
<class 'pandas.core.series.Series'>
scientists = pd.DataFrame({
'Name': ['Rosaline Franklin', 'William Gosset'],
'Occupation': ['Chemist', 'Statistician'],
'Born': ['1920-07-25', '1876-06-13'],
'Died': ['1958-04-16', '1937-10-16'],
'missing': [NaN, nan]
})
print(scientists)
print(type(scientists))
Name Occupation Born Died missing
0 Rosaline Franklin Chemist 1920-07-25 1958-04-16 NaN
1 William Gosset Statistician 1876-06-13 1937-10-16 NaN
<class 'pandas.core.frame.DataFrame'>
책에는 인덱스를 다시 만들때 누락값이 발생하기도 한다고 한다.
gapminder.csv 파일을 가져와 확인해보자.
gapminder = pd.read_csv('../data/gapminder.tsv', sep='\t')
life_exp = gapminder.groupby(['year'])['lifeExp'].mean()
print(life_exp)
year
1952 49.057620
1957 51.507401
1962 53.609249
1967 55.678290
1972 57.647386
1977 59.570157
1982 61.533197
1987 63.212613
1992 64.160338
1997 65.014676
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
print(life_exp.loc[range(2000,2010)]) # 책에는 누락값으로 표현된다고 하지만 에러가 발생한다.
KeyError: '[2000, 2001, 2003, 2004, 2005, 2006, 2008, 2009] not in index'
기대했던 것은 2000년부터 2009년까지는 NaN을, 나머지 2002와 2007은 값을 가진채로 출력되는 것이지만 현재는 위와 같은 에러가 발생한다. 아마 버전이 이전과 달라지면서 생긴 변화가 아닐까 생각된다.
아시는 분이 있다면 알려주세요!
2000년대 이후의 결과를 보고 싶다면 다음과 같이 코드를 작성하면 된다.
y2000 = life_exp[life_exp.index>2000]
print(y2000)
year
2002 65.694923
2007 67.007423
Name: lifeExp, dtype: float64
이제 데이터에 포함된 누락값의 개수를 구해보자.
데이터의 개수를 센 후에 총 행의 개수에서 빼면 누락값의 개수를 구할 수 있다.
많은 누락값을 가진 country_timeseries.csv를 가져와 사용한다.
행의 개수에서 데이터 수를 담은 시리즈를 빼면 브로드캐스팅되어 한 번에 계산할 수 있다.
ebola = pd.read_csv('../data/country_timeseries.csv')
print(ebola.shape) # (행, 열) == (122, 18)
num_rows = ebola.shape[0]
print(num_rows, type(ebola.count()), '\n')
num_missing = num_rows - ebola.count() # 정수 - series = 값들이 행마다 각각 계산됨.
print(num_missing) # 누락값의 개수
(122, 18)
122 <class 'pandas.core.series.Series'>
Date 0
Day 0
Cases_Guinea 29
Cases_Liberia 39
Cases_SierraLeone 35
Cases_Nigeria 84
Cases_Senegal 97
Cases_UnitedStates 104
Cases_Spain 106
Cases_Mali 110
Deaths_Guinea 30
Deaths_Liberia 41
Deaths_SierraLeone 35
Deaths_Nigeria 84
Deaths_Senegal 100
Deaths_UnitedStates 104
Deaths_Spain 106
Deaths_Mali 110
dtype: int64
numpy와 isnull을 이용하여 총 누락값과 데이터의 개수를 쉽게 구할 수 있다.
import numpy as np
print(np.count_nonzero(ebola.isnull())) # 전체 누락값의 개수
print(122*18) # 누락값이 없을 때 데이터의 개수
print(np.count_nonzero(ebola['Cases_Guinea'].isnull())) # 누락값의 개수
1214
2196
29
시리즈에 포함된 value_counts 메서드를 이용하여 시리즈의 누락값의 개수를 구할 수도 있다.
print(ebola.Cases_Guinea.value_counts(dropna=False).head())
# 시리즈에 포함된 메서드이므로 ebola(데이터프레임)의 한 열(Cases_Guinea; 시리즈)을 대상으로 사용.
NaN 29
86.0 3
495.0 2
112.0 2
390.0 2
Name: Cases_Guinea, dtype: int64
마침내 누락값을 처리해보려 한다.
누락값의 처리 방법은 크게는 두 가지로, 다른 값으로 변경하거나 삭제하는 것이다.
다른 값으로 변경할때는 주로 fillna 메서드를 사용한다.
fillna는 데이터프레임에 포함된 메서드로 처리해야 하는 데이터프레임의 크기가 매우 크고 메모리를 효율적으로 사용해야 할 때 주로 사용된다.
fillna메서드에 입력하는 값으로 누락값을 모두 변경할 수 있다.
fillna메서드에 method='ffill'을 사용하면 누락값의 바로 전 값으로 누락값을 변경한다. 바로 전 행의 값을 사용하기 때문에 처음 0행에 있는 누락값은 처리가 불가능하다.
fillna메서드에 method='bfill'을 사용하면 누락값의 바로 다음 값을 사용하여 누락값을 처리한다. 따라서 마지막행에 있는 누락값은 처리가 불가능하다.
다음의 예를 살펴보자.
print(ebola.fillna(0).iloc[0:10, 0:5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
0 1/5/2015 289 2776.0 0.0 10030.0
1 1/4/2015 288 2775.0 0.0 9780.0
2 1/3/2015 287 2769.0 8166.0 9722.0
3 1/2/2015 286 0.0 8157.0 0.0
4 12/31/2014 284 2730.0 8115.0 9633.0
5 12/28/2014 281 2706.0 8018.0 9446.0
6 12/27/2014 280 2695.0 0.0 9409.0
7 12/24/2014 277 2630.0 7977.0 9203.0
8 12/21/2014 273 2597.0 0.0 9004.0
9 12/20/2014 272 2571.0 7862.0 8939.0
누락값이 모두 0으로 변경되었다.
print(ebola.fillna(method='ffill').iloc[0:10, 0:5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
0 1/5/2015 289 2776.0 NaN 10030.0
1 1/4/2015 288 2775.0 NaN 9780.0
2 1/3/2015 287 2769.0 8166.0 9722.0
3 1/2/2015 286 2769.0 8157.0 9722.0
4 12/31/2014 284 2730.0 8115.0 9633.0
5 12/28/2014 281 2706.0 8018.0 9446.0
6 12/27/2014 280 2695.0 8018.0 9409.0
7 12/24/2014 277 2630.0 7977.0 9203.0
8 12/21/2014 273 2597.0 7977.0 9004.0
9 12/20/2014 272 2571.0 7862.0 8939.0
처음 행의 누락값을 제외하고 모두 전 행의 값들로 누락값이 변경되었다.
print(ebola.fillna(method='bfill').iloc[0:10, 0:5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
0 1/5/2015 289 2776.0 8166.0 10030.0
1 1/4/2015 288 2775.0 8166.0 9780.0
2 1/3/2015 287 2769.0 8166.0 9722.0
3 1/2/2015 286 2730.0 8157.0 9633.0
4 12/31/2014 284 2730.0 8115.0 9633.0
5 12/28/2014 281 2706.0 8018.0 9446.0
6 12/27/2014 280 2695.0 7977.0 9409.0
7 12/24/2014 277 2630.0 7977.0 9203.0
8 12/21/2014 273 2597.0 7862.0 9004.0
9 12/20/2014 272 2571.0 7862.0 8939.0
모든 누락값이 다음 행의 값들로 변경되었다.
fillna대신에 interpolate 메서드를 사용하기도 한다. 한국말로 내삽이라고 하며, 누락값 앞뒤의 두 값의 중간값을 사용하여 데이터가 이어지듯이 연결할 수 있다. ffill이나 bfill과 마찬가지로 양 끝에 있는 값은 처리가 불가능하다
print(ebola.interpolate().iloc[0:10, 0:5])
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone
0 1/5/2015 289 2776.0 NaN 10030.0
1 1/4/2015 288 2775.0 NaN 9780.0
2 1/3/2015 287 2769.0 8166.0 9722.0
3 1/2/2015 286 2749.5 8157.0 9677.5
4 12/31/2014 284 2730.0 8115.0 9633.0
5 12/28/2014 281 2706.0 8018.0 9446.0
6 12/27/2014 280 2695.0 7997.5 9409.0
7 12/24/2014 277 2630.0 7977.0 9203.0
8 12/21/2014 273 2597.0 7919.5 9004.0
9 12/20/2014 272 2571.0 7862.0 8939.0
위와 같은 방법들로 누락값을 변경하기도 하지만, 누락값을 삭제하는 것으로 처리하기도 한다.
누락값의 삭제는 dropna 메서드를 이용하여 간단하게 처리할 수 있다.
이때, 너무 많은 데이터가 한 번에 사라질 수 있으므로 분석하는 사람의 판단이 중요하다.
아래의 결과를 살펴보면 데이터가 대폭 줄어든 것을 알 수 있다.
print(ebola.shape)
ebola_dropna = ebola.dropna() # 누락값이 포함된 시리즈를 삭제하는 메서드.
print(ebola_dropna.shape, '\n')
print(ebola_dropna)
(122, 18)
(1, 18)
Date Day Cases_Guinea Cases_Liberia Cases_SierraLeone \
19 11/18/2014 241 2047.0 7082.0 6190.0
Cases_Nigeria Cases_Senegal Cases_UnitedStates Cases_Spain Cases_Mali \
19 20.0 1.0 4.0 1.0 6.0
Deaths_Guinea Deaths_Liberia Deaths_SierraLeone Deaths_Nigeria \
19 1214.0 2963.0 1267.0 8.0
Deaths_Senegal Deaths_UnitedStates Deaths_Spain Deaths_Mali
19 0.0 1.0 0.0 6.0
만약 누락값을 처리하지 않고 데이터를 계산하면 어떻게 될까?
ebola 데이터의 누락값이 있는 3개 열을 이용하여 이를 확인해보자.
ebola['Cases_multiple'] = ebola['Cases_Guinea'] + ebola['Cases_Liberia'] + ebola['Cases_SierraLeone']
# 누락값이 있는 3개의 열을 더한 새로운 열을 만든다.
ebola_subset = ebola.loc[:, ['Cases_Guinea', 'Cases_Liberia', 'Cases_SierraLeone', 'Cases_multiple']]
print(ebola_subset.head(n=10))
Cases_Guinea Cases_Liberia Cases_SierraLeone Cases_multiple
0 2776.0 NaN 10030.0 NaN
1 2775.0 NaN 9780.0 NaN
2 2769.0 8166.0 9722.0 20657.0
3 NaN 8157.0 NaN NaN
4 2730.0 8115.0 9633.0 20478.0
5 2706.0 8018.0 9446.0 20170.0
6 2695.0 NaN 9409.0 NaN
7 2630.0 7977.0 9203.0 19810.0
8 2597.0 NaN 9004.0 NaN
9 2571.0 7862.0 8939.0 19372.0
누락값이 하나라도 있으면 더한 값이 모두 누락값으로 처리된 것을 알 수 있다.
이를 방지하기 위해 sum 메서드에 skipna=True를 사용하는 방법도 있다.
print(ebola.Cases_Guinea.sum(skipna=True)) # 누락값은 무시하고 계산.
print(ebola.Cases_Guinea.sum(skipna=False))
84729.0
nan
'Pandas' 카테고리의 다른 글
[Do it! Pandas] 8. 판다스 자료형 (0) | 2023.02.18 |
---|---|
[Do it! Pandas] 7. 깔끔한 데이터 (0) | 2023.02.12 |
[Do it! Pandas] 5. 데이터 연결하기 (0) | 2023.02.12 |
[Do it! Pandas] 4. matplotlib 라이브러리를 이용한 다양한 그래프 그리기 (0) | 2023.02.10 |
[Do it! Pandas] 데이터프레임과 시리즈, 브로드캐스팅 (0) | 2023.02.09 |