🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌

6 분 소요

판다스(Pandas)의 .groupby() 기능은 데이터를 그룹별로 분할하여 독립된 그룹에 대하여 별도로 데이터를 처리(혹은 적용)하거나 그룹별 통계량을 확인하고자 할 때 유용한 함수 입니다.

.groupby()의 동작 원리는 아래 그림과 같습니다.

pandas-groupby

이미지 출처: www.w3resource.com

  • Split 단계: 위에 이미지에서 Split 단계에서 .groupby()에서 정의한 컬럼 조건에 따라 독립된 그룹으로 나누어 줍니다. 예시에서는 ID 값을 기준으로 그룹을 나누었는데, 3개의 sub-group으로 분할된 모습입니다.

  • Apply 단계: 나뉘어진 독립된 그룹별 함수를 적용하는 단계 입니다. 예시에서는 합계(sum)함수를 적용하여 각 그룹별 총계가 합산된 결과를 확인할 수 있습니다.

  • Combine 단계: 최종 단계이며, 각각의 독립된 그룹별로 함수가 적용된 결과를 종합하여 다시 하나의 테이블로 합칩니다.

판다스(Pandas)의 .groupby() 메서드는 앞서 언급한 바와 같이 데이터를 특정 기준으로 그룹화하여 처리할 수 있는 기능 덕분에, 데이터 전처리/분석 시 유용하게 활용할 수 있습니다.

아래는 다양한 활용 사례에 대하여 소개해 드리고자 합니다.

# 모듈 import
import pandas as pd
import seaborn as sns

# 샘플 데이터 로드
df = sns.load_dataset('tips')
df
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
... ... ... ... ... ... ... ...
239 29.03 5.92 Male No Sat Dinner 3
240 27.18 2.00 Female Yes Sat Dinner 2
241 22.67 2.00 Male Yes Sat Dinner 2
242 17.82 1.75 Male No Sat Dinner 2
243 18.78 3.00 Female No Thur Dinner 2

244 rows × 7 columns

그룹별 통계량 확인

  • 데이터 프레임에 .groupby(컬럼) + 통계함수로 그룹별 통계량을 확인할 수 있습니다.

  • 통계 결과는 통계계산이 가능한 수치형(numerical) 컬럼에 대해서만 산출합니다.

# 평균(mean)
df.groupby('sex').mean()
total_bill tip size
sex
Male 20.744076 3.089618 2.630573
Female 18.056897 2.833448 2.459770
# 분산(var)
df.groupby('sex').var()
total_bill tip size
sex
Male 85.497185 2.217424 0.913931
Female 64.147429 1.344428 0.879177

.agg()에 적용할 수 있는 통계함수 문자열 표

함수 내용
count 데이터의 개수
sum 합계
mean 평균
median 중앙값
var, std 분산, 표준편차
min, max 최소, 최대값
unique, nunique 고유값, 고유값 개수
prod
first, last 첫째, 마지막값

.agg()를 활용하여 다중 통계량을 구할 수 있습니다.

# 다중 통계 적용
df.groupby('sex').agg(['mean', 'var'])
total_bill tip size
mean var mean var mean var
sex
Male 20.744076 85.497185 3.089618 2.217424 2.630573 0.913931
Female 18.056897 64.147429 2.833448 1.344428 2.459770 0.879177

.agg()에서 문자열로 지정할 수 있는 함수 목록은 다음과 같습니다.

.agg()로 다중 통계량을 구할 때 컬럼별 적용할 통계 함수를 다르게 적용할 수 있습니다.

# 컬럼별 다른 통계량 산출
df.groupby('sex').agg({'total_bill': 'mean', 
                       'tip': ['sum', 'var'],
                       'size': 'median'
                      })
total_bill tip size
mean sum var median
sex
Male 20.744076 485.07 2.217424 2.0
Female 18.056897 246.51 1.344428 2.0

