
데이터 분석을 처음 배울 때 대부분은 Pandas(판다스)로 시작합니다. Pandas는 직관적이고 강력한 도구로, 작은 데이터셋에서는 훌륭한 성능을 보여줍니다. 하지만 데이터가 커지면 점점 느려지고, 메모리 사용량도 기하급수적으로 늘어나죠. 코랩을 사용한다면 "RAM 초과"현상을 경험하게 됩니다.
"더 빠르고 효율적인 방법 없을까?"라고 생각한 적이 있다면, 이제 Polars를 만날 준비가 된 겁니다.
Polars는 Pandas의 장점을 이어받아, 더 빠르고 메모리 효율적인 방식으로 데이터 처리를 가능하게 하는 새로운 라이브러리입니다. 특히, 대용량 데이터셋을 다루는 데 최적화되어 있어 대규모 프로젝트에서도 뛰어난 성능을 자랑하죠. 이 라이브러리는 Rust로 작성되었고, 기본적으로 멀티코어 병렬 처리를 지원하니, 속도는 Pandas와는 비교가 안 될 정도로 빠릅니다.
그럼 이제, Pandas와 Polars의 차이점을 하나씩 살펴보면서 왜 Polars가 Pandas를 뛰어넘는지 알아볼까요?
Pandas는 데이터에 무언가를 하라고 명령하면 바로바로 실행하는 '즉시 실행(Eager execution)' 방식을 사용합니다. 데이터 필터링이나 집계 등은 실행할 때마다 처리되어 결과를 바로 보여줍니다. 그게 얼마나 간단하고 좋은데! 하지만 문제가 있어요. 큰 데이터에서는 매번 실행할 때마다 시간이 많이 걸리고, 메모리도 그만큼 소모됩니다.
Polars는 '지연 실행(Lazy execution)' 방식을 사용해요. 즉, 여러 명령을 한 번에 묶어 최적화한 다음, 마지막에 한꺼번에 처리하는 방식이죠. 무슨 의미냐고요? Polars는 여러분이 하는 모든 작업을 최적화된 방식으로 한 번에 처리하기 때문에 속도가 훨씬 빠르고, 불필요한 연산을 줄일 수 있습니다. 게다가 한 번에 처리되니 메모리 사용량도 대폭 감소합니다!
Pandas는 기본적으로 단일 스레드에서 동작합니다. 그래서 여러 코어를 가진 컴퓨터라 할지라도, 한 번에 한 가지만 처리하는 것이죠. 작은 데이터셋에서는 괜찮지만, 데이터가 많아지면 처리가 느려집니다.
Polars는 여기서 한 단계 더 나아가 멀티코어 병렬 처리를 지원합니다. 여러 코어를 사용해 한 번에 여러 작업을 처리할 수 있어 데이터가 클수록 성능 차이가 극명하게 나타납니다. Polars는 정말로 대규모 데이터 작업에서 빛을 발합니다.
Pandas는 데이터가 메모리에 전부 올라가 있기 때문에, 데이터 크기가 커질수록 메모리 사용량도 크게 증가합니다. 이로 인해 Out of Memory(OOM) 에러를 만날 때가 많죠.
반면 Polars는 필요할 때만 메모리를 사용하는 매우 효율적인 구조를 가지고 있습니다. 특히 Polars의 Lazy API를 사용하면, 필요하지 않은 중간 결과들을 메모리에 올리지 않고 최적화된 방식으로 처리해 메모리 사용량을 최소화할 수 있습니다.
Pandas의 장점 중 하나는 매우 직관적인 API입니다. 누구나 금방 익힐 수 있고, 데이터를 빠르게 처리할 수 있죠. 하지만 큰 데이터에서 성능이 부족한 것은 분명한 단점입니다.
Polars는 Pandas와 매우 유사한 API를 제공하면서도, 더 빠르고 강력한 성능을 보여줍니다. 한 가지 차이점은 Lazy API의 사용인데, 이 부분은 조금 낯설 수 있지만 금방 익숙해질 수 있습니다. 그리고 Polars는 Pandas보다 훨씬 더 복잡한 작업을 효율적으로 처리할 수 있다는 장점이 있습니다.
"그래도 실제로 얼마나 빠른지 궁금한데?"라고 생각하셨죠? 간단한 예시로 성능 차이를 비교해 보겠습니다.
Pandas 코드
import pandas as pd
df = pd.read_csv("/kaggle/input/creditcardfraud/creditcard.csv")
df.head()
CPU times: user 2.84 s, sys: 114 ms, total: 2.95 s
Wall time: 2.95 s
Polars 코드
import polars as pl
df = pl.read_csv("/kaggle/input/creditcardfraud/creditcard.csv")
df.head()
CPU times: user 887 ms, sys: 113 ms, total: 999 ms
Wall time: 265 ms
Polars는 150m 데이터를 불러오는 데 265ms(0.265초) 밖에 걸리지 않았습니다. Pandas와 비교하면 약 11배 더 빠르게 데이터를 불러왔습니다.Polars는 멀티코어 CPU 병렬 처리를 지원하며, 데이터를 최적화된 방식으로 불러옵니다. 또한 Polars는 Rust로 작성되어 메모리와 성능 측면에서 매우 효율적입니다
Pandas 코드
import pandas as pd
# 데이터 불러오기
df = pd.read_csv("data.csv")
# 조건에 따라 필터링 후 집계
result = df[df['Amount'] > 100].groupby("Category").mean()
CPU times: user 2.81 s, sys: 182 ms, total: 2.99 s
Wall time: 2.99 s
Polars 코드
코드 복사
import polars as pl
# Lazy API를 사용한 Polars 데이터 처리
df = pl.read_csv("data.csv").lazy()
result = (
df.filter(pl.col("Amount") > 100)
.groupby("Category")
.agg([pl.mean("Amount")])
.collect()
)
CPU times: user 943 ms, sys: 71.4 ms, total: 1.01 s
Wall time: 267 ms
이 두 코드가 같은 작업을 하지만, Polars는 Lazy API를 사용해 불필요한 작업을 최적화하고, 훨씬 더 빠르게 처리합니다.
Lazy API를 사용하면 줄별로 실행하지 않고 대신 최적화하여 전체 쿼리를 처리할 수 있습니다.
파일에서 Lazy를 사용한다면 읽는 데이터 양을 줄이는데 도움이 될 수 있다.
q1 = (
pl.scan_csv(f"data.csv")
.with_columns(pl.col("a").str.to_uppercase())
.filter(pl.col("b > 0)
)
Polars의 DataFrame은 Pandas의 DataFrame과 유사하게, 데이터를 바로 메모리에 로드하고 각 연산이 즉시 실행됩니다. 모든 명령은 호출되는 즉시 실행되어 결과가 즉각적으로 반환됩니다.
import polars as pl
# 데이터프레임 생성
df = pl.DataFrame({
"A": [1, 2, 3, 4, 5],
"B": [10, 20, 30, 40, 50]
})
# 즉시 실행: 각 연산이 즉시 처리됨
df.filter(pl.col("A") > 2)
LazyFrame은 지연 실행을 지원하는 Polars의 데이터 구조입니다. 연산을 요청할 때 즉시 처리하지 않고, 여러 연산을 모아서 최적화한 후에 한 번에 실행합니다. 이렇게 하면 불필요한 연산을 줄이고, 메모리와 CPU 자원을 효율적으로 사용할 수 있습니다
import polars as pl
# LazyFrame 생성
df_lazy = pl.DataFrame({
"A": [1, 2, 3, 4, 5],
"B": [10, 20, 30, 40, 50]
}).lazy()
# 여러 연산을 지연 실행으로 연결
df_filtered_lazy = df_lazy.filter(pl.col("A") > 2)
df_result_lazy = df_filtered_lazy.with_columns((pl.col("B") * 2).alias("B_double"))
# 연산이 지연되다가 collect()를 호출할 때 실행됨
df_result_lazy.collect()
Polars가 엄청난 성능을 자랑하는 건 사실이지만, 몇 가지 단점도 있습니다.
Pandas는 매우 직관적이고 쉽게 익힐 수 있는 반면, Polars의 Lazy API는 익숙하지 않은 사용자에게는 조금 복잡하게 느껴질 수 있습니다. 지연 실행 방식 때문에 중간 결과를 즉시 확인하는 것이 어렵고, 실시간으로 데이터의 변화를 추적하는 데 시간이 걸릴 수 있습니다.
또한, Pandas에 비해 Polars는 아직 자료나 커뮤니티가 상대적으로 적습니다. 이에 따라, 학습을 진행하는 데 시간이 더 걸릴 수 있습니다.
Pandas는 이미 널리 사용되며, 많은 데이터 분석 및 머신러닝 라이브러리와 완벽히 호환됩니다. 반면, Polars는 아직 Scikit-learn, TensorFlow 같은 라이브러리와의 호환성에서 Pandas만큼 완벽하지는 않습니다. 이 부분이 가장 아쉬운데 데이터를 처리한 후, 사이킷런을 사용하기 위해서는 반드시 Pandas로 변환해야 합니다. 다만, to_pandas() 함수를 사용해 손쉽게 변환할 수 있습니다.
import polars as pl
import pandas as pd
# Polars 데이터프레임 생성 (예시 데이터)
df_polars = pl.DataFrame({
"A": [1, 2, 3],
"B": [4, 5, 6],
"C": [7, 8, 9]
})
# Polars 데이터프레임을 Pandas로 변환
df_pandas = df_polars.to_pandas()
Polars는 Pandas와 비교해 더 나은 성능을 제공할 뿐만 아니라, 복잡한 데이터 처리도 훨씬 효율적으로 처리합니다.
import polars as pl
pl.Series("a", [1, 2, 3, 4, 5])
shape: (5,)
Series: 'a' [i64]
[
1
2
3
4
5
]
from datetime import datetime
df = pl.DataFrame(
{
"integer": [1, 2, 3, 4, 5],
"date": [
datetime(2025, 1, 1),
datetime(2025, 1, 2),
datetime(2025, 1, 3),
datetime(2025, 1, 4),
datetime(2025, 1, 5),
],
"float": [4.0, 5.0, 6.0, 7.0, 8.0],
}
)
print(df)
shape: (5, 3)
┌─────────┬─────────────────────┬───────┐
│ integer ┆ date ┆ float │
│ --- ┆ --- ┆ --- │
│ i64 ┆ datetime[μs] ┆ f64 │
╞═════════╪═════════════════════╪═══════╡
│ 1 ┆ 2025-01-01 00:00:00 ┆ 4.0 │
│ 2 ┆ 2025-01-02 00:00:00 ┆ 5.0 │
│ 3 ┆ 2025-01-03 00:00:00 ┆ 6.0 │
│ 4 ┆ 2025-01-04 00:00:00 ┆ 7.0 │
│ 5 ┆ 2025-01-05 00:00:00 ┆ 8.0 │
└─────────┴─────────────────────┴───────┘