안녕하세요, 데이터 처리와 분석을 위한 라이브러리 polars
에 대해 들어보셨나요? Polars
는 Pandas
보다 빠른 성능과 직관적인 API로 최근 데이터 엔지니어와 사이언티스트 사이에서 큰 주목을 받고 있습니다.
이전에 '자주 쓰는 명령어로 배우는 Pandas 시리즈'를 연재한 적이 있는데요, 이번에는 그와 유사하게 '자주 쓰는 명령어로 배우는 Polars 시리즈'를 작성해 보려고 합니다.
이번 포스트에서는 Polars의 주요 특징과 설치 방법, 그리고 기본적인 사용법에 대해 알아보겠습니다.
🤔 Pandas vs Polars: 무엇이 다를까?
Pandas와 Polars는 모두 강력한 데이터 조작과 처리를 위한 라이브러리지만, 몇 가지 중요한 차이점이 있습니다:
- 성능: Polars는 Rust로 작성되어 Pandas보다 일반적으로 더 빠릅니다. 특히 대규모 데이터셋에서 그 차이가 두드러집니다. (참고)
메모리 효율성: Polars는 메모리를 더 효율적으로 사용합니다. 반면 Pandas는 때때로 예상보다 많은 메모리를 사용합니다.
API 스타일: Pandas는 메서드 체이닝과 속성 접근을 혼용하는 반면, Polars는 일관된 메서드 체이닝 스타일을 사용합니다.
# Pandas 스타일 df['new_col'] = df['col1'] + df['col2'] # Polars 스타일df = df.with_columns(pl.col("col1") + pl.col("col2").alias("new_col"))
Lazy Evaluation: Polars는 기본적으로 Lazy Evaluation을 지원하여 쿼리 최적화가 용이합니다. Pandas는 이에 비해 즉시 평가(Eager Evaluation) 방식을 주로 사용합니다. Lazy Evaluation을 사용하면 아래와 같은 이점을 얻을 수 있습니다.
- 쿼리 최적화: Polars는 전체 쿼리를 분석하여 최적의 실행 계획을 세웁니다.
- 메모리 효율성: 중간 결과를 메모리에 저장하지 않아 메모리 사용량을 줄일 수 있습니다.
- 병렬 처리: 가능한 경우 작업을 자동으로 병렬화합니다.
lazy_df = pl.LazyFrame( { "A": [1, 2, 3, 4, 5], "B": [10, 20, 30, 40, 50], "C": [100, 200, 300, 400, 500] } ) # filter와 select 연산은 즉시 실행되지 않고, collect() 메서드가 호출될 때까지 지연 result = ( lazy_df .filter(pl.col("A") > 2) .select(pl.col("B"), pl.col("C") * 2) .collect() # 이 시점에 실제 계산이 수행됩니다. ) print(result)
병렬 처리: Polars는 기본적으로 멀티코어를 활용한 병렬 처리를 지원합니다. Pandas는 일부 작업에서만 제한적으로 병렬 처리를 지원합니다.
학습 곡선: Pandas는 오랫동안 사용되어 왔고 풍부한 문서와 커뮤니티 지원이 있어 초보자가 시작하기 쉽습니다. Polars는 상대적으로 새로운 라이브러리이지만, 직관적인 API로 빠르게 익힐 수 있습니다.
생태계: Pandas는 방대한 생태계를 가지고 있어 다양한 외부 라이브러리와의 호환성이 뛰어납니다. Polars는 아직 생태계가 성장 중이지만, 빠르게 발전하고 있습니다.
위의 특성을 고려해서 프로젝트의 요구사항과 개인의 선호도에 따라 적절한 도구를 선택하는 것이 중요합니다. Pandas에 익숙하다면 Polars를 학습하는 것도 그리 어렵지 않을 거예요! 이번 블로그 시리즈를 통해 차근차근 공부해봅시다! 😊
🚀 Polars 시작하기
Polars 설치하기
pip install polars
pip
뿐만이 아니라 poetry
등의 패키지 매니저 도구를 사용할 수 있습니다. 자세한 내용은 ‘vscode에서 파이썬 개발환경 세팅하기’를 확인해주세요!
기본 예제
자, 이제 Polars를 활용한 몇 가지 기본 예제를 살펴보겠습니다. 다음 시리즈부터 각 기능을 자세히 다룰 예정이니, 지금은 "아, Polars로 이런 것들을 할 수 있구나!"라는 느낌으로 가볍게 봐주세요! 😊
import polars as pl
df = pl.DataFrame(
{
"name": ["피카츄", "파이리", "이상해씨", "꼬부기", "리자몽", "거북왕"],
"type": ["전기", "불꽃", "풀", "물", "불꽃", "물"],
"weight": [6.0, 8.5, 6.9, 9.0, 90.5, 85.5], # (kg)
"height": [0.4, 0.6, 0.7, 0.5, 1.5, 1.6], # (m)
}
)
print(df)
실행 결과:
shape: (6, 4)
┌──────────┬──────┬────────┬────────┐
│ name ┆ type ┆ weight ┆ height │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 │
╞══════════╪══════╪════════╪════════╡
│ 피카츄 ┆ 전기 ┆ 6.0 ┆ 0.4 │
│ 파이리 ┆ 불꽃 ┆ 8.5 ┆ 0.6 │
│ 이상해씨 ┆ 풀 ┆ 6.9 ┆ 0.7 │
│ 꼬부기 ┆ 물 ┆ 9.0 ┆ 0.5 │
│ 리자몽 ┆ 불꽃 ┆ 90.5 ┆ 1.5 │
│ 거북왕 ┆ 물 ┆ 85.5 ┆ 1.6 │
└──────────┴──────┴────────┴────────┘
이름과 타입 그리고 bmi 계산하여 반환하기 (select)
result = df.select(
pl.col("name"),
pl.col("type"),
(pl.col("weight") / (pl.col("height") ** 2)).alias("bmi"),
)
print(result)
실행결과:
shape: (6, 3)
┌──────────┬──────┬───────────┐
│ name ┆ type ┆ bmi │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 │
╞══════════╪══════╪═══════════╡
│ 피카츄 ┆ 전기 ┆ 37.5 │
│ 파이리 ┆ 불꽃 ┆ 23.611111 │
│ 이상해씨 ┆ 풀 ┆ 14.081633 │
│ 꼬부기 ┆ 물 ┆ 36.0 │
│ 리자몽 ┆ 불꽃 ┆ 40.222222 │
│ 거북왕 ┆ 물 ┆ 33.398437 │
└──────────┴──────┴───────────┘
2. bmi 열 추가하여 반환하기 (with_columns)
result = df.with_columns(
bmi=(pl.col("weight") / (pl.col("height") ** 2)).alias("bmi"),
)
print(result)
실행결과:
shape: (6, 5)
┌──────────┬──────┬────────┬────────┬───────────┐
│ name ┆ type ┆ weight ┆ height ┆ bmi │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 │
╞══════════╪══════╪════════╪════════╪═══════════╡
│ 피카츄 ┆ 전기 ┆ 6.0 ┆ 0.4 ┆ 37.5 │
│ 파이리 ┆ 불꽃 ┆ 8.5 ┆ 0.6 ┆ 23.611111 │
│ 이상해씨 ┆ 풀 ┆ 6.9 ┆ 0.7 ┆ 14.081633 │
│ 꼬부기 ┆ 물 ┆ 9.0 ┆ 0.5 ┆ 36.0 │
│ 리자몽 ┆ 불꽃 ┆ 90.5 ┆ 1.5 ┆ 40.222222 │
│ 거북왕 ┆ 물 ┆ 85.5 ┆ 1.6 ┆ 33.398437 │
└──────────┴──────┴────────┴────────┴───────────┘
3. 불꽃 타입 포켓몬만 필터링 (filter)
result = df.filter(pl.col("type") == "불꽃")
print(result)
실행결과:
shape: (2, 4)
┌────────┬──────┬────────┬────────┐
│ name ┆ type ┆ weight ┆ height │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 │
╞════════╪══════╪════════╪════════╡
│ 파이리 ┆ 불꽃 ┆ 8.5 ┆ 0.6 │
│ 리자몽 ┆ 불꽃 ┆ 90.5 ┆ 1.5 │
└────────┴──────┴────────┴────────┘
4. 타입별로 그룹화하여 보기 (group_by)
result = df.group_by(
pl.col("type"),
maintain_order=True,
).agg(
pl.len().alias("sample_size"),
pl.col("weight").mean().round(2).alias("avg_weight"),
pl.col("height").max().alias("tallest"),
)
print(result)
실행결과:
shape: (4, 4)
┌──────┬─────────────┬────────────┬─────────┐
│ type ┆ sample_size ┆ avg_weight ┆ tallest │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ u32 ┆ f64 ┆ f64 │
╞══════╪═════════════╪════════════╪═════════╡
│ 전기 ┆ 1 ┆ 6.0 ┆ 0.4 │
│ 불꽃 ┆ 2 ┆ 49.5 ┆ 1.5 │
│ 풀 ┆ 1 ┆ 6.9 ┆ 0.7 │
│ 물 ┆ 2 ┆ 47.25 ┆ 1.6 │
└──────┴─────────────┴────────────┴─────────┘
이번 글에서는 Polars에 대한 간략한 소개와 주요 특징에 대해 알아보았습니다. 다음 글부터는 Polars에서 자주 사용되는 명령어들을 하나씩 자세히 살펴보며, 각 명령어의 사용법과 활용 사례에 대해 더욱 깊이 있게 다룰 예정입니다. 많은 관심과 기대 부탁드립니다! 🚀
🔖 참고
'개발 > 파이썬' 카테고리의 다른 글
Polars로 데이터 필터링 및 정렬하기 - 자주 쓰는 명령어로 배우는 Polars #3 (0) | 2024.11.09 |
---|---|
Polars로 데이터 불러오기 및 조작하기 - 자주 쓰는 명령어로 배우는 Polars #2 (1) | 2024.10.26 |
poetry와 github actions를 활용한 파이썬 라이브러리 배포 자동화 (2) | 2024.04.13 |
SEO를 위한 sitemapr 라이브러리 소개: 구글에 서비스 페이지를 알려주자. (25) | 2024.03.16 |
파이썬 asyncio로 생산자/소비자 (Producer/Consumer) 패턴 구현하기 (25) | 2024.03.02 |