안녕하세요! '자주 쓰는 명령어로 배우는 Polars' 시리즈의 네 번째 글입니다. 지난 글에서는 데이터 필터링과 정렬에 대해 알아보았는데요. 오늘은 데이터 분석에서 매우 중요한 그룹화(Grouping)와 집계(Aggregation) 기능에 대해 알아보도록 하겠습니다.
데이터를 그룹화하고 집계하는 과정을 통해 의미있는 인사이트를 얻을 수 있습니다. 예를들어, '지역별 매출 합계', '연령대별 고객 수' 등의 정보를 파악할 수 있습니다. Polars는 이러한 작업을 매우 효율적이고 직관적으로 수행할 수 있도록 다양한 기능을 제공하고 있습니다.
오늘도 포켓몬 데이터 예시를 통해 그룹화와 집계에 대해 자세히 살펴보도록 하겠습니다. 😊
💡 여기서 사용하는 Polars 버전은 1.5입니다. polars 버전이 1 미만이면 예시 코드가 제대로 동작하지 않을 수 있습니다.
먼저 필요한 라이브러리를 임포트하고 예제 데이터를 불러와 보겠습니다. (아직 설치를 안 하신 분들이 있다면 지난 글을 참고해서 먼저 설치해 주세요 🛠️)
예제 데이터는 아래 파일을 다운받아주세요.
import polars as pl
# 데이터 불러오기
df = pl.read_csv("Pokemon.csv")
1. 기본 그룹화 작업 🎯
단일 컬럼 그룹화
가장 기본적인 그룹화는 group_by
메서드를 사용합니다. 하나의 컬럼을 기준으로 데이터를 그룹화하고, 각 그룹에 대해 집계 함수를 적용할 수 있습니다.
# 타입별 포켓몬 수 계산
type_counts = df.group_by("Type 1").agg(
pl.len().alias("count")
)
print("타입별 포켓몬 수:")
print(type_counts.sort("count", descending=True))
실행결과
다중 컬럼 그룹화
하나의 기준으로는 부족할 때 여러 기준으로 한 번에 그룹화 할 수 있어요. 이는 더 세분화된 분석이 필요할 때 유용합니다.
예를들어, '지역별 + 상품별 매출분석', '연령대별 + 성별 고객 통계' 등에 활용할 수 있어요.
# 세대와 타입별 평균 능력치 계산
gen_type_stats = df.group_by(["Generation", "Type 1"]).agg([
pl.col("HP").mean().alias("avg_HP"),
pl.col("Attack").mean().alias("avg_Attack"),
])
print("\n세대와 타입별 평균 능력치:")
print(gen_type_stats.head())
실행결과
다만 그룹이 기하급수적으로 늘어나거나 데이터가 너무 잘게 쪼개질 수 있으니 주의가 필요합니다!
2. 다양한 집계 함수 활용하기 📊
기본 집계 함수
Polars는 다양한 내장 집계 함수를 제공합니다. 이러한 함수들은 그룹화된 데이터에 대해 통계적 계산을 수행합니다.
type_stats = df.group_by("Type 1").agg([
pl.len().alias("count"),
pl.col("HP").mean().alias("avg_HP"),
pl.col("Attack").sum().alias("total_Attack"),
pl.col("Defense").std().alias("std_Defense"),
pl.col("Speed").max().alias("max_Speed"),
])
print("타입별 상세 통계:")
print(type_stats.head())
실행결과
이 외에도 polars는 아래와 같은 집계함수들을 제공합니다.
수치형 데이터용:
mean()
: 평균값 (가장 기본적인 통계)sum()
: 합계 (전체 규모 파악할 때)std()
: 표준편차 (데이터 분포 확인)var()
: 분산min()
,max()
: 최소/최대값median()
: 중앙값 (이상치에 강함)quantile()
: 분위수
카운트 계열:
count()
: 개수 세기n_unique()
: 고유값 개수first()
,last()
: 첫/마지막 값value_counts()
: 값별 빈도수
조건부 집계
단순 집계를 넘어서 조건을 걸어서 더 세밀한 분석이 가능합니다. '특정 조건 만족하는 케이스만 분석할 때', 'A/B 테스트 결과 분석', '이상치 비율 계산' 등을 위해 조건부 집계를 활용할 수 있어요.
# 타입별로 HP가 100 이상인 포켓몬의 수와 비율 계산
type_hp_stats = df.group_by("Type 1").agg([
pl.col("HP").count().alias("total"),
(pl.col("HP") >= 100).sum().alias("high_hp_count"),
((pl.col("HP") >= 100).sum() / pl.col("HP").count() * 100).alias("high_hp_ratio")
])
print("타입별 고능력치 포켓몬 비율:")
print(type_hp_stats.sort("high_hp_ratio", descending=True))
실행결과
3. 윈도우 함수와 고급 집계 🚀
윈도우 함수 활용하기
윈도우 함수는 그룹 내에서 행별로 계산을 수행하는 강력한 기능입니다. '매출 순위 매기기', '전월 대비 증감률'등을 계산하는데 활용할 수 있어요.
# 타입별 순위 매기기
ranked_pokemon = df.with_columns([
pl.col("Attack").rank()
.over("Type 1")
.alias("attack_rank_in_type")
])
print("\n타입 내 공격력 순위:")
print(ranked_pokemon.select(["Name", "Type 1", "Attack", "attack_rank_in_type"]).head())
실행결과
복수 컬럼 기반 집계
여러 컬럼을 동시에 집계할 수 있습니다:
# 타입별 전체 능력치 통계
type_stats_all = df.group_by("Type 1").agg(
[
pl.col(["HP", "Attack", "Defense", "Speed"]).mean().name.suffix("_avg"),
pl.col(["HP", "Attack", "Defense", "Speed"]).std().name.suffix("_std"),
]
)
print("\n타입별 종합 능력치 통계:")
print(type_stats_all.head())
실행결과
4. Polars 그룹화/집계 팁 💪
마지막으로 큰 데이터셋을 다룰 때 활용할 수 있는 팁을 알려드리겠습니다.
- 성능 최적화
- 그룹화 전에 필요한 컬럼만 선택하면 메모리 사용량과 처리 속도를 개선할 수 있습니다.
- 큰 데이터셋의 경우
lazy
평가를 활용하면 좋습니다.
# 필요한 컬럼만 선택하여 그룹화
efficient_group_by = (
df
.select(["Type 1", "HP", "Attack"]) # 필요한 컬럼만 선택
.group_by("Type 1")
.agg([
pl.col("HP").mean(),
pl.col("Attack").mean()
])
)
print(efficient_group_by)
실행결과
# Lazy 평가 활용
lazy_group_by = (
df.lazy()
.group_by("Type 1")
.agg([
pl.col("HP").mean(),
pl.col("Attack").mean()
])
.collect()
)
print(lazy_group_by)
- 가독성 향상
- 복잡한 집계 로직은 별도의 함수로 분리하면 코드 관리가 쉬워집니다.
- 결과 컬럼명에
alias
를 적절히 사용하면 결과를 이해하기 쉬워집니다. - 체이닝 방식으로 코드를 작성하면 데이터 처리 흐름을 파악하기 쉽습니다.
- 순서가 중요한 경우 🚨
- 기본적으로
group_by
는 결과의 순서를 보장하지 않습니다. - 원본 데이터의 순서를 유지하고 싶다면
maintain_order=True
옵션을 써야 합니다. - 단, 이 옵션을 쓰면 성능이 조금 떨어질 수 있어요
- 기본적으로
# 기본 group_by (순서 보장 안됨)
basic_group = df.group_by("Type 1").agg([
pl.col("HP").mean().alias("avg_HP")
])
print("기본 그룹화:")
print(basic_group.head())
# 순서 유지하면서 group_by
ordered_group = df.group_by("Type 1", maintain_order=True).agg([
pl.col("HP").mean().alias("avg_HP")
])
print("\n순서 유지한 그룹화:")
print(ordered_group.head())
실행결과
이번 글에서는 Polars의 그룹화와 집계 기능에 대해 자세히 알아보았습니다. 다음 글에서는 데이터 병합과 재구조화에 대해 알아보도록 하겠습니다. 긴 글 읽어주셔서 감사합니다! 🙇♂️
📚 참고자료
'개발 > 파이썬' 카테고리의 다른 글
데이터 결합과 재구조화 🔄 - 자주 쓰는 명령어로 배우는 Polars #5 (1) | 2024.12.08 |
---|---|
Polars로 데이터 필터링 및 정렬하기 - 자주 쓰는 명령어로 배우는 Polars #3 (0) | 2024.11.09 |
Polars로 데이터 불러오기 및 조작하기 - 자주 쓰는 명령어로 배우는 Polars #2 (1) | 2024.10.26 |
Polars 시작하기 (소개 및 설치) - 자주쓰는 명령어로 배우는 Polars #1 (0) | 2024.10.12 |
poetry와 github actions를 활용한 파이썬 라이브러리 배포 자동화 (2) | 2024.04.13 |