-
Chapter 11-1~11-2 데이터 집계, 데이터 변환Do it! 판다스 입문 2022. 6. 25. 17:56
데이터 집계하기 - groupby 메서드
02장에서 갭마인더 데이터 집합으로 각 연도의 평균 수명을 구했던 것을 기억하나요? 수집한 데이터를 바탕으로 평균이나 합 등을 구하여 의미 있는 값을 도출해 내는 것을 '집계'라고 합니다. 데이터를 집계하면 전체 데이터를 요약, 정리하여 볼 수 있기 때문에 데이터 분석이 훨씬 편해지죠. 그러면 groupby 메서드로 평균값을 구하는 과정을 통해 데이터 집계가 무엇인지 알아보겠습니다.
groupby 메서드로 평균값 구하기
1. 먼저 갭마인더 데이터 집합을 불러옵니다.
import pandas as pd
df = pd.read_csv('../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
...
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.3150533. 아직 lifeExp 열의 평균값을 구하지 않았습니다. 다음은 과정 2에서 추출한 1952년의 데이터에서 lifeExp 열의 평균값을 구한 것입니다. 이 과정도 '반영' 작업의 한 부분입니다.
y1952_mean = y1952.lifeExp.mean( )
print(y1952_mean)
49.0576197183098664. 과정 2~3을 반복하여 남은 연도의 평균값을 구하면 비로소 '반영' 작업이 끝납니다.
y1957 = df.loc[df.year == 1957, :]
y1957_mean = y1957.lifeExp.mean( )
print(y1957_mean)
51.50740112676054
...
y2007 = df.loc[df.year == 2007, :]
y2007_mean = y2007.lifeExp.mean( )
print(y2007_mean)
67.007422535211265. 마지막으로 연도별로 계산한 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.007423groupby 메서드와 함께 사용하는 집계 메서드
다음은 집계 메서드를 정리한 표입니다. 이후에 수행할 실습에서 자주 등장하는 메서드이니 미리 읽어보고 넘어가는 것이 좋습니다.
집계 메서드
메서드 설명 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 / n2. 다음은 과정 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: float642개의 인잣값을 받아 처리하는 사용자 함수와 grooupby 메서드
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_value2. 다음은 연도별 평균 수명에서 전체 평균 수명을 뺀 값을 구하는 것입니다. agg 메서드의 첫 번째 인자에 my_mean_diff 함수를 전달하고 두 번째 인자에 전체 평균 수명값을 전달했습니다.
global_mean = df.lifeExp.mean( )
print(global_mean)
59.47443936617174
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
...
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.0730212. 이번에는 집계 메서드를 딕셔너리에 담아 agg 메서드에 전달해 보겠습니다. 딕셔너리의 키로 집계 메서드를 적용할 열 이름을 전달하고 딕셔너리의 값으로 집계 메서드를 전달하면 됩니다.
gdf_dict = df.groupby('year').agg({'lifeExp': 'mean', 'pop': 'median', 'gdpPer-cap': '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
...
2002 65.694923 10372918.5 5319.804524
2007 67.007423 10517531.0 6124.371109데이터 변환
11-1에서는 집계 메서드의 활용법을 알아보았습니다. 이번에는 데이터 변환 메서드에 대해 알아보겠습니다. 데이터 변환 메서드는 데이터와 메서드를 일대일로 대응시켜 계산하기 때문에 데이터의 양이 줄어들지 않습니다. 말 그대로 데이터를 변환하는 데 사용합니다.
표준점수 계산하기
통계 분야에서는 데이터의 평군과 표준편차의 차이를 표준점수라고 부릅니다. 표준점수를 구하면 변환한 데이터의 평균값이 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: float643. my_zscore 함수는 데이터를 표준화할 뿐 집계는 하지 않습니다. 즉, 데이터의 양이 줄어들지 않습니다. 다음은 원본 데이터프레임(df)의 데이터 크기와 변환한 데이터프레임(transform_z)의 데이터 크기를 비교한 것입니다.
print(df.shape)
(1704, 6)
print(transform_z.shape)
(1704, )누락값을 평균값으로 처리하기
06장에서 누락값을 처리하는 다양한 방법에 대해 살펴봤습니다. 하지만 가끔은 누락값을 평균값으로 처리하는 것이 더 좋을 때가 있습니다. 이번에는 누락값을 평균값으로 처리하는 방법을 알아보겠습니다.
누락값을 평균값으로 처리하기
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], '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 Dineer 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 22. 그런데 total_bll 열의 누락값을 단순히 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
sex
Male 4 7 7 7 7 7
Female 2 3 3 3 3 33. 다음은 성별을 구분하여 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 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 Dineer 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출처 : "판다스 입문"
'Do it! 판다스 입문' 카테고리의 다른 글
Chapter 12-1 datetime 오브젝트 (0) 2022.06.26 Chapter 11-3~11-4 데이터 필터링, 그룹 오브젝트 (0) 2022.06.25 Chapter 10 apply 메서드 활용 (0) 2022.06.25 Chapter 09-3~09-4 문자열 포매팅, 정규식으로 문자열 처리에 날개 달기 (0) 2022.06.25 Chapter 09-1~09-2 문자열 다루기, 문자열 메서드 (0) 2022.06.25