파이썬 데이터 분석 #7 polars 맛보기: pandas가 느려질 때의 다음 수

6 분 소요

시리즈 마지막 편입니다. 지금까지 여섯 편 동안 pandas 하나로 불러오기부터 시각화까지 한 바퀴를 돌았습니다. 마지막 편에서는 시야를 한 칸 넓혀 보겠습니다. pandas가 버거워지는 순간이 언제인지, 그때 꺼낼 수 있는 다음 수인 polars가 어떤 도구인지를 코드 비교 중심으로 맛보겠습니다. 새 문법을 외우는 글이 아니라, “이런 선택지가 있다"는 전체 그림을 파악하는 글입니다.

pandas가 버거워지는 순간 #

pandas는 수십만 행까지는 거의 고민 없이 잘 동작합니다. 문제는 데이터가 커질 때입니다. 보통 세 가지 벽에 부딪힙니다.

  • 행 수: 수백만〜수천만 행이 되면 groupbymerge 한 번에 수십 초씩 걸리기 시작합니다.
  • 메모리: pandas는 파일 전체를 메모리에 올립니다. 2GB짜리 CSV가 메모리에서는 그 몇 배로 부풀어, 노트북에서 MemoryError를 만나게 됩니다.
  • 단일 코어: pandas의 연산 대부분은 CPU 코어 하나만 사용합니다. 8코어 머신에서도 7개는 놀고 있습니다.

작은 데이터에서는 전혀 문제가 되지 않던 설계가, 데이터가 커지는 순간 한꺼번에 비용으로 돌아옵니다.

polars: Rust로 다시 만든 DataFrame #

polars는 Rust로 작성된 DataFrame 라이브러리입니다. 파이썬에서 import polars로 그대로 쓸 수 있고, 설계 자체가 위의 세 벽을 겨냥합니다.

  • Rust 기반: 핵심 연산이 컴파일된 네이티브 코드로 동작합니다.
  • 멀티코어: 별도 설정 없이 모든 코어를 사용해 병렬로 연산합니다.
  • Arrow 메모리 포맷: Apache Arrow라는 컬럼 기반 표준 포맷을 쓰기 때문에 메모리 효율이 좋고, 다른 도구와 데이터를 복사 없이 주고받을 수 있습니다.

설치는 한 줄입니다.

polars 설치
uv add polars

같은 작업을 나란히 놓고 비교 #

문법이 얼마나 닮았고 어디가 다른지, 시리즈 내내 쓰던 판매 데이터 sales.csv(city, category, price, quantity 열)로 같은 작업을 양쪽에 시켜 보겠습니다.

먼저 읽기입니다.

읽기: pandas
import pandas as pd

df = pd.read_csv("sales.csv")
읽기: polars
import polars as pl

df = pl.read_csv("sales.csv")

여기까지는 거의 같습니다. polars의 read_csv는 멀티코어로 파싱하기 때문에 같은 파일도 체감 속도가 다릅니다. 다음은 필터입니다.

필터: pandas
expensive = df[df["price"] > 10000]
필터: polars
expensive = df.filter(pl.col("price") > 10000)

pandas는 불리언 마스크를 대괄호에 넣었지만, polars는 pl.col("price") > 10000이라는 표현식filter 메소드에 넘깁니다. 이 차이가 polars 문법의 중심입니다. 마지막으로 groupby 집계입니다.

groupby: pandas
result = (
    df[df["price"] > 10000]
    .groupby("city")["price"]
    .mean()
    .sort_values(ascending=False)
)
groupby: polars
result = (
    df.filter(pl.col("price") > 10000)
    .group_by("city")
    .agg(pl.col("price").mean())
    .sort("price", descending=True)
)

읽어 보면 하는 일은 같습니다. 1만 원 초과 거래만 남기고, 도시별 평균 가격을 구해, 내림차순으로 정렬합니다.

표현식 API라는 사고방식 #

위 코드에서 드러난 차이를 한 문장으로 정리하면 이렇습니다. pandas는 “DataFrame 객체를 메소드로 계속 가공"하고, polars는 “무엇을 계산할지를 표현식으로 조립해서 한 번에 넘깁니다”.

pl.col("price").mean()은 그 자체로는 아무것도 계산하지 않습니다. “price 열의 평균"이라는 계산 계획일 뿐이고, agg() 같은 자리에 끼워 넣었을 때 비로소 실행됩니다. 표현식이 독립된 부품이기 때문에 조합이 자유롭습니다.

표현식 여러 개를 한 번에
result = df.group_by("city").agg(
    pl.col("price").mean().alias("avg_price"),
    pl.col("price").max().alias("max_price"),
    pl.col("quantity").sum().alias("total_qty"),
)

pandas에서 agg({"price": ["mean", "max"], ...})처럼 딕셔너리로 적던 것이, polars에서는 표현식 목록으로 평평하게 나열됩니다. 그리고 polars는 이렇게 받은 표현식들을 내부에서 병렬로 실행합니다.

lazy 모드: 계획을 세우고 한 번에 실행 #

