누락값과 누락값 확인하기¶
누락값(NaN)은 NaN,NAN,nan과 같은 방법으로 표기할 수 있습니다. 이 책에서는 누락값을 NaN이라고 표기하여 사용합니다. 그러면 바로 실습을 진행해 보면서 누락값이 무엇인지 알아보겠습니다.
누락값 확인하기¶
먼저 누락값을 사용하기 위해 numpy에서 누락값을 불러옵니다.
from numpy import NaN, NAN, nan
2.¶
누락값은 0, ''와 같은 값과는 다른 개념이라는 것에 주의해야 합니다. 누락값은 말 그대로 데이터 자체가 없다는 것을 의미합니다. 그래서 '같다'라는 개념도 없죠. 다음은 누락값과 True,False,0,''을 비교한 결과입니다.
print(NaN == True)
False
print(NaN == False)
False
print(NaN == 0 )
False
print(NaN == '')
False
3.¶
고정 2에서도 언급했듯이 누락값은 값 자체가 없기 때문에 자기 자신과 비교해도 True가 아닌 False가 출력됩니다.
print(NaN == NaN)
False
print(NaN == nan)
False
print(NaN == NAN)
False
print(nan == NAN)
False
4.¶
그러면 누락값은 어떻게 확인할 수 있을까요? 다행히 판다스에는 누락값을 확인하는 메서드인 isnull이 있습니다. 오른쪽은 isnull 메서드로 누락값을 검사한 예입니다.
import pandas as pd
print(pd.isnull(NaN))
True
print(pd.isnull(nan))
True
print(pd.isnull(NAN))
True
5.¶
반대의 경우(누락값이 아닌 경우)도 검사할 수 있습니다. 오른쪽은 notnull 메서드로 누락값이 아닌 경우를 검사한 예입니다.
print(pd.notnull(NaN))
False
print(pd.notnull(42))
True
print(pd.notnull('missing'))
True
누락값이 생기는 이유¶
누락값은 왜 생길까요? 누락값은 처음부터 누락값이 있는 데이터를 불러오거나 데이터를 연결, 입력하는 등의 과정에서 생길 수 있습니다. 그러면 다음 예제를 통해 누락값이 생기는 이유에 대해 자세히 알아보겠습니다.
visited = pd.read_csv('./data/survey_visited.csv')
survey = pd.read_csv('./data/survey_survey.csv')
print(visited)
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
print(survey)
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
2.¶
과정 1에서 구한 데이터 집합을 연결해 볼까요? 그러면 누락값이 많이 생겨난 것을 볼 수 있습니다.
vs = visited.merge(survey, left_on='ident', right_on='taken')
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. 데이터를 입력할 때 누락값이 생기는 경우¶
누락값은 데이터를 잘못 입력하여 생길 수도 있습니다. 다음은 시리즈를 생성할 때 데이터 프레임에 없는 열과 행 데이터를 입력하여 누락값이 생긴 것입니다. scientists 데이터프레임을 확인하면 missing이라는 열과 함께 해 데이터에 누락값이 추가된 것을 확인할 수 있습니다.
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'>
4. 범위를 지정하여 데이터를 추출할 때 누락값이 생기는 경우¶
데이터프레임에 존재하지 않는 데이터를 추출하면 누락값이 생깁니다. 이번에는 갭마인더 데이터 집합을 불러와 실습해 보겠습니다.
gapminder = pd.read_csv('./data/gapminder.tsv', sep='\t')
5.¶
다음은 gapmider 데이터프레임을 연도별로 그룹화한 다음 lifeExp 열의 평균을 구한 것입니다.
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
6.¶
다음은 range 메서드를 이용하여 life_Exp 열에서 2000~2009년의 데이터를 추출한 것입니다. 그런데 이렇게 데이터를 추출하면 처음부터 life_Exp열에 없었던 연도가 포함되기 때문에 누락값이 많이 발생합니다.
print(life_exp.loc[range(2000, 2010), ])
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-29-b8bd5e983958> in <module> ----> 1 print(life_exp.loc[range(2000, 2010), ]) ~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in __getitem__(self, key) 871 # AttributeError for IntervalTree get_value 872 pass --> 873 return self._getitem_tuple(key) 874 else: 875 # we by definition only have the 0th axis ~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _getitem_tuple(self, tup) 1051 # ugly hack for GH #836 1052 if self._multi_take_opportunity(tup): -> 1053 return self._multi_take(tup) 1054 1055 return self._getitem_tuple_same_dim(tup) ~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _multi_take(self, tup) 1003 d = { 1004 axis: self._get_listlike_indexer(key, axis) -> 1005 for (key, axis) in zip(tup, self.obj._AXIS_ORDERS) 1006 } 1007 return self.obj._reindex_with_indexers(d, copy=True, allow_dups=True) ~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in <dictcomp>(.0) 1003 d = { 1004 axis: self._get_listlike_indexer(key, axis) -> 1005 for (key, axis) in zip(tup, self.obj._AXIS_ORDERS) 1006 } 1007 return self.obj._reindex_with_indexers(d, copy=True, allow_dups=True) ~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _get_listlike_indexer(self, key, axis, raise_missing) 1252 keyarr, indexer, new_indexer = ax._reindex_non_unique(keyarr) 1253 -> 1254 self._validate_read_indexer(keyarr, indexer, axis, raise_missing=raise_missing) 1255 return keyarr, indexer 1256 ~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _validate_read_indexer(self, key, indexer, axis, raise_missing) 1314 with option_context("display.max_seq_items", 10, "display.width", 80): 1315 raise KeyError( -> 1316 "Passing list-likes to .loc or [] with any missing labels " 1317 "is no longer supported. " 1318 f"The following labels were missing: {not_found}. " KeyError: "Passing list-likes to .loc or [] with any missing labels is no longer supported. The following labels were missing: Int64Index([2000, 2001, 2003, 2004, 2005, 2006, 2008, 2009], dtype='int64', name='year'). See https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike"
7.¶
앞에서 발생한 문제를 해결하기 위해서는 불린 추출을 이용하여 데이터를 추출하면 됩니다.
y2000 = life_exp[life_exp.index > 2000]
print(y2000)
year 2002 65.694923 2007 67.007423 Name: lifeExp, dtype: float64
ebola = pd.read_csv('./data/country_timeseries.csv')
2.¶
먼저 count 메서드로 누락값이 아닌 값의 개수를 구해 보겠습니다.
print(ebola.count())
Date 122 Day 122 Cases_Guinea 93 Cases_Liberia 83 Cases_SierraLeone 87 Cases_Nigeria 38 Cases_Senegal 25 Cases_UnitedStates 18 Cases_Spain 16 Cases_Mali 12 Deaths_Guinea 92 Deaths_Liberia 81 Deaths_SierraLeone 87 Deaths_Nigeria 38 Deaths_Senegal 22 Deaths_UnitedStates 18 Deaths_Spain 16 Deaths_Mali 12 dtype: int64
3.¶
과정 2의 결과만 잘 활용해도 누락값의 개수를 쉽게 구할 수 있습니다. shape[0]에 전체 행의 데이터 개수가 저장되어 있다는 점을 이용하여 shape[0]에서 누락값이 아닌 값의 개수를 빼면 누락값의 개수를 구할 수 있습니다.
num_rows = ebola.shape[0]
num_missing = num_rows - ebola.count()
print(num_missing)
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
4.¶
count 메서드를 사용해도 되지만 count_nonzero, isnull 메서드를 조합해도 누락값의 개수를 구할 수 있습니다.
import numpy as np
print(np.count_nonzero(ebola.isnull()))
1214
print(np.count_nonzero(ebola['Cases_Guinea'].isnull()))
29
5.¶
시리즈에 포함된 value_counts 메서드는 지정한 열의 빈도를 구하는 메서드입니다. value_counts 메서드를 사용해 Cases_Guinea 열의 누락값 개수를 구하려면 다음과 같이 입력합니다.
print(ebola.Cases_Guinea.value_counts(dropna=False).head())
NaN 29 86.0 3 495.0 2 112.0 2 390.0 2 Name: Cases_Guinea, dtype: int64
누락값 처리하기¶
누락값은 누락값을 임의의 값으로 변경하거나 데이터프레임에 이미 있는 값으로 대신 채우는 방법 등으로 처리할 수 있습니다. 그러면 누락값을 처리하는 방법을 하나씩 알아보겠습니다.
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
2.¶
fillna 메서드의 method 인잣값을 ffill로 지정하면 누락값이 나타나기 전의 값으로 누락값이 나타나기 전의 값으로 누락값이 변경됩니다.예를 들어 6행의 누락값은 누락값이 나타나기 전의 값인 5행의 값을 사용하여 누락값을 처리합니다. 하지만 0,1행은 처음부터 누락값이기 때문에 누락값이 그대로 남아 있습니다.
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
3.¶
method 인잣값을 bfill로 지정하면 누락값이 나타난 이후의 첫 번째 값으로 앞쪽의 누락값이 모두 변경됩니다. 즉, 과정 2의 반대 방향으로 누락값을 처리한다고 생각하면 됩니다. 하지만 이 방법도 마지막 값이 누락값인 경우에는 처리하지 못한다는 단점이 있습니다.
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
4.¶
과정 2,3에서는 데이터프레임의 값을 그대로 사용하여 누락값을 처리했습니다. interpolate 메서드는 누락값 양쪽에 있는 값을 이용하여 중간값을 구한 다음 누락값을 처리합니다. 이렇게 하면 데이터프레임이 일정한 간격을 유지하고 있는 것처럼 수정할 수 있습니다.
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
5. 누락값 삭제하기¶
누락값이 필요 없을 경우에는 누락값을 삭제해도 됩니다. 하지만 누락값을 무작정 삭제하면 데이터가 너무 편향되거나 데이터의 개수가 너무 적어질 수도 있습니다. 그래서 누락값을 삭제할 때는 분석하는 사람이 잘 판단해야 합니다. 먼저 ebola의 데이터 구조를 확인해 보겠습니다.
print(ebola.shape)
(122, 18)
6.¶
누락값을 삭제하기 위해 dropna 메서드를 사용하겠습니다. 누락값이 포함된 행들이 모두 삭제되기 때문에 많은 데이터가 삭제되었습니다.
ebola_dropna = ebola.dropna()
print(ebola_dropna.shape)
(1, 18)
print(ebola_dropna)
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 발병 수를 구하는 실습입니다. ebola 데이터프레임에는 누락값이 많습니다. 이 점을 염두에 구소 다음 실습을 진행해 보세요.
누락값이 포함된 데이터 계산하기¶
1.¶
Guines, Liberia, SierraLeone 열에는 누락값들이 존재합니다. 만약 누락값이 존재하는 Guinea, Liberia, SierraLeone 열을 가지고 ebola 발병 수의 합을 계산하면 어떻게 될까요? 다음은 Guses_Guinea, Cases_Liberia, Cases_SierraLeone 열을 더하여 Cases_multiple 열을 새로 만든 것입니다.
ebola['Cases_multiple'] = ebola['Cases_Guinea'] + ebola['Cases_Liberia'] + ebola['Cases_SierraLeone']
2.¶
과정 1에서 계산한 Cases_multiple 열을 포함하여 ebola_subset 이라는 데이터프레임을 새로 만들어서 어떤 값이 존재하는지 확인해 보겠습니다. Cases_Guinea, Cases_LiberiaLeone에서 누락값이 하나라도 있는 행은 계산 결과(Cases_multiple)가 NaN이 되었음을 알 수 있습니다. 즉, 계산 결과 누락값이 더 많이 생겼습니다.
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
3.¶
Cases_multiple 열을 sum 메서드를 사용해 더하면 세 지역의 ebola 발병 수의 합을 구할 수 있습니다. 이때 sum 메서드를 그냥 사용하면 누락값을 포함해 계산합니다. 따라서 결괏값도 누락값이 됩니다. 누락값을 무시한 채 계산하려면 skipna 인잣값을 True로 설정 하면 됩니다.
print(ebola.Cases_Guinea.sum(skipna = True))
84729.0
print(ebola.Cases_Guinea.sum(skipna = False))
nan
마무리하며¶
데이터 분석 분야에서는 누락값을 처리하는 능력이 매우 중요합니다. 데이터를 통해 의사 결정이나 추론을 하려면 반드시 필요한 능력이죠.이 장에서는 누락값을 처리하는 여러가지 방법을 알아보았습니다. 누락값은 언제 어디서든 생길 수 있기 때문에 누락값을 처리하는 방법은 반드시 알아두어야 합니다.
출처 : Do it 데이터 분석을 위한 판다스입문
'Do it 판다스 입문' 카테고리의 다른 글
Do it pandas Chapter 8. 판다스 자료형 (0) | 2021.03.28 |
---|---|
Do it pandas Chapter 7. 깔끔한 데이터 (0) | 2021.03.28 |
Do it pandas Chapter 5. 데이터 연결하기 (0) | 2021.03.26 |
Do it pandas Chapter 4. 그래프 그리기 (이어서) (0) | 2021.03.25 |
Do it pandas Chapter 4. 그래프 그리기 (0) | 2021.03.24 |