지난번 포스팅에서는 Python pandas의 GroupBy 집계 메소드와 함수에 대해서 알아보았습니다.
이번 포스팅에서는 Python pandas의 GroupBy 집계를 할 때 grouped.agg() 를 사용하여 다수의 함수를 적용하는 몇 가지 방법을 소개하고자 합니다.
(1) 함수 리스트(List)를 사용하여 다수의 GroupBy 집계 함수를 동일한 칼럼에 적용하기
(2) 칼럼과 함수를 매핑한 Dict를 사용하여 칼럼별로 특정 GroupBy 집계 함수를 적용하기
(3) (이름, 함수)의 튜플 (Tuples of (name, function))을 사용하여 GroupBy 집계 함수에 이름 부여하기
[ Python pandas: GroupBy with multiple functions using lists, Dicts, tuples ]
예제로 사용할 데이터는 UCI Machine Learning Repository에 있는 Abalone data set 입니다. 전복의 둘레, 두께, 높이, 전체 무게, 껍질 무게 등 4,177개의 전복을 측정해 놓은 데이터셋입니다.
[ UCI Machine Learning Repository ] Name Data Type Meas. Description
---- --------- ----- -----------
Sex nominal M, F, and I (infant)
Length continuous mm Longest shell measurement
Diameter continuous mm perpendicular to length
Height continuous mm with meat in shell
Whole weight continuous grams whole abalone
Shucked weight continuous grams weight of meat
Viscera weight continuous grams gut weight (after bleeding)
Shell weight continuous grams after being dried
Rings integer +1.5 gives the age in years
|
UCI machine learning repository 웹사이트로부터 Abalone 데이터셋을 csv파일을 다운로드 받아서 pandas DataFrame로 불러오도록 하겠습니다.
# Importing common libraries import numpy as np import pandas as pd # Import Abalone data set from UCI machine learning repository directly import csv import urllib2 url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data' downloaded_data = urllib2.urlopen(url) abalone = pd.read_csv(downloaded_data, names = ['sex', 'length', 'diameter', 'height', 'whole_weight', 'shucked_weight', 'viscera_weight', 'shell_weight', 'rings'], header = None)
abalone.head() | sex | length | diameter | height | whole_weight | shucked_weight | viscera_weight | shell_weight | rings |
---|
0 | M | 0.455 | 0.365 | 0.095 | 0.5140 | 0.2245 | 0.1010 | 0.150 | 15 |
---|
1 | M | 0.350 | 0.265 | 0.090 | 0.2255 | 0.0995 | 0.0485 | 0.070 | 7 |
---|
2 | F | 0.530 | 0.420 | 0.135 | 0.6770 | 0.2565 | 0.1415 | 0.210 | 9 |
---|
3 | M | 0.440 | 0.365 | 0.125 | 0.5160 | 0.2155 | 0.1140 | 0.155 | 10 |
---|
4 | I | 0.330 | 0.255 | 0.080 | 0.2050 | 0.0895 | 0.0395 | 0.055 | 7 |
---|
|
예제에서 GroupBy 집계 시 그룹을 나누는 기준으로 사용할 용도로 'length' 변수에 대해 중앙값을 기준으로 큰지, 작은지 여부에 따라 'length_cat' 라는 범주형 변수를 하나 더 만들어보겠습니다.
abalone['length_cat'] = np.where(abalone.length > np.median(abalone.length), 'length_long', # True 'length_short') # False
abalone[['length', 'length_cat']][:10] | length | length_cat |
---|
0 | 0.455 | length_short |
---|
1 | 0.350 | length_short |
---|
2 | 0.530 | length_short |
---|
3 | 0.440 | length_short |
---|
4 | 0.330 | length_short |
---|
5 | 0.425 | length_short |
---|
6 | 0.530 | length_short |
---|
7 | 0.545 | length_short |
---|
8 | 0.475 | length_short |
---|
9 | 0.550 | length_long |
---|
|
(1) 함수 리스트(List)를 사용하여 다수의 GroupBy 집계 함수를 동일한 칼럼에 적용하기 |
'sex' ('F', 'I', 'M' 계급), 'length_cat' ('length_short', 'length_long' 계급) 의 두 개의 범주형 변수를 사용하여 GroupBy 집계 시 그룹을 나누는 기준으로 사용하겠으며, 'whole_weight' 연속형 변수에 대해 GroupBy 집계 함수를 적용하여 그룹 집계를 해보겠습니다.
grouped_ww = abalone.groupby(['sex', 'length_cat'])['whole_weight'] grouped_ww <pandas.core.groupby.SeriesGroupBy object at 0x10a7e0290>
|
먼저, 복습을 하는 차원에서 지난번 포스팅에서 소개했던 '단일 함수'를 사용하여 GroupBy 집계하는 두가지 방법, 즉 (1) GroupBy method를 사용하거나 아니면 (2) grouped.agg(함수)를 사용하는 방법을 소개하면 아래와 같습니다. 하나의 집계함수를 적용하면 반환되는 결과는 Series 가 됩니다.
(방법1) GroupBy methods |
(방법2) grouped.agg(function) |
grouped_ww.mean() # Series sex length_cat
F length_long 1.261330
length_short 0.589702
I length_long 0.923215
length_short 0.351234
M length_long 1.255182
length_short 0.538157
Name: whole_weight, dtype: float64
|
grouped_ww.agg('mean') # Series sex length_cat
F length_long 1.261330
length_short 0.589702
I length_long 0.923215
length_short 0.351234
M length_long 1.255182
length_short 0.538157
Name: whole_weight, dtype: float64
|
이제부터 '여러개의 함수'를 적용하여 GroupBy 집계하는 방법을 소개하겠습니다. 먼저, GroupBy 집계하려는 함수들의 문자열 리스트(list)로 grouped.agg() 에 적용하는 방법입니다. 이처럼 여러개의 집계함수를 적용하면 반환되는 결과는 DataFrame이 됩니다.
grouped_ww.agg(['size', 'mean', 'std', 'min', 'max']) # DataFrame | | size | mean | std | min | max |
---|
sex | length_cat | | | | | |
---|
F | length_long | 889 | 1.261330 | 0.329656 | 0.6405 | 2.6570 |
---|
length_short | 418 | 0.589702 | 0.202400 | 0.0800 | 1.3580 |
---|
I | length_long | 188 | 0.923215 | 0.218334 | 0.5585 | 2.0495 |
---|
length_short | 1154 | 0.351234 | 0.204237 | 0.0020 | 1.0835 |
---|
M | length_long | 966 | 1.255182 | 0.354682 | 0.5990 | 2.8255 |
---|
length_short | 562 | 0.538157 | 0.246498 | 0.0155 | 1.2825 |
---|
|
function_list = ['size', 'mean', 'std', 'min', 'max'] grouped_ww.agg(function_list) | | size | mean | std | min | max |
---|
sex | length_cat | | | | | |
---|
F | length_long | 889 | 1.261330 | 0.329656 | 0.6405 | 2.6570 |
---|
length_short | 418 | 0.589702 | 0.202400 | 0.0800 | 1.3580 |
---|
I | length_long | 188 | 0.923215 | 0.218334 | 0.5585 | 2.0495 |
---|
length_short | 1154 | 0.351234 | 0.204237 | 0.0020 | 1.0835 |
---|
M | length_long | 966 | 1.255182 | 0.354682 | 0.5990 | 2.8255 |
---|
length_short | 562 | 0.538157 | 0.246498 | 0.0155 | 1.2825 |
---|
|
물론, "다수의 칼럼"에 대해서 여러개의 함수를 적용하는 것도 가능합니다. 아래의 예에서는 'whole_weight', 'shell_weight'의 두 개의 칼럼에 대해서 GroupBy 집계 함수 리스트(list)를 적용하여 집계하여 보았습니다.
grouped = abalone.groupby(['sex', 'length_cat']) function_list = ['size', 'mean', 'std'] groupby_result = grouped['whole_weight', 'shell_weight'].agg(function_list) groupby_result | | whole_weight | shell_weight |
---|
| | size | mean | std | size | mean | std |
---|
sex | length_cat | | | | | | |
---|
F | length_long | 889 | 1.261330 | 0.329656 | 889 | 0.360013 | 0.104014 |
---|
length_short | 418 | 0.589702 | 0.202400 | 418 | 0.178650 | 0.063085 |
---|
I | length_long | 188 | 0.923215 | 0.218334 | 188 | 0.273247 | 0.064607 |
---|
length_short | 1154 | 0.351234 | 0.204237 | 1154 | 0.104549 | 0.061003 |
---|
M | length_long | 966 | 1.255182 | 0.354682 | 966 | 0.351683 | 0.102636 |
---|
length_short | 562 | 0.538157 | 0.246498 | 562 | 0.162141 | 0.075629 |
---|
|
GroupBy 집계 결과가 pandas DataFrame으로 반환된다고 하였으므로, DataFrame에서 사용하는 Indexing 기법을 그대로 사용할 수 있습니다. 예를 들어, 칼럼을 기준으로 집계 결과 데이터프레임인 groupby_result 로 부터 'shell_weight' 변수에 대한 결과만 Indexing 해보겠습니다.
groupby_result['shell_weight'] | | size | mean | std |
---|
sex | length_cat | | | |
---|
F | length_long | 889 | 0.360013 | 0.104014 |
---|
length_short | 418 | 0.178650 | 0.063085 |
---|
I | length_long | 188 | 0.273247 | 0.064607 |
---|
length_short | 1154 | 0.104549 | 0.061003 |
---|
M | length_long | 966 | 0.351683 | 0.102636 |
---|
length_short | 562 | 0.162141 | 0.075629 |
---|
groupby_result['shell_weight'][['size', 'mean']] | | size | mean |
---|
sex | length_cat | | |
---|
F | length_long | 889 | 0.360013 |
---|
length_short | 418 | 0.178650 |
---|
I | length_long | 188 | 0.273247 |
---|
length_short | 1154 | 0.104549 |
---|
M | length_long | 966 | 0.351683 |
---|
length_short | 562 | 0.162141 |
---|
|
GroupBy 집계 결과 데이터프레임으로부터 row를 기준으로 Indexing을 할 수도 있습니다. DataFrame에서 row 기준으로 indexing 할 때 DataFrame.loc[] 를 사용하는 것과 동일합니다.
groupby_result.loc['M'] | whole_weight | shell_weight |
---|
| size | mean | std | size | mean | std |
---|
length_cat | | | | | | |
---|
length_long | 966 | 1.255182 | 0.354682 | 966 | 0.351683 | 0.102636 |
---|
length_short | 562 | 0.538157 | 0.246498 | 562 | 0.162141 | 0.075629 |
---|
groupby_result.loc['M', 'shell_weight'] | size | mean | std |
---|
length_cat | | | |
---|
length_long | 966 | 0.351683 | 0.102636 |
---|
length_short | 562 | 0.162141 | 0.075629 |
---|
|
(2) 칼럼과 함수를 매핑한 Dict를 사용하여 칼럼별로 특정 GroupBy 집계 함수를 적용하기 |
먼저, 범위(range)와 IQR(Inter-Quartile Range)를 구하는 사용자 정의 함수를 정의한 후에 grouped.agg() 에 적용해보겠습니다.
def range_func(x): max_val = np.max(x) min_val = np.min(x) range_val = max_val - min_val return range_val
def iqr_func(x): q3, q1 = np.percentile(x, [75, 25]) iqr = q3 - q1 return iqr |
이제 Dicts를 사용하여 'whole_weight' 칼럼에는 size(), mean(), std() 메소드를 매핑하여 GroupBy 집계에 적용하고, 'shell_weight' 칼럼에는 range_func, iqr_func 사용자 정의 함수를 매핑하여 GroupBy 집계에 적용해보겠습니다.
size(), mean(), std() 등의 메소드는 문자열(string)로 grouped.agg() 안에 넣어주어야 해서 작은따옴표('method_name')로 감싸주었으며, 사용자 정의 함수(UDF)는 작은따옴표 없이 그냥 써주면 됩니다.
grouped.agg({'whole_weight': ['size', 'mean', 'std'], # put method's name as a string 'shell_weight': [range_func, iqr_func]}) # UDF name | | whole_weight | shell_weight |
---|
| | size | mean | std | range_func | iqr_func |
---|
sex | length_cat | | | | | |
---|
F | length_long | 889 | 1.261330 | 0.329656 | 0.850 | 0.127000 |
---|
length_short | 418 | 0.589702 | 0.202400 | 0.378 | 0.080500 |
---|
I | length_long | 188 | 0.923215 | 0.218334 | 0.485 | 0.067875 |
---|
length_short | 1154 | 0.351234 | 0.204237 | 0.349 | 0.092750 |
---|
M | length_long | 966 | 1.255182 | 0.354682 | 0.776 | 0.124000 |
---|
length_short | 562 | 0.538157 | 0.246498 | 0.375 | 0.102750 |
---|
|
(3) (이름, 함수)의 튜플 (Tuples of (name, function))을 사용하여 GroupBy 집계 함수에 이름 부여하기 |
위의 (2)번에서 Dicts를 사용하여 shell_weight 변수에 대해 range_func, iqr_func 사용자 정의 함수를 적용하여 GroupBy 집계를 하였는데요, 집계 결과로 반환된 데이터프레임의 변수 이름이 그대로 'range_func', 'iqr_func' 여서 왠지 좀 마음에 들지 않군요. 이럴 때 (이름, 함수) 의 튜플 (Tuples of (name, function))을 사용하여 함수에 특정 이름을 부여할 수 있습니다.
아래 예제에서는 알아보기에 쉽도록 'range_func'는 'Range'라는 이름으로, 'iqr_func'는 'Inter-Quartile_Range'라는 이름을 부여하여 변경을 해보겠습니다.
# (name, function) tuples grouped.agg({'whole_weight': ['size', 'mean', 'std'], 'shell_weight': [('Range', range_func), # (name, function) tuple ('Inter-Quartile_Range', iqr_func)]}) # (name, function) tuple | | whole_weight | shell_weight |
---|
| | size | mean | std | Range | Inter-Quartile_Range |
---|
sex | length_cat | | | | | |
---|
F | length_long | 889 | 1.261330 | 0.329656 | 0.850 | 0.127000 |
---|
length_short | 418 | 0.589702 | 0.202400 | 0.378 | 0.080500 |
---|
I | length_long | 188 | 0.923215 | 0.218334 | 0.485 | 0.067875 |
---|
length_short | 1154 | 0.351234 | 0.204237 | 0.349 | 0.092750 |
---|
M | length_long | 966 | 1.255182 | 0.354682 | 0.776 | 0.124000 |
---|
length_short | 562 | 0.538157 | 0.246498 | 0.375 | 0.102750 |
---|
|
많은 도움이 되었기를 바랍니다.