Pandas를 뛰어넘는 Polars, 차세대 데이터 분석 도구

Leo·2024년 9월 24일

데이터 분석을 처음 배울 때 대부분은 Pandas(판다스)로 시작합니다. Pandas는 직관적이고 강력한 도구로, 작은 데이터셋에서는 훌륭한 성능을 보여줍니다. 하지만 데이터가 커지면 점점 느려지고, 메모리 사용량도 기하급수적으로 늘어나죠. 코랩을 사용한다면 "RAM 초과"현상을 경험하게 됩니다.
"더 빠르고 효율적인 방법 없을까?"라고 생각한 적이 있다면, 이제 Polars를 만날 준비가 된 겁니다.

Polars가 뭐야?

Polars는 Pandas의 장점을 이어받아, 더 빠르고 메모리 효율적인 방식으로 데이터 처리를 가능하게 하는 새로운 라이브러리입니다. 특히, 대용량 데이터셋을 다루는 데 최적화되어 있어 대규모 프로젝트에서도 뛰어난 성능을 자랑하죠. 이 라이브러리는 Rust로 작성되었고, 기본적으로 멀티코어 병렬 처리를 지원하니, 속도는 Pandas와는 비교가 안 될 정도로 빠릅니다.

그럼 이제, Pandas와 Polars의 차이점을 하나씩 살펴보면서 왜 Polars가 Pandas를 뛰어넘는지 알아볼까요?

Pandas와 Polars, 무엇이 다를까?

1. 즉시 실행 vs 지연 실행

Pandas는 데이터에 무언가를 하라고 명령하면 바로바로 실행하는 '즉시 실행(Eager execution)' 방식을 사용합니다. 데이터 필터링이나 집계 등은 실행할 때마다 처리되어 결과를 바로 보여줍니다. 그게 얼마나 간단하고 좋은데! 하지만 문제가 있어요. 큰 데이터에서는 매번 실행할 때마다 시간이 많이 걸리고, 메모리도 그만큼 소모됩니다.

Polars는 '지연 실행(Lazy execution)' 방식을 사용해요. 즉, 여러 명령을 한 번에 묶어 최적화한 다음, 마지막에 한꺼번에 처리하는 방식이죠. 무슨 의미냐고요? Polars는 여러분이 하는 모든 작업을 최적화된 방식으로 한 번에 처리하기 때문에 속도가 훨씬 빠르고, 불필요한 연산을 줄일 수 있습니다. 게다가 한 번에 처리되니 메모리 사용량도 대폭 감소합니다!

2. 단일 스레드 vs 멀티코어 병렬 처리

Pandas는 기본적으로 단일 스레드에서 동작합니다. 그래서 여러 코어를 가진 컴퓨터라 할지라도, 한 번에 한 가지만 처리하는 것이죠. 작은 데이터셋에서는 괜찮지만, 데이터가 많아지면 처리가 느려집니다.

Polars는 여기서 한 단계 더 나아가 멀티코어 병렬 처리를 지원합니다. 여러 코어를 사용해 한 번에 여러 작업을 처리할 수 있어 데이터가 클수록 성능 차이가 극명하게 나타납니다. Polars는 정말로 대규모 데이터 작업에서 빛을 발합니다.

3. 메모리 효율성

Pandas는 데이터가 메모리에 전부 올라가 있기 때문에, 데이터 크기가 커질수록 메모리 사용량도 크게 증가합니다. 이로 인해 Out of Memory(OOM) 에러를 만날 때가 많죠.

반면 Polars는 필요할 때만 메모리를 사용하는 매우 효율적인 구조를 가지고 있습니다. 특히 Polars의 Lazy API를 사용하면, 필요하지 않은 중간 결과들을 메모리에 올리지 않고 최적화된 방식으로 처리해 메모리 사용량을 최소화할 수 있습니다.

4. 사용 편의성

