Pandas GroupBy: Aggregation·Transform·Apply 완벽 가이드
Updated on
split-apply-combine 패턴은 Pandas의 핵심입니다. 하지만 agg, transform, apply 선택이나 정렬, 누락된 그룹, SettingWithCopy 때문에 자주 막힙니다.
PAS 관점:
- 문제: 그룹 계산이 불투명하고 느리게 느껴지며, 필요한 것은 합계나 행 단위 비율뿐인 경우가 많다.
- 심화:
apply남용이나as_index=False누락은 예상치 못한 형태, 중복 컬럼, 느린 코드로 이어진다. - 해결:
agg로 요약,transform으로 행 단위 피처,apply는 꼭 필요할 때만. 정렬과 누락 그룹 옵션을 명시적으로 설정한다.
언제 어떤 메서드를 쓸까 (빠른 표)
| 메서드 | 반환 형태 | 적합한 용도 | 예시 |
|---|---|---|---|
agg / aggregate | 그룹당 1행 | 요약, 여러 메트릭 | df.groupby("team").agg(avg_score=("score","mean")) |
transform | 입력과 동일한 길이 | 그룹 기반 행 피처 | df["z"] = df.groupby("team")["score"].transform("zscore") |
apply | 유연 | agg/transform으로 안 되는 복잡 처리 | df.groupby("team").apply(custom_fn) |
예제 데이터
import pandas as pd
data = {
"team": ["A", "A", "B", "B", "B", "C"],
"player": ["x1", "x2", "y1", "y2", "y3", "z1"],
"score": [9, 7, 8, 6, 10, 3],
"minutes": [30, 25, 28, 32, 20, 15],
}
df = pd.DataFrame(data)집계 패턴 (agg)
summary = (
df.groupby("team", as_index=False)
.agg(
avg_score=("score", "mean"),
max_score=("score", "max"),
minutes_played=("minutes", "sum"),
)
)- Named aggregation으로 컬럼명을 깔끔하게 유지.
as_index=False는team을 컬럼으로 유지(머지/시각화 용이).sort=False로 입력 순서를 보존.
한 컬럼에 여러 집계
df.groupby("team", as_index=False).agg(
score_mean=("score", "mean"),
score_std=("score", "std"),
score_count=("score", "size"),
)행 단위 피처 (transform)
transform은 행 수를 유지하며 그룹 메트릭을 되돌려준다.
df["score_pct_of_team"] = (
df["score"] / df.groupby("team")["score"].transform("sum")
)팀별 Z-스코어:
df["score_z"] = (
df.groupby("team")["score"]
.transform(lambda s: (s - s.mean()) / s.std(ddof=0))
)transform 활용 사례:
- 그룹 합계 대비 비율
- 정규화 점수
- 그룹 플래그(
rank,cumcount등)
apply가 필요한 경우
apply는 유연하지만 느리다. agg/transform이 불가할 때만.
def top_n(group, n=2):
return group.nlargest(n, "score")
top_players = df.groupby("team").apply(top_n, n=1).reset_index(drop=True)apply를 쓸 때:
- 그룹마다 반환 행 수가 달라야 할 때
- 벡터화할 수 없는 파이썬 로직이 필요할 때
누락 그룹과 정렬 제어
result = (
df.groupby("team", dropna=False, sort=False)
.agg(avg_score=("score", "mean"))
)dropna=False로NaN그룹 라벨을 유지.sort=False로 재정렬을 방지(순서에 의미가 있을 때).
여러 키와 인덱스에서 GroupBy
multi = (
df.groupby(["team", "player"], as_index=False)
.agg(score_mean=("score", "mean"))
)인덱스 레벨로 그룹화:
df2 = df.set_index(["team", "player"])
df2.groupby(level="team")["score"].mean()흔한 함정
| 문제 | 해결 |
|---|---|
예상치 못한 형태 (Series vs DataFrame) | as_index=False 설정 또는 이후 reset_index() |
| 머지 후 컬럼명 충돌 | Named aggregation으로 컬럼명 지정 |
apply로 느린 속도 | agg/transform 또는 벡터화 메서드로 대체 |
| 출력에서 카테고리가 사라짐 | observed=False 유지, dropna=False로 NaN 보존 |
내보내기·시각화 팁
- 집계 후 정렬:
summary.sort_values("avg_score", ascending=False). - 차트용 피벗:
summary.pivot(index="team", values="avg_score").
관련 가이드
핵심 정리
- 요약은
agg, 행 피처는transform,apply는 최소화. as_index와reset_index로 형태를 제어.sort,dropna로 순서와 누락 라벨을 관리.- 벡터화 메서드를 우선해 파이프라인을 빠르게 유지.