polars의 진짜 무기는 lazy 모드입니다. 지금까지의 코드는 한 줄 한 줄 즉시 실행되는 eager 방식이었습니다. lazy 모드는 다릅니다. scan_csv로 시작하면 연산을 바로 실행하지 않고 계획만 쌓아 두다가, 마지막에 collect()를 호출하는 순간 전체 계획을 한 번에 실행합니다.

lazy 모드
result = (
    pl.scan_csv("sales.csv")
    .filter(pl.col("price") > 10000)
    .group_by("city")
    .agg(pl.col("price").mean())
    .collect()
)

장보기에 비유하면 이렇습니다. eager 방식은 “우유 사 와”, “계란 사 와"라는 말을 들을 때마다 마트를 한 번씩 다녀오는 것입니다. lazy 방식은 목록을 끝까지 받아 적은 다음, 동선을 짜서 마트를 한 번만 다녀오는 것입니다.

collect() 직전에 polars의 쿼리 옵티마이저가 계획을 검토하고 다시 짭니다. 위 예시라면 “어차피 city와 price 두 열만 쓰니까 나머지 열은 읽지 말자”, “price > 10000 필터를 파일 읽는 단계로 앞당겨서 필요한 행만 올리자” 같은 최적화가 자동으로 적용됩니다. 메모리에 다 안 올라가던 파일이 lazy 모드에서는 처리되는 일이 흔한 이유입니다.

어느 쪽을 쓸까 #

둘 중 하나를 고르는 문제라기보다, 상황에 맞는 기본값을 정하는 문제입니다.

기준pandaspolars
생태계·검색 자료압도적으로 많음빠르게 성장 중
시각화·라이브러리 연동대부분 pandas 기준변환을 거치면 가능
속도·멀티코어단일 코어기본 병렬
메모리보다 큰 데이터어려움lazy 모드로 가능

수십만 행 이하의 탐색 작업, 그리고 다른 라이브러리와 엮이는 작업이라면 pandas가 여전히 편한 선택입니다. 수백만 행 이상이거나 같은 파이프라인을 반복 실행한다면 polars가 시간을 크게 아껴 줍니다. 그리고 둘은 적이 아닙니다. 변환 한 줄로 서로 오갈 수 있기 때문에, 무거운 가공은 polars로 하고 마지막에 pandas로 바꿔 시각화하는 식의 조합이 실무에서 자주 쓰입니다.

둘 사이를 오가기
df_pl = pl.from_pandas(df_pd)   # pandas -> polars
df_pd = df_pl.to_pandas()       # polars -> pandas

더 큰 데이터로 가는 지도 #

polars로도 부족해지는 규모가 오면, 그다음 키워드 두 개만 기억해 두면 됩니다. Parquet는 CSV를 대체하는 컬럼 기반 파일 포맷으로, 같은 데이터를 훨씬 작게 저장하고 필요한 열만 읽을 수 있습니다. DuckDB는 파일 위에서 바로 SQL을 실행하는 분석용 데이터베이스로, 메모리보다 큰 데이터를 노트북 한 대로 다룰 때 표준처럼 쓰입니다. 둘 다 Arrow 포맷으로 polars와 자연스럽게 연결되므로, 이번 시리즈에서 배운 사고방식이 그대로 이어집니다.

시리즈를 마치며 #

일곱 편을 한 줄씩 돌아보겠습니다.

  • #1 시작: 데이터 분석이 무엇이고, 환경을 어떻게 준비하는지 정리했습니다.
  • #2 불러오기와 탐색: read_csv로 데이터를 열고 head, info, describe로 첫인상을 잡았습니다.
  • #3 선택과 필터: loc와 불리언 마스크로 원하는 행과 열만 꺼냈습니다.
  • #4 변형과 결측치: 새 열을 만들고, 비어 있는 값을 다루는 기준을 세웠습니다.
  • #5 그룹과 집계·결합: groupby로 묶어 요약하고 merge로 표를 합쳤습니다.
  • #6 시각화: 숫자 요약을 그래프로 바꿔 패턴을 눈으로 확인했습니다.
  • #7 polars 맛보기: pandas의 한계와 그 너머의 도구를 살펴봤습니다.

도구 이야기를 길게 했지만, 분석의 본질은 도구가 아니라 질문입니다. “어느 도시의 매출이 빠지고 있는가”, “이 변화는 우연인가"라는 질문이 먼저 있고, pandas든 polars든 그 질문에 답을 가져오는 수단일 뿐입니다. 이 시리즈에서 익힌 한 바퀴, 즉 불러오고 → 살펴보고 → 골라내고 → 다듬고 → 요약하고 → 그려보는 흐름은 도구가 바뀌어도 그대로 남습니다.

다음 학습으로는 두 갈래를 권합니다. 반복되는 데이터 작업을 코드로 굳히고 싶다면 파이썬 자동화 시리즈가 자연스럽게 이어지고, 파이썬 언어 자체의 근육을 키우고 싶다면 모던 파이썬 중급이 다음 단계입니다. 좋은 질문을 만나면, 이제 답을 구할 도구는 손에 있습니다.

X