바로 이전 출력 결과에서는 MultiIndex로 된 컬럼 형태로 출력이 되는데, 이를 평탄화(Flatten) 하여 보기 좋게 만들 수 있습니다.

# MultiIndex 컬럼을 평탄화 하는 함수
def flat_cols(df):
    df.columns = [' / '.join(x) for x in df.columns.to_flat_index()]
    return df

# 컬럼별 다른 통계량 산출
df.groupby('sex').agg({'total_bill': 'mean', 
                       'tip': ['sum', 'var'],
                       'size': 'median'
                      }).pipe(flatten_cols)
total_bill / mean tip / sum tip / var size / median
sex
Male 20.744076 485.07 2.217424 2.0
Female 18.056897 246.51 1.344428 2.0

만약 위의 결과 테이블에서 출력결과의 소수점 자릿수를 지정하고 싶다면, 끝에 .round(소수점 자릿수)를 추가할 수 있습니다.

# .round(2): 소수점 둘째자리까지 반올림하여 결과 출력
df.groupby('sex').agg({'total_bill': 'mean', 
                       'tip': ['sum', 'var'],
                       'size': 'median'
                      }).pipe(flatten_cols).round(2)
total_bill / mean tip / sum tip / var size / median
sex
Male 20.74 485.07 2.22 2.0
Female 18.06 246.51 1.34 2.0

.agg()함수에 사용자 정의 함수(Custom Function)를 적용할 수 있습니다.

  • 단, 사용자 정의 함수는 합산(aggregated)된 결과를 반환해야 합니다.
df.groupby('sex')[['total_bill', 'tip']].agg(lambda x: x.mean() / x.std())
total_bill tip
sex
Male 2.243459 2.074820
Female 2.254517 2.443693

.agg() 적용한 결과에 대하여 reset_index()를 적용하여 왼쪽 Index를 초기화할 수 있습니다.

# 인덱스 초기화 전
display(df.groupby('sex').mean())

# 인덱스 초기화 후
df.groupby('sex').mean().reset_index()
total_bill tip size
sex
Male 20.744076 3.089618 2.630573
Female 18.056897 2.833448 2.459770
sex total_bill tip size
0 Male 20.744076 3.089618 2.630573
1 Female 18.056897 2.833448 2.459770

분리된 group 순회

groupby를 활용한 순회시 첫번째 인자는 key값을, 두번재 인자는 group을 반환합니다.

# sex, smoker 기준으로 그룹한 후 순회하며 출력
for (k1, k2), group in df.groupby(['sex', 'smoker']):
    print((k1, k2))
    # 데이터프레임 출력
    display(group.head())
('Male', 'Yes')
total_bill tip sex smoker day time size
56 38.01 3.00 Male Yes Sat Dinner 4
58 11.24 1.76 Male Yes Sat Dinner 2
60 20.29 3.21 Male Yes Sat Dinner 2
61 13.81 2.00 Male Yes Sat Dinner 2
62 11.02 1.98 Male Yes Sat Dinner 2
('Male', 'No')
total_bill tip sex smoker day time size
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
5 25.29 4.71 Male No Sun Dinner 4
6 8.77 2.00 Male No Sun Dinner 2
('Female', 'Yes')
total_bill tip sex smoker day time size
67 3.07 1.00 Female Yes Sat Dinner 1
72 26.86 3.14 Female Yes Sat Dinner 2
73 25.28 5.00 Female Yes Sat Dinner 2
92 5.75 1.00 Female Yes Fri Dinner 2
93 16.32 4.30 Female Yes Fri Dinner 2
('Female', 'No')
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
11 35.26 5.00 Female No Sun Dinner 4
14 14.83 3.02 Female No Sun Dinner 2
16 10.33 1.67 Female No Sun Dinner 3

다음과 같이 dict로 변환한 후 키 값으로 조회하여 그룹별 데이터프레임(DataFrame)을 조회할 수 있습니다.

