-
chapter-11 그룹 연산판다스 2021. 9. 23. 00:43In [2]:
import pandas as pd df=pd.read_csv('data/gapminder.tsv',sep='\t')
2.¶
다음은 year 열을 기준으로 데이터를 그룹화한 다음 lifeExp 열의 평균을 구한 것입니다.
In [28]:avg_life_exp_by_year=df.groupby('year'). lifeExp.mean() print(avg_life_exp_by_year)
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
분할-반영-결과 과정 살펴보기 -- groupby¶
앞에서 groupby 메서드를 사용해 lifeExp 열의 연도별 평균값을 구했습니다. 그러면 실제로 groupby 메서드는 어떤 과정을 통해 데이터 집계를 할까요? groupby 메서드 자체를 분해하여 살펴보는 것은 불가능하기 때문에 비슷한 연산을 수행하는 메서드를 순서대로 실행하며 알아보겠습니다.
In [29]:years=df.year.unique() print(years)
[1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007]
2.¶
그런 다음에는 연도별로 평균값을 구합니다. 그러려면 일단 각 연도별로 데이터를 추출해야겠죠? 다음은 1952년의 데이터를 추출한 것입니다. 이 과정을 '반영'작업의 한 부분이라고 이해하세요.
In [30]:y1952=df.loc[df.year==1952,:] print(y1952.head())
country continent year lifeExp pop gdpPercap 0 Afghanistan Asia 1952 28.801 8425333 779.445314 12 Albania Europe 1952 55.230 1282697 1601.056136 24 Algeria Africa 1952 43.077 9279525 2449.008185 36 Angola Africa 1952 30.015 4232095 3520.610273 48 Argentina Americas 1952 62.485 17876956 5911.315053
3.¶
아직 lifeExp 열의 평균값을 구하지 않았습니다. 다음은 과정 2에서 추출한 1952년의 데이터에서 lifeExp 열의 평균값을 구한 것입니다. 이 과정도 '반영'작업의 한 부분입니다.
In [31]:y1952_mean=y1952.lifeExp.mean() print(y1952_mean)
49.05761971830987
In [32]:y1962_mean=y1962.lifeExp.mean() print(y1962_mean)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-32-0816777756f2> in <module> ----> 1 y1962_mean=y1962.lifeExp.mean() 2 print(y1962_mean) NameError: name 'y1962' is not defined
4.¶
과정 2~3을 반복하여 남은 연도의 평균값을 구하면 비로소 '반영'작업이 끝납니다.
In [33]:y1957=df.loc[df.year==1957,:] y1957_mean=y1957.lifeExp.mean() print(y1957_mean)
51.507401126760534
In [34]:y2007=df.loc[df.year==2007,:] y2007_mean=y2007.lifeExp.mean() print(y2007_mean)
67.00742253521126
In [37]:y1962=df.loc[df.year==1962,:] y1962_mean=y1962.lifeExp.mean() print(y1962_mean)
53.60924901408449
5.¶
마지막으로 연도별로 계산한 lifeExp의 평균값을 합칩니다. 바로 이 과정이 '결합'작업입니다.
In [38]:df2=pd.DataFrame({"year":[1952,1957,1962,2007], "":[y1952_mean, y1957_mean, y1962_mean, y2007_mean]}) print(df2)
year 0 1952 49.057620 1 1957 51.507401 2 1962 53.609249 3 2007 67.007423
groupby 메서드와 함께 사용하는 집계 메서드¶
다음은 집계 메서드를 정리한 표입니다. 이후에 수행할 실습에서 자중 등장하는 메서드이니 미리 읽어보고 넘어가는 것이 좋습니다.
집계 메서드¶
- 메서드 : 설명
- count : 누락값을 제외한 데이터 수를 변환
- size : 누락값을 포함한 데이터 수를 반환
- mean : 평균값 반환
- std : 표준편차 반환
- min : 최솟값 반환
- quantile(q=0.25) : 백분위수 25%
- quantile(q=0.50) : 백분위수 50%
- quantile(q=0.75) : 백분위수 75%
- max : 최댓값 반환
- sum : 전체 합 반환
- var : 분산 반환
- sem : 평균의 표준편차 반환
- describe : 데이터 수, 평균, 표준편차, 최소값, 백분위수(25,50,75%), 최대갓을 모두 반환
- first : 첫 번째 행 반환
- last : 마지막 행 반환
- nth : n번째 행 반환
agg 메서드로 사용자 함수와 groupby 메서드 조합하기¶
라이브러리에서 제공하는 집계 메서드로 원하는 값을 계산할 수 없는 경우에는 직접 함수를 만들어서 사용해야 합니다. 이번에는 사용자 함수와 groupby 메서드를 조합하려면 agg 메서드를 이용해야 합니다.
In [46]:def my_mean(values): n=len(values) sum = 0 for value in values: sum +=value return sum / n
2.¶
다음은 과정 1에서 만든 함수를 groupby 메서드와 조합하기 위해 agg메서드를 사용한 것입니다. 결과를 보면 mean 메서드를 사용하여 얻은 값과 동일하다는 것을 알 수 있습니다.
In [48]:agg_my_mean=df.groupby('year').lifeExp.agg(my_mean) print(agg_my_mean)
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
In [50]:def my_mean_diff(values,diff_value): n=len(values) sum = 0 for value in values: sum+=value mean=sum/n return mean -diff_value
2.¶
다음은 연도별 평균 수명에서 전체 평균 수명을 뺀 값을 구한 것입니다. agg 메서드의 첫 번째 인자에 my_mean_diff 함수를 전달하고 두 번째 인자에 전체 평균 수명값을 전달했습니다.
In [51]:global_mean=df.lifeExp.mean() print(global_mean)
59.47443936619714
In [52]:agg_mean_diff=df.groupby('year').lifeExp.agg(my_mean_diff, diff_value=global_mean) print(agg_mean_diff)
year 1952 -10.416820 1957 -7.967038 1962 -5.865190 1967 -3.796150 1972 -1.827053 1977 0.095718 1982 2.058758 1987 3.738173 1992 4.685899 1997 5.540237 2002 6.220483 2007 7.532983 Name: lifeExp, dtype: float64
In [54]:import numpy as np gdf=df.groupby('year'). lifeExp.agg([np.count_nonzero, np.mean,np.std]) print(gdf)
count_nonzero mean std year 1952 142.0 49.057620 12.225956 1957 142.0 51.507401 12.231286 1962 142.0 53.609249 12.097245 1967 142.0 55.678290 11.718858 1972 142.0 57.647386 11.381953 1977 142.0 59.570157 11.227229 1982 142.0 61.533197 10.770618 1987 142.0 63.212613 10.556285 1992 142.0 64.160338 11.227380 1997 142.0 65.014676 11.559439 2002 142.0 65.694923 12.279823 2007 142.0 67.007423 12.073021
2.¶
이번에는 집계 메서드를 딕셔너리에 담아 agg 메서드에 전달해 보겠습니다. 딕셔너리의 키로 집계 메서드를 적용할 열 이름을 전달하고 딕셔너리의 값으로 집계 메서드를 전달하면 됩니다.
In [57]:gdf_dict=df.groupby('year').agg({'lifeExp':'mean','pop':'median','gdpPercap':'median'}) print(gdf_dict)
lifeExp pop gdpPercap year 1952 49.057620 3943953.0 1968.528344 1957 51.507401 4282942.0 2173.220291 1962 53.609249 4686039.5 2335.439533 1967 55.678290 5170175.5 2678.334740 1972 57.647386 5877996.5 3339.129407 1977 59.570157 6404036.5 3798.609244 1982 61.533197 7007320.0 4216.228428 1987 63.212613 7774861.5 4280.300366 1992 64.160338 8688686.5 4386.085502 1997 65.014676 9735063.5 4781.825478 2002 65.694923 10372918.5 5319.804524 2007 67.007423 10517531.0 6124.371108
11-2 데이터 변환¶
11-1에서는 집계 메서드의 활용법을 알아보았습니다. 이번에는 데이터 변환 메서드에 대해 알아보겠습니다. 데이터 변환 메서드는 데이터와 메서드를 일대일로 대응시켜 계산하기 때문에 데이터의 양이 줄어들지 않습니다. 말 그대로 데이터를 변환하는 데 사용합니다.
표준점수 계산하기¶
통계 분야에서는 데이터의 평균과 표준편차의 차이를 표준점수라고 부릅니다. 표준점수를 구하면 변환한 데이터의 평균값이 0이 되고 표준편차는 1이 됩니다. 그러면 데이터가 표준화되어 서로 다른 데이터를 쉽게 비교할 수 있게 되죠. 표준점수는 통계에서 자주 사용하는 지표입니다.
In [58]:def my_zscore(x): return (x - x.mean())/x.std()
2.¶
다음은 각 연도별 lifeExp 열의 표준점수를 계산한 것입니다. my_zscore 함수를 적용하기 위해 transform 메서드를 사용했습니다.
In [60]:transform_z=df.groupby('year').lifeExp.transform(my_zscore) print(transform_z.head())
0 -1.656854 1 -1.731249 2 -1.786543 3 -1.848157 4 -1.894173 Name: lifeExp, dtype: float64
3.¶
my_zscore 함수는 데이터를 표준화할 뿐 집게는 하지 않습니다. 즉, 데이터의 양이 줄어들지 않습니다. 다음은 원본 데이터프레임의 데이터 크기와 변환한 데이터프레임의 데이터 크기를 비교한 것입니다.
In [61]:print(df.shape)
(1704, 6)
In [62]:print(transform_z.shape)
(1704,)
In [64]:import seaborn as sns import numpy as np np.random.seed(42) tips_10=sns.load_dataset('tips').sample(10) tips_10.loc[np.random.permutation(tips_10.index)[:4], 'total_bill']=np.NaN print(tips_10)
total_bill tip sex smoker day time size 24 19.82 3.18 Male No Sat Dinner 2 6 8.77 2.00 Male No Sun Dinner 2 153 NaN 2.00 Male No Sun Dinner 4 211 NaN 5.16 Male Yes Sat Dinner 4 198 NaN 2.00 Female Yes Thur Lunch 2 176 NaN 2.00 Male Yes Sun Dinner 2 192 28.44 2.56 Male Yes Thur Lunch 2 124 12.48 2.52 Female No Thur Lunch 2 9 14.78 3.23 Male No Sun Dinner 2 101 15.38 3.00 Female Yes Fri Dinner 2
2.¶
그런데 total_bill 열의 누락값을 단순히 total_bill 열의 평균값으로 채우면 안됩니다. 무슨 말일까요? 현재 tips_10의 데이터는 여성보다 남성이 더 만습니다. 즉, 여성과 남성을 구분하여 total_bill 열의 평균값을 구하지 않으면 여성 데이터가 남성 데이터의 영향을 많이 받아 여성의 데이터가 훼손될 수 있습니다. 다음은 성별로 그룹화한 다음 각 열의 데이터 수를 구한 것입니다. total_bill 열을 살펴보면 남ㅁ성의 누락값은 3개, 여성의 누락값은 1개라는 것을 알 수 있습니다.
In [65]:count_sex = tips_10.groupby('sex').count() print(count_sex)
total_bill tip smoker day time size sex Male 4 7 7 7 7 7 Female 2 3 3 3 3 3
3.¶
다음은 성별을 구분하여 total_bill열의 데이터를 받아 평균값을 구하는 함수입니다.
In [66]:def fill_na_mean(x): avg=x.mean() return x.fillna(avg)
4.¶
다음은 성별을 구분한 total_bill 열의 데이터를 fill_na_mean 함수에 전달하여 평균값을 구한 다음 tips_10에 새로운 열로 추가한 것입니다. 남성과 여성의 누락값을 고려하여 계산한 평균값으로 잘 채워져 있는 것을 알 수 있습니다.
In [67]:total_bill_group_mean=tips_10.groupby('sex').total_bill.transform(fill_na_mean) tips_10['fill_total_bill']=total_bill_group_mean print(tips_10)
total_bill tip sex smoker day time size fill_total_bill 24 19.82 3.18 Male No Sat Dinner 2 19.8200 6 8.77 2.00 Male No Sun Dinner 2 8.7700 153 NaN 2.00 Male No Sun Dinner 4 17.9525 211 NaN 5.16 Male Yes Sat Dinner 4 17.9525 198 NaN 2.00 Female Yes Thur Lunch 2 13.9300 176 NaN 2.00 Male Yes Sun Dinner 2 17.9525 192 28.44 2.56 Male Yes Thur Lunch 2 28.4400 124 12.48 2.52 Female No Thur Lunch 2 12.4800 9 14.78 3.23 Male No Sun Dinner 2 14.7800 101 15.38 3.00 Female Yes Fri Dinner 2 15.3800
In [69]:tips=sns.load_dataset('tips') print(tips.shape)
(244, 7)
2.¶
size 열의 데이터 수를 확인해 보면 1,5,6 테이블의 주문이 매우 적다는 것을 알 수 있습니다.
In [71]:print(tips['size'].value_counts())
2 156 3 38 4 37 5 5 1 4 6 4 Name: size, dtype: int64
3.¶
상황에 따라 이런 데이터는 제외하기도 합니다. 만약 30번 이상의 주문이 있는 테이블만 추려 데이터 분석을 하려면 어떻게 해야 할까요? 다음은 30번 이상의 주문이 있는 테이블만 그룹화하여 변수 tips_filtered에 저장한 것입니다.
In [75]:tips_filtered=tips.\ groupby('size').\ filter(lambda x: x['size'].count()>=30)
4.¶
과정 3을 거치고 나면 1,5,6 테이블의 데이터가 제외되었다는 것을 알 수 있습니다.
In [76]:print(tips_filtered.shape)
(231, 7)
In [77]:print(tips_filtered['size'].value_counts())
2 156 3 38 4 37 Name: size, dtype: int64
In [78]:tips_10=sns.load_dataset('tips').sample(10,random_state=42) print(tips_10)
total_bill tip sex smoker day time size 24 19.82 3.18 Male No Sat Dinner 2 6 8.77 2.00 Male No Sun Dinner 2 153 24.55 2.00 Male No Sun Dinner 4 211 25.89 5.16 Male Yes Sat Dinner 4 198 13.00 2.00 Female Yes Thur Lunch 2 176 17.89 2.00 Male Yes Sun Dinner 2 192 28.44 2.56 Male Yes Thur Lunch 2 124 12.48 2.52 Female No Thur Lunch 2 9 14.78 3.23 Male No Sun Dinner 2 101 15.38 3.00 Female Yes Fri Dinner 2
2.¶
groupby 메서드의 결괏값을 출력하면 자료형이 그룹 오브젝트라는 것을 확인할 수 있습니다.
In [79]:grouped =tips_10.groupby('sex') print(grouped)
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7f2ca99106d0>
3.¶
그룹 오브젝트에 포함된 그룹을 보려면 groups 속성을 출력하면 됩니다. 그러면 sex 열로 그룹화한 데이터프레임의 인덱스를 확인할 수 있습니다. 이 그룹 오브젝트로 집계, 변환, 필터작업을 수행하면 되는 것이죠.
In [80]:print(grouped.groups)
{'Male': [24, 6, 153, 211, 176, 192, 9], 'Female': [198, 124, 101]}
한 번에 그룹 오브젝트 계산하기¶
바로 앞의 실습에서 구한 그룹 오브젝트는 smoker,day,time 열과 같은 평균값을 구할 수 없는 열도 포함하고 있습니다. 이런 그룹 오브젝트에 mean 메서드와 같은 집계 메서드를 사용하면 어떻게 될까요? 오류가 발생할까요? 아닙니다! 다행히 파이썬은 자동으로 계산할 수 있는 열의 골라주는 기능을 제공합니다. 따라서 그룹 오브젝트에 mean 메서드를 사용해도 바로 평균값을 구할 수 있습니다.
In [81]:avgs=grouped.mean() print(avgs)
total_bill tip size sex Male 20.02 2.875714 2.571429 Female 13.62 2.506667 2.000000
2.¶
tips 데이터 집합의 열을 확인해 보면 평균값을 계산할 수 없는 열인 smoker,day,time 열은 그룹 연산에서 제외되었다는 것을 알 수 있습니다. 이처럼 파이썬은 그룹 연산에 적합한 열을 알아서 골라줍니다.
In [82]:print(tips_10.columns)
Index(['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size'], dtype='object')
그룹 오브젝트 활용하기¶
이번에는 그룹 오브젝트를 좀 더 다양하게 활용하는 방법에 대해 알아보겠습니다. 그룹 오브젝트를 활용하는 방법은 아주 많습니다. 여기에서 실습을 통해 소개할 내용은 그룹 오브젝트에서 '데이터 추출하기'와 '반복문 사용하기' 입니다. 만약 그룹 오브젝트를 활용하는 방법을 더 알고 싶다면 다음 웹 사이트를 참고하세요.
In [83]:female=grouped.get_group('Female') print(female)
total_bill tip sex smoker day time size 198 13.00 2.00 Female Yes Thur Lunch 2 124 12.48 2.52 Female No Thur Lunch 2 101 15.38 3.00 Female Yes Fri Dinner 2
2.¶
이번에는 그룹 오브젝트를 반복문에 사용해 보겠습니다. sex 열을 기준으로 그룹화한 tips 데이터 집합은 여성 그룹과 남성 그룹으로 나누어져 있습니다. 이 특징을 이용하여 반복문을 사용하면 됩니다. 다음은 각 성별 그룹의 데이터를 반복문을 이용하여 출력한 것입니다.
In [85]:for sex_group in grouped: print(sex_group)
('Male', total_bill tip sex smoker day time size 24 19.82 3.18 Male No Sat Dinner 2 6 8.77 2.00 Male No Sun Dinner 2 153 24.55 2.00 Male No Sun Dinner 4 211 25.89 5.16 Male Yes Sat Dinner 4 176 17.89 2.00 Male Yes Sun Dinner 2 192 28.44 2.56 Male Yes Thur Lunch 2 9 14.78 3.23 Male No Sun Dinner 2) ('Female', total_bill tip sex smoker day time size 198 13.00 2.00 Female Yes Thur Lunch 2 124 12.48 2.52 Female No Thur Lunch 2 101 15.38 3.00 Female Yes Fri Dinner 2)
3.¶
그런데 과정 2의 결과를 자세히 살펴보면 sex_group으로 넘어온 값이 튜플이라는 것을 알 수 있습니다. 다음은 sex_group의 자세한 정보를 출력한 것입니다.
In [91]:for _sex_group in grouped: print('the type is: {}\n'.format(type(sex_group))) print('the length is: {}\n'.format(len(sex_group))) first_element=sex_group[0] print('the first element is: {}\n'.format(first_element)) print('it has a type of:{}\n'.format(type(sex_group[0]))) second_element=sex_group[1] print('the second element is:\n{}\n'.format(second_element)) print('it has a type of:{}\n'.format(type(second_element))) print('what we have:') print(sex_group) break
the type is: <class 'tuple'> the length is: 2 the first element is: Female it has a type of:<class 'str'> the second element is: total_bill tip sex smoker day time size 198 13.00 2.00 Female Yes Thur Lunch 2 124 12.48 2.52 Female No Thur Lunch 2 101 15.38 3.00 Female Yes Fri Dinner 2 it has a type of:<class 'pandas.core.frame.DataFrame'> what we have: ('Female', total_bill tip sex smoker day time size 198 13.00 2.00 Female Yes Thur Lunch 2 124 12.48 2.52 Female No Thur Lunch 2 101 15.38 3.00 Female Yes Fri Dinner 2)
여러 열을 사용해 그룹 오브젝트 만들고 계산하기¶
지금까지는 하나의 열을 사용하여 그룹 오브젝트를 만들고 연산을 수행했습니다. 하지만 여러 열을 사용하여 그룹 오브젝트를 만들고 평균값을 구하는 등의 계산도 할 수 있습니다.
In [93]:bill_sex_time=tips_10.groupby(['sex','time']) group_avg=bill_sex_time.mean() print(group_avg)
total_bill tip size sex time Male Lunch 28.440000 2.560000 2.000000 Dinner 18.616667 2.928333 2.666667 Female Lunch 12.740000 2.260000 2.000000 Dinner 15.380000 3.000000 2.000000
2.¶
과정 1을 거친 group_avg의 자료형을 확인해 보면 데이터프레임이라는 것을 알 수 있습니다. 그리고 변수 group_avg에 포함된 열은 total_bill, tip, size라는 것도 알 수 있습니다.
In [94]:print(type(group_avg))
<class 'pandas.core.frame.DataFrame'>
In [95]:print(group_avg.columns)
Index(['total_bill', 'tip', 'size'], dtype='object')
3.¶
group_avg의 자료형은 데이터프레임이라고 했습니다. 그러면 인덱스는 어떻게 구성되어 있을까요? 다음은 group_avg의 인덱스를 출력한 것입니다.
In [96]:print(group_avg.index)
MultiIndex([( 'Male', 'Lunch'), ( 'Male', 'Dinner'), ('Female', 'Lunch'), ('Female', 'Dinner')], names=['sex', 'time'])
4. 과정 3과 같이 데이터프레임의 인덱스가 MultiIndex 메서드를 사용하여 데이터프레임의 인덱스를 새로 부여할 수도 있습니다.¶
In [97]:group_method=tips_10.groupby(['sex','time']).mean().reset_index() print(group_method)
sex time total_bill tip size 0 Male Lunch 28.440000 2.560000 2.000000 1 Male Dinner 18.616667 2.928333 2.666667 2 Female Lunch 12.740000 2.260000 2.000000 3 Female Dinner 15.380000 3.000000 2.000000
5.¶
reset_index 메서드 대신 as_index 인자를 False로 설정해도 과정 4와 같은 결과를 얻을 수 있습니다.
In [98]:group_param=tips_10.groupby(['sex', 'time'], as_index=False).mean() print(group_param)
sex time total_bill tip size 0 Male Lunch 28.440000 2.560000 2.000000 1 Male Dinner 18.616667 2.928333 2.666667 2 Female Lunch 12.740000 2.260000 2.000000 3 Female Dinner 15.380000 3.000000 2.000000
마무리하며¶
이 장에서는 froupby 메서드의 분리-반영-결합 과정을 구체적으로 살펴보고 데이터를 집계,변환,필터링하는 방법을 알아보았습니다. 또 여러 가지 방법으로 그룹 오브젝트를 만들고 계산하는 방법도 알아보았습니다. 데이터를 그룹화하여 계산하는 작업은 실무에서 자주 사용하므로 반드시 알아두어야 합니다.
출처 : "do it 데이터 분석을 위한 판다스 입문"
'판다스' 카테고리의 다른 글
chapter-12 시계열 데이터 (0) 2021.09.23 chapter-10 apply 메서드 활용 (0) 2021.09.23 chapter-9 문자열 처리하기 (0) 2021.09.23 chapter-8 판다스 자료형 (0) 2021.09.23 chapter-7 깔끔한 데이터 (0) 2021.09.23