26편의 예제는 20행이었다. 실무 데이터는 수십만~수억 행이고, 파일도 CSV 하나가 아니라 여러 개로 흩어져 있는 경우가 흔하다. 이 규모에서는 Pandas 하나로 밀어붙이기보다 Polars 와 DuckDB 를 상황에 맞게 섞어 쓰는 편이 낫다.
한 줄 정의
Pandas 는 즉시 실행되는 싱글스레드 표 처리 라이브러리, Polars 는 Rust로 작성된 멀티스레드 DataFrame 엔진(즉시 실행 Eager와 실행 계획을 먼저 세우는 Lazy API를 모두 지원), DuckDB 는 CSV·Parquet 파일에 서버 설치 없이 SQL을 직접 실행하는 임베디드 분석 엔진이다. 셋 다 표 형태 데이터 를 다루지만 실행 방식이 다르다.
Pandas가 힘들어지는 지점
Pandas는 한 스레드에서 순서대로 연산을 처리한다. 데이터가 수백만 행을 넘어가면 연산 하나하나의 시간이 눈에 띄게 늘고, 중간 결과를 복사하며 메모리를 원본 파일 크기의 여러 배까지 쓰기도 한다. Polars와 DuckDB는 이 두 가지 — 속도와 메모리 — 를 각자 다른 방식으로 해결한다.
Polars: Eager API vs Lazy API
구분 | 실행 시점 | 적합한 상황 |
|---|---|---|
Eager | 코드 한 줄마다 즉시 실행 (Pandas와 비슷한 감각) | 작은 데이터를 탐색적으로 확인할 때 |
Lazy |
| 큰 파일을 걸러내고 집계하는 파이프라인 |
import polars as pl
# Eager: 한 줄씩 즉시 실행
df = pl.read_csv("sales.csv")
result = df.filter(pl.col("amount") > 0)
# Lazy: 실행 계획을 세운 뒤 collect()에서 한 번에 실행
result = (
pl.scan_csv("sales.csv")
.filter(pl.col("amount") > 0)
.group_by("region")
.agg([pl.col("amount").sum().alias("total")])
.sort("total", descending=True)
.collect() # 여기서 실제 실행
)
collect() 를 부르기 전까지는 아무 연산도 일어나지 않는다. 그 사이 Polars는 전체 계획을 보고 필요한 열만 읽고(projection pushdown), 필터를 파일을 읽는 단계로 최대한 앞당기는(predicate pushdown) 최적화를 적용한다. .explain() 로 실제 계획을 확인할 수 있다.
plan.explain()
AGGREGATE[maintain_order: false]
[col("amount").sum().alias("total")] BY [col("region")]
FROM
Csv SCAN [sales.csv]
PROJECT 2/4 COLUMNS
SELECTION: col("amount") > 0.0
PROJECT 2/4 COLUMNS 는 파일에 열이 4개 있어도 이 계획에 필요한 2개(region, amount)만 실제로 읽었다는 뜻이다. Pandas라면 read_csv 시점에 열 4개를 전부 메모리에 올린 뒤 걸러낸다.
Polars 핵심 문법 — Pandas와 대응 관계
하는 일 | Pandas | Polars |
|---|---|---|
열 참조 |
|
|
행 필터 |
|
|
열 선택 |
|
|
열 추가·수정 |
|
|
그룹 집계 |
|
|
Pandas와 서로 변환도 가능하다.
df_pd = df_pl.to_pandas()
df_pl = pl.from_pandas(df_pd)
DuckDB — 파일에 바로 SQL
DuckDB는 CSV·Parquet 파일을 먼저 표로 읽어들이지 않고, 파일 경로를 SQL의 FROM 절에 직접 쓴다.
import duckdb
result = duckdb.sql("""
SELECT region,
SUM(amount) AS total,
COUNT(*) AS cnt
FROM 'sales.csv'
WHERE amount > 0
GROUP BY region
ORDER BY total DESC
""").df() # 결과를 Pandas DataFrame으로
*.csv, *.parquet 같은 와일드카드로 여러 파일을 한 번에 조회할 수 있고, JOIN·GROUP BY·윈도우 함수까지 표준 SQL을 그대로 지원한다.
duckdb.sql("""
SELECT s.*, c.tier
FROM 'sales.parquet' s
JOIN 'customers.csv' c ON s.customer_id = c.id
""").show()
결과를 어느 도구로 이어받을지는 변환 메서드로 정한다 — .df() 는 Pandas, .pl() 는 Polars DataFrame을 돌려준다.
실측: 같은 집계, 세 도구
50만 행짜리 CSV(약 14.7MB)에 같은 조건(amount > 0)으로 필터링한 뒤 지역별 합계를 구하는 집계를 네 가지 방식으로 실행해 시간을 쟀다.
방식 | 걸린 시간 |
|---|---|
Pandas (read_csv + groupby) | 0.078초 |
Polars Eager (read_csv + filter + group_by) | 0.032초 |
Polars Lazy (scan_csv + collect) | 0.019초 |
DuckDB (SQL, 파일에 직접) | 0.053초 |
이 표는 로컬 환경에서 한 번 측정한 값이다. 실제 격차는 데이터 크기, 열 개수, 파일 형식(CSV vs Parquet), 필터가 얼마나 많은 행을 걸러내는지에 따라 달라진다. 다만 방향성 — Lazy API가 Eager보다, Polars가 Pandas보다 대체로 빠르다 — 은 데이터가 커질수록 더 뚜렷해진다.
Parquet — 컬럼형 저장의 이득
CSV는 행 단위 텍스트라 열 하나만 읽어도 파일 전체를 훑어야 한다. Parquet은 열 단위(컬럼형)로 저장해 필요한 열만 골라 읽을 수 있고, 압축 효율도 더 좋다. 같은 50만 행 데이터를 두 형식으로 저장해 비교했다.
항목 | CSV | Parquet |
|---|---|---|
파일 크기 | 14.7MB | 5.7MB (2.6배 작음) |
전체 로딩 | 0.058초 | 0.051초 |
열 2개만 선택해 로딩 | 0.049초 | 0.030초 (1.6배 빠름) |
열을 적게 선택할수록 Parquet의 이득이 커진다. CSV는 어떤 열을 읽든 파일 전체를 파싱해야 하지만, Parquet은 선택한 열의 데이터만 디스크에서 골라 읽기 때문이다.
Arrow — 도구 사이를 오가는 비용을 줄이는 포맷
Apache Arrow 는 컬럼형 인메모리 데이터 포맷이다. Polars, DuckDB, PyArrow가 모두 이 포맷을 내부적으로 쓰기 때문에, 세 도구 사이를 오갈 때 데이터를 다시 직렬화하지 않고 그대로 주고받을 수 있다. duckdb.sql(...).pl() 처럼 결과를 Polars로 바로 받는 것도 이 덕분이다. Pandas는 내부적으로 NumPy 배열을 쓰므로, Pandas와 주고받을 때는 이 제로카피 이점이 적용되지 않는다.
도구 선택 가이드
상황 | 선택 | |
|---|---|---|
수백만 행 이하를 Jupyter에서 탐색하고 바로 시각화한다 |
| |
대용량 파일을 빠르게 필터링·집계해야 한다 |
| |
여러 CSV·Parquet 파일을 SQL 조인·집계로 다뤄야 한다 |
| |
최종 결과를 시각화·리포트·모델 입력으로 넘긴다 | 필요한 시점에 |
|
세 도구를 배타적으로 고르지 않아도 된다. 원본 CSV를 Parquet으로 변환해 저장하고, DuckDB나 Polars Lazy로 걸러 집계한 뒤, 최종 검토와 시각화 단계에서만 Pandas로 바꾸는 흐름이 실무에서 흔하다.
장점과 한계
구분 | 내용 |
|---|---|
장점 | Polars·DuckDB는 대용량에서 Pandas보다 빠르고 메모리를 덜 쓰며, Arrow 포맷 덕분에 서로 변환 비용도 낮다. |
한계 | 생태계(시각화·통계·모델 라이브러리)는 여전히 Pandas 중심이라, 다른 도구로 처리한 결과도 결국 Pandas로 변환해 넘기는 경우가 많다. 팀 전체가 Pandas API에 익숙하다면 문법을 새로 배우는 비용도 고려해야 한다. |
자주 발생하는 문제
Lazy 체인 끝에 collect()를 빼먹는다. scan_csv 로 시작한 체인은 collect() 를 호출하기 전까지 LazyFrame 상태로만 존재한다. 결과를 출력하거나 다음 단계로 넘기려면 반드시 collect() 로 실제 실행을 트리거해야 한다.
세 도구의 벤치마크 조건을 다르게 잰다. 캐시된 파일과 캐시되지 않은 파일, 다른 반복 횟수로 시간을 재면 비교 자체가 무의미해진다. 같은 데이터, 같은 반복 횟수로 측정해야 한다.
Polars Eager로 큰 데이터를 처리하고 Lazy를 안 쓴다. read_csv 는 Pandas처럼 파일 전체를 즉시 메모리에 올린다. 대용량 파일에서는 scan_csv 로 시작해야 프로젝션·필터 최적화의 이점을 받는다.
관련 문서
이 문서에서 비교한 세 도구의 기초 문법은 Pandas로 데이터 탐색·정제·집계하기 에서 다룬 EDA·groupby·merge를 전제로 한다. 파일 형식별 읽기·쓰기 API는 CSV·JSON·Parquet 파일 읽고 쓰기 를 먼저 본다.
참고 자료
Polars 공식 문서 — Lazy API (2026-08-10 확인)
DuckDB 공식 문서 — Python API (2026-08-10 확인)
Apache Arrow 공식 사이트 (2026-08-10 확인)
댓글 0
댓글을 불러오는 중…