티스토리 뷰
#16 판다스 chapter 16. 그룹연산 #1
그룹 연산
데이터 집계
데이터 집계하기
앞서 배웠던 갭마인더 데이터 집합으로 각 연도의 평균 수명을 구했던 것을 토대로 수집한 데이터를 바탕으로 평균이나 합 등을 구하여 의미있는 값을 도출해 내는 것을 '집계'라고 한다. 데이터를 집계하면 전체 데이터를 요약, 정리하여 볼 수 있기 때문에 데이터 분석이 훨씬 편해진다. 그러면 groupby 메서드로 평균값을 구하는 과정을 통해 데이터 집계가 무엇인지 알아보자.
- groupby 메서드로 평균값 구하기
1) 먼저 갭마인더 데이터 집합을 불러온다.
import pandas as pd
df = pd.read_csv('C:/Users/이재윤/doit_pandas_study/data/gapminder.tsv', sep='\t')
2) 다음은 year열을 기준으로 데이터를 그룹화한 다음 lifeExp 열의 평균을 구한 것이다.
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 메서드 자체를 분해하여 살펴보는 것은 불가능하기 때문에 비슷한 연산을 수행하는 메서드를 순서대로 실행하며 알아보자.
- 분할-반영-결합 과정 살펴보기
1) 실제로 groupby 메서드에 life 열을 전달하면 가장 먼저 연도별로 데이터를 나누는 과정이 진행된다. 다음은 year 열의 데이터를 중복 없이 추출한 것이다. groupby 메서드에 열 이름을 전달하면 이런 '분할' 작업이 먼저 일어난다.
years = df.year.unique()
print(years)
[1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007]
2) 그런 다음 연도별로 평균값을 구한다. 그러려면 일단 각 연도별로 데이터를 추출해야 된다. 다음은 1952년의 데이터를 추출한 것이다. 이 과정을 '반영' 작업의 한 부분이라고 이해하면 된다.
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 열의 평균값을 구한 것이다. 이 과정도 '반영'작업의 한 부분이다.
y1952_mean = y1952.lifeExp.mean()
print(y1952_mean)
49.057619718309866
4) 과정 2~3을 반복하여 남은 연도의 평균값을 구하면 비로소 '반영' 작업이 끝난다.
y1957 = df.loc[df.year == 1957, :]
y1957_mean = y1957.lifeExp.mean( )
print(y1957_mean)
y1962 = df.loc[df.year == 1962, :]
y1962_mean = y1962.lifeExp.mean( )
print(y1962_mean)
y2007 = df.loc[df.year == 2007, :]
y2007_mean = y2007.lifeExp.mean( )
print(y2007_mean)
51.50740112676056
53.609249014084504
67.00742253521126
5) 마지막으로 연도별로 계산한 lifeExp의 평균값을 합친다. 바로 이 과정이 '결합 작업이다.
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 메서드를 조합해서 사용해 보자. 사용자 함수와 groupby 메서드를 조합하려면 agg 메서드를 이용해야 한다.
- 평균값을 구하는 사용자 함수와 groupby 메서드
1) 다음은 입력받은 열의 평균값을 구하는 함수이다.
def my_mean(values):
n = len(values)
sum = 0
for value in values:
sum += value
return sum / n
2) 다음은 과정 1에서 만든 함수를 groupby 메서드와 조합하기 위해 agg 메서드를 사용한 것이다. 결과를 보면 mean 메서드를 사용하여 얻은 값과 동일하다는 것을 알 수 있다.
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
- 2개의 인잣값을 받아 처리하는 사용자 함수와 groupby 메서드
1) 이번엔 2개의 인잣값을 받아 처리하는 사용자 정의 함수(my_mean_diff)를 만들어 보자. 다음은 첫 번째 인자로 받은 열의 평균값을 구하여 두 번째 인자로 받은 값과의 차이를 계산한 다음 반환하는 함수다.
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 함수를 전달하고 두 번째 인자에 전체 평균 수명값을 전달 했다.
global_mean = df.lifeExp.mean()
print(global_mean)
59.474439366197174
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
여러 개의 집계 메서드 한 번에 사용하기
여러 개의 집계 메서드를 한 번에 사용하고 싶다면 어떻게 해야 할까? 집계 메서드를 리스트나 딕셔너리에 담아 agg 메서드에 전달하면 된다.
- 집계 메서드를 리스트, 딕셔너리에 담아 전달하기
1) 다음은 연도별로 그룹화한 lifeExp 열의 0이 아닌 값의 개수, 평균, 표준편차를 한 번에 계산하여 출력한 것이다. 넘파이 메서드인 count_nonzero, mean, std를 리스트에 담아 agg 메서드에 전달했다.
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메서드에 전달해 보자. 딕셔너리의 키로 집계 메서드를 적용할 열 이름을 전달하고 딕셔너리의 값으로 집계 메서드를 전달하면 된다.
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
데이터 변환
이번에는 데이터 변환 메서드에 대해 알아 보자. 데이터 변환 메서드는 데이터와 메서드를 일대일로 대응시켜 계산하기 때문에 데이터의 양이 줄어들지 않는다. 말 그대로 데이터를 변환하는 데 사용한다.
표준점수 계산하기
통계 분야에서는 데이터의 평균과 표준편차의 차이를 표준점수라고 부른다. 표준점수를 구하면 변환한 데이터의 평균값이 0이 되고 표준편차는 1이 된다. 그러면 데이터가 표준화되어 서로 다른 데이터를 쉽게 비교할 수 있다.
- 표준점수 계산하기
1) 다음은 표준점수를 계산하는 함수이다.
def my_zscore(x):
return (x - x.mean()) / x.std()
2) 다음은 각 연도별 lifeExp 열의 표준점수를 계산한 것이다. my_zscore 함수를 적용 하기 위해 transform 메서드를 사용했다.
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 함수는 데이터를 표준화할 뿐 집계는 하지 않는다. 즉, 데이터의 양이 줄어들지 않는다. 다음은 원본 데이터프레임의 데이터 크기와 변환한 데이터프레임의 데이터 크기를 비교한 것이다.
print(df.shape)
print(transform_z.shape)
(1704, 6)
(1704,)
누락값을 평균값으로 처리하기
앞서 누락값을 처리하는 다양한 방법에 대해 살펴보았다. 하지만 가끔은 누락값을 평균값으로 처리하는 것이 더 좋을 때가 있다.
- 누락값을 평균값으로 처리하기
1) 다음은 seaborn 라이브러리의 tips 데이터 집합에서 10개의 행 데이터만 가져온 다음 total_bill 열의 값 4개를 임의로 선택하여 누락값으로 바꾼 것이다.
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], 'totall_bill'] = np.NaN
print(tips_10)
total_bill tip sex smoker day time size totall_bill
24 19.82 3.18 Male No Sat Dinner 2 NaN
6 8.77 2.00 Male No Sun Dinner 2 NaN
153 24.55 2.00 Male No Sun Dinner 4 NaN
211 25.89 5.16 Male Yes Sat Dinner 4 NaN
198 13.00 2.00 Female Yes Thur Lunch 2 NaN
176 17.89 2.00 Male Yes Sun Dinner 2 NaN
192 28.44 2.56 Male Yes Thur Lunch 2 NaN
124 12.48 2.52 Female No Thur Lunch 2 NaN
9 14.78 3.23 Male No Sun Dinner 2 NaN
101 15.38 3.00 Female Yes Fri Dinner 2 NaN
2) 그런데 total_bill 열의 누락값을 단순히 total_bill 열의 평균값으로 채우면 안된다. 현재 tips_10의 데이터는 여성보다 남성이 더 많다. 즉, 여성과 남성을 구분하여 total_bill 열의 평균값을 구하지 않으면 여성 데이터가 남성 데이터의 영향을 많이 받아 여성의 데이터가 훼손될 수 있다. 다음은 성별로 그룹화한 다음 각 열의 데이터 수를 구한 것이다. total_bill 열을 살펴보면 남성의 누락값은 3개, 여성의 누락값은 1개라는 것을 알 수 있다.
count_sex = tips_10.groupby('sex').count()
print(count_sex)
total_bill tip smoker day time size totall_bill
sex
Male 7 7 7 7 7 7 0
Female 3 3 3 3 3 3 0
3) 다음은 성별을 구분하여 total_bill 열의 데이터를 받아 평균값을 구하는 함수이다.
def fill_na_mean(x):
avg = x.mean()
return x.fillna(avg)
4) 다음은 성별을 구분한 다음 total_bill 열의 데이터를 fill_na_mean 함수에 전달하여 평균값을 구한 다음 tips_10에 새로운 열로 추가한 것이다. 남성과 여성의 누락값을 고려하여 계산한 평균값으로 잘 채워져 있는 것을 알 수 있다.
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 totall_bill \
24 19.82 3.18 Male No Sat Dinner 2 NaN
6 8.77 2.00 Male No Sun Dinner 2 NaN
153 24.55 2.00 Male No Sun Dinner 4 NaN
211 25.89 5.16 Male Yes Sat Dinner 4 NaN
198 13.00 2.00 Female Yes Thur Lunch 2 NaN
176 17.89 2.00 Male Yes Sun Dinner 2 NaN
192 28.44 2.56 Male Yes Thur Lunch 2 NaN
124 12.48 2.52 Female No Thur Lunch 2 NaN
9 14.78 3.23 Male No Sun Dinner 2 NaN
101 15.38 3.00 Female Yes Fri Dinner 2 NaN
fill_total_bill
24 19.82
6 8.77
153 24.55
211 25.89
198 13.00
176 17.89
192 28.44
124 12.48
9 14.78
101 15.38
출처 : 데이터 분석을 위한 판다스 입문
'판다스 입문' 카테고리의 다른 글
판다스 입문(시계열 데이터-datetime 오브젝트) (0) | 2021.03.26 |
---|---|
판다스 입문(그룹 연산 - 데이터필터링, 그룹 오브젝트) (0) | 2021.03.25 |
판다스 입문(apply 메서드 활용) (0) | 2021.03.22 |
판다스 입문(문자열 처리하기) (0) | 2021.03.22 |
판다스 입문(판다스 자료형) (0) | 2021.03.21 |