Pandas의 장점 중 하나는 매우 직관적인 API입니다. 누구나 금방 익힐 수 있고, 데이터를 빠르게 처리할 수 있죠. 하지만 큰 데이터에서 성능이 부족한 것은 분명한 단점입니다.

Polars는 Pandas와 매우 유사한 API를 제공하면서도, 더 빠르고 강력한 성능을 보여줍니다. 한 가지 차이점은 Lazy API의 사용인데, 이 부분은 조금 낯설 수 있지만 금방 익숙해질 수 있습니다. 그리고 Polars는 Pandas보다 훨씬 더 복잡한 작업을 효율적으로 처리할 수 있다는 장점이 있습니다.

Pandas와 Polars, 성능 테스트

"그래도 실제로 얼마나 빠른지 궁금한데?"라고 생각하셨죠? 간단한 예시로 성능 차이를 비교해 보겠습니다.

데이터 불러오기 (150M, csv파일)

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 API를 사용하면 줄별로 실행하지 않고 대신 최적화하여 전체 쿼리를 처리할 수 있습니다.

파일에서 Lazy API 사용

파일에서 Lazy를 사용한다면 읽는 데이터 양을 줄이는데 도움이 될 수 있다.

  • 데이터 로드
  • a 열을 대문자로 변환
  • b 열에 필터 적용
q1 = (
    pl.scan_csv(f"data.csv")
    .with_columns(pl.col("a").str.to_uppercase())
    .filter(pl.col("b > 0)
)

DataFrame vs LazyFrame

LazyFrame (지연 실행, Lazy Execution)

DataFrame 사용 예시 (즉시 실행)

Polars의 DataFrame은 Pandas의 DataFrame과 유사하게, 데이터를 바로 메모리에 로드하고 각 연산이 즉시 실행됩니다. 모든 명령은 호출되는 즉시 실행되어 결과가 즉각적으로 반환됩니다.

  • 즉시 실행: filter 연산이 호출되자마자 실행되어 결과가 바로 반환됩니다.
  • 중간 단계마다 데이터를 바로 확인할 수 있습니다.
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 사용 예시 (지연 실행)

LazyFrame은 지연 실행을 지원하는 Polars의 데이터 구조입니다. 연산을 요청할 때 즉시 처리하지 않고, 여러 연산을 모아서 최적화한 후에 한 번에 실행합니다. 이렇게 하면 불필요한 연산을 줄이고, 메모리와 CPU 자원을 효율적으로 사용할 수 있습니다

  • 지연 실행: filter와 with_columns 연산은 즉시 실행되지 않고, 마지막 .collect()를 호출할 때 한꺼번에 실행됩니다.
  • 연산 최적화: 여러 연산을 한 번에 처리하며, 불필요한 중간 연산을 생략하여 성능을 최적화할 수 있습니다.
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의 단점은 없을까?

Polars가 엄청난 성능을 자랑하는 건 사실이지만, 몇 가지 단점도 있습니다.

1. 학습 곡선

Pandas는 매우 직관적이고 쉽게 익힐 수 있는 반면, Polars의 Lazy API는 익숙하지 않은 사용자에게는 조금 복잡하게 느껴질 수 있습니다. 지연 실행 방식 때문에 중간 결과를 즉시 확인하는 것이 어렵고, 실시간으로 데이터의 변화를 추적하는 데 시간이 걸릴 수 있습니다.
또한, Pandas에 비해 Polars는 아직 자료나 커뮤니티가 상대적으로 적습니다. 이에 따라, 학습을 진행하는 데 시간이 더 걸릴 수 있습니다.

2. 호환성 문제

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()

언제 Pandas 대신 Polars를 사용해야 할까?

  • 데이터셋이 매우 클 때.
  • 성능이 중요한 프로젝트를 진행할 때.
  • 복잡한 데이터 변환이 필요할 때.
  • 메모리 제한이 있는 환경에서 작업할 때.

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   │
└─────────┴─────────────────────┴───────┘
profile
매일 성장

0개의 댓글