BioPlayground

🧬
목록으로

groupby — Split-Apply-Combine 패턴

pandas groupby의 작동 원리를 Split-Apply-Combine으로 이해하고, 실전 집계 패턴을 배웁니다.

중급
|
10
|
검증 완료 (2026-07)
groupbySplit-Apply-Combine집계그룹별 연산pandas
진행률0/8 (0%)

groupby — Split-Apply-Combine 패턴

이 토픽을 마치면

pandas groupby()의 내부 동작을 Split-Apply-Combine 패턴으로 설명할 수 있고, 실무에서 자주 쓰는 그룹별 집계, 변환, 필터링 패턴을 익힙니다.


왜 그룹별 연산이 필요한가

"전체 평균"은 df["score"].mean()으로 한 줄입니다. 하지만 실무 질문은 대부분 이런 형태입니다:

  • 부서별 평균 연봉은?
  • 월별 매출 합계는?
  • 카테고리별 가장 많이 팔린 상품은?

"~별"이 붙는 순간, 그룹별 연산이 필요합니다. 이것이 groupby()의 역할입니다.


Split-Apply-Combine 패턴

Hadley Wickham이 2011년에 명명한 이 패턴은, 데이터 분석에서 가장 자주 반복되는 사고 구조입니다.

text
원본 데이터
┌────────┬──────┬───────┐
│ dept   │ name │ score │
├────────┼──────┼───────┤
│ Sales  │ Alice│   85  │
│ Sales  │ Bob  │   90  │
│ Dev    │ Carol│   95  │
│ Dev    │ Dave │   88  │
│ HR     │ Eve  │   78  │
└────────┴──────┴───────┘

1. SPLIT — 부서별로 나누기
   Sales: [85, 90]
   Dev:   [95, 88]
   HR:    [78]

2. APPLY — 각 그룹에 함수 적용 (예: 평균)
   Sales: 87.5
   Dev:   91.5
   HR:    78.0

3. COMBINE — 결과를 하나로 합치기
   ┌────────┬───────┐
   │ dept   │ score │
   ├────────┼───────┤
   │ Dev    │  91.5 │
   │ HR     │  78.0 │
   │ Sales  │  87.5 │
   └────────┴───────┘

pandas에서는 이 세 단계가 한 줄입니다:

python
df.groupby("dept")["score"].mean()

기본 사용법

python
import pandas as pd
df = pd.DataFrame({
"dept": ["Sales", "Sales", "Dev", "Dev", "HR"],
"name": ["Alice", "Bob", "Carol", "Dave", "Eve"],
"score": [85, 90, 95, 88, 78],
"salary": [50000, 52000, 70000, 68000, 45000]
})
# 부서별 점수 평균
df.groupby("dept")["score"].mean()
# dept
# Dev 91.5
# HR 78.0
# Sales 87.5
# 부서별 급여 합계
df.groupby("dept")["salary"].sum()
# dept
# Dev 138000
# HR 45000
# Sales 102000

GroupBy 객체란

python
grouped = df.groupby("dept")
print(type(grouped)) # <class 'pandas.core.groupby.DataFrameGroupBy'>

groupby()는 즉시 계산하지 않습니다. 지연 연산(lazy evaluation) — 어떤 함수(mean, sum 등)를 적용할 때 비로소 계산이 실행됩니다. 이것은 대용량 데이터에서 불필요한 연산을 방지합니다.

그룹 내용 확인

python
for name, group in df.groupby("dept"):
print(f"\n--- {name} ---")
print(group)
# --- Dev ---
# dept name score salary
# 2 Dev Carol 95 70000
# 3 Dev Dave 88 68000
# --- HR ---
# ...

여러 집계 함수

하나의 컬럼, 여러 함수

python
df.groupby("dept")["score"].agg(["mean", "min", "max", "count"])
# mean min max count
# dept
# Dev 91.5 88 95 2
# HR 78.0 78 78 1
# Sales 87.5 85 90 2

컬럼별 다른 함수

python
df.groupby("dept").agg(
avg_score=("score", "mean"),
total_salary=("salary", "sum"),
headcount=("name", "count")
)
# avg_score total_salary headcount
# dept
# Dev 91.5 138000 2
# HR 78.0 45000 1
# Sales 87.5 102000 2

