🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈 바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의 바로가기 🙌

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

댓글남기기