# 그룹별 데이터프레임을 생성 후 dict에 저장
output = dict(list(df.groupby(['sex', 'smoker'])))
output.keys()
dict_keys([('Male', 'Yes'), ('Male', 'No'), ('Female', 'Yes'), ('Female', 'No')])
# sex == Male & smoker == Yes 인 그룹 조회
output[('Male', 'Yes')].head()
total_bill tip sex smoker day time size
56 38.01 3.00 Male Yes Sat Dinner 4
58 11.24 1.76 Male Yes Sat Dinner 2
60 20.29 3.21 Male Yes Sat Dinner 2
61 13.81 2.00 Male Yes Sat Dinner 2
62 11.02 1.98 Male Yes Sat Dinner 2

apply()로 그룹별 데이터 전처리

# 샘플 데이터셋 로드
df = sns.load_dataset('titanic')
df.head()
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
0 0 3 male 22.0 1 0 7.2500 S Third man True NaN Southampton no False
1 1 1 female 38.0 1 0 71.2833 C First woman False C Cherbourg yes False
2 1 3 female 26.0 0 0 7.9250 S Third woman False NaN Southampton yes True
3 1 1 female 35.0 1 0 53.1000 S First woman False C Southampton yes False
4 0 3 male 35.0 0 0 8.0500 S Third man True NaN Southampton no True

타아타닉(titanic) 데이터셋을 로드하였습니다.

현재 age 컬럼에는 총 177건의 결측 데이터가 존재합니다.

# age 컬럼의 결측치 조회
df['age'].isnull().sum()
177

나이 컬럼에 대한 결측치를 단순 통계량이나 임의의 값으로 채울 수 있지만, .groupby()를 활용하여 그룹별 통계량으로 채울 수 있습니다.

아래의 예시는 먼저 .groupby('sex')로 성별 그룹으로 나눈 뒤, 나이 컬럼에 대하여 각 그룹의 나이 평균으로 결측치를 채웁니다.

즉, 남자는 남자 나이의 평균값으로 / 여자는 여자 나이의 평균값으로 결측치가 채워집니다.

# 성별 나이의 평균으로 각각 성별 그룹의 나이의 평균으로 값을 채웁니다.
df.groupby('sex')['age'].apply(lambda x: x.fillna(x.mean()))
0      22.000000
1      38.000000
2      26.000000
3      35.000000
4      35.000000
         ...    
886    27.000000
887    19.000000
888    27.915709
889    26.000000
890    32.000000
Name: age, Length: 891, dtype: float64

.apply() 함수에 사용자 정의 함수(Custom Function)를 적용하는 것도 가능합니다.

아래 예시는 상위 5개의 행을 출력하는 함수를 적용한 예시입니다.

def get_top5(x):
    # 나이를 기준으로 정렬하여 상위 5개 행을 반환
    return x.sort_values('age').head()

df.groupby('sex').apply(get_top5)
survived pclass sex age sibsp parch fare embarked class who adult_male deck embark_town alive alone
sex
female 469 1 3 female 0.75 2 1 19.2583 C Third child False NaN Cherbourg yes False
644 1 3 female 0.75 2 1 19.2583 C Third child False NaN Cherbourg yes False
381 1 3 female 1.00 0 2 15.7417 C Third child False NaN Cherbourg yes False
172 1 3 female 1.00 1 1 11.1333 S Third child False NaN Southampton yes False
642 0 3 female 2.00 3 2 27.9000 S Third child False NaN Southampton no False
male 803 1 3 male 0.42 0 1 8.5167 C Third child False NaN Cherbourg yes False
755 1 2 male 0.67 1 1 14.5000 S Second child False NaN Southampton yes False
831 1 2 male 0.83 1 1 18.7500 S Second child False NaN Southampton yes False
78 1 2 male 0.83 0 2 29.0000 S Second child False NaN Southampton yes False
305 1 1 male 0.92 1 2 151.5500 S First child False C Southampton yes False

댓글남기기