Named aggregation(컬럼명=("원래컬럼", "함수"))은 결과의 컬럼 이름까지 한 번에 지정할 수 있어서 가장 깔끔한 방식입니다.


여러 컬럼으로 그룹화

python
sales = pd.DataFrame({
"region": ["East", "East", "West", "West", "East", "West"],
"category": ["A", "B", "A", "B", "A", "A"],
"revenue": [100, 200, 150, 250, 120, 180]
})
sales.groupby(["region", "category"])["revenue"].sum()
# region category
# East A 220
# B 200
# West A 330
# B 250

리스트로 여러 컬럼을 넘기면 계층적 인덱스(MultiIndex) 가 생깁니다.

python
# MultiIndex를 일반 컬럼으로
result = sales.groupby(["region", "category"])["revenue"].sum().reset_index()
# region category revenue
# 0 East A 220
# 1 East B 200
# 2 West A 330
# 3 West B 250

reset_index()는 그룹 키를 인덱스에서 일반 컬럼으로 되돌립니다. 후속 작업(시각화, 저장 등)에서는 이 형태가 편합니다.


transform — 그룹별 변환

agg()는 그룹별로 하나의 값을 반환합니다 (5행 → 3행). transform()은 원본과 같은 크기의 결과를 반환합니다 (5행 → 5행).

python
# 부서별 평균을 원본 데이터에 붙이기
df["dept_avg"] = df.groupby("dept")["score"].transform("mean")
print(df)
# dept name score salary dept_avg
# 0 Sales Alice 85 50000 87.5
# 1 Sales Bob 90 52000 87.5
# 2 Dev Carol 95 70000 91.5
# 3 Dev Dave 88 68000 91.5
# 4 HR Eve 78 45000 78.0

각 행에 자기 부서의 평균이 붙습니다. 이것으로 부서 평균 대비 점수를 계산할 수 있습니다:

python
df["vs_avg"] = df["score"] - df["dept_avg"]
# Alice: 85 - 87.5 = -2.5 (부서 평균 이하)
# Carol: 95 - 91.5 = +3.5 (부서 평균 이상)

filter — 그룹별 필터링

조건을 만족하는 그룹 전체를 남기거나 제거합니다.

python
# 인원이 2명 이상인 부서만
df.groupby("dept").filter(lambda g: len(g) >= 2)
# dept name score salary
# 0 Sales Alice 85 50000
# 1 Sales Bob 90 52000
# 2 Dev Carol 95 70000
# 3 Dev Dave 88 68000

HR(1명)이 통째로 제거되었습니다. 개별 행이 아니라 그룹 단위로 필터링하는 것이 filter()의 핵심입니다.


실전 패턴 — 월별 매출 분석

python
orders = pd.DataFrame({
"date": pd.to_datetime([
"2026-01-05", "2026-01-15", "2026-02-03",
"2026-02-20", "2026-03-10", "2026-03-25"
]),
"product": ["Widget", "Gadget", "Widget", "Gadget", "Widget", "Widget"],
"amount": [1200, 800, 1500, 900, 1100, 1300]
})
# 월별 매출 합계
monthly = orders.groupby(orders["date"].dt.to_period("M"))["amount"].sum()
print(monthly)
# date
# 2026-01 2000
# 2026-02 2400
# 2026-03 2400
# 상품별 월 평균 매출
orders.groupby("product").agg(
total=("amount", "sum"),
avg=("amount", "mean"),
count=("amount", "count")
)
# total avg count
# Gadget 1700 850.0 2
# Widget 5100 1275.0 4

날짜 데이터에서 .dt.to_period("M")으로 월 단위 그룹을 만드는 것은 매우 자주 쓰는 패턴입니다.


핵심 정리

메서드동작결과 크기
agg()그룹별 집계 (sum, mean, count...)그룹 수만큼 (줄어듦)
transform()그룹별 변환 (원본 크기 유지)원본과 동일
filter()조건 만족하는 그룹 전체 유지원본 이하

groupby()는 pandas에서 가장 강력하면서도 가장 자주 쓰이는 기능입니다. Split-Apply-Combine 패턴을 머릿속에 그릴 수 있으면, 복잡한 집계도 "어떤 컬럼으로 나누고(Split), 무슨 함수를 적용하고(Apply), 결과를 어떤 형태로 합칠지(Combine)" 세 단계로 자연스럽게 분해할 수 있습니다.