이미지 로딩 중...

Polars 프로젝트 시작하기 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 15. · 3 Views

Polars 프로젝트 시작하기 완벽 가이드

Python 데이터 분석의 새로운 강자 Polars를 처음 시작하는 초급 개발자를 위한 가이드입니다. 프로젝트 소개부터 환경 설정까지 실무에 바로 적용할 수 있는 내용을 담았습니다.


목차

  1. Polars란 무엇인가
  2. 개발 환경 설정하기
  3. 첫 번째 데이터 로딩하기
  4. 데이터프레임 구조 이해하기
  5. 컬럼 선택과 필터링 기초
  6. 데이터 정렬과 순위 매기기
  7. 그룹화와 집계 연산
  8. Lazy 모드와 쿼리 최적화
  9. Jupyter 노트북에서 Polars 사용하기
  10. VSCode에서 Polars 개발 환경 구축하기

1. Polars란 무엇인가

시작하며

여러분이 대용량 데이터를 Pandas로 처리하다가 메모리 부족으로 커널이 죽어버린 경험 있나요? 혹은 데이터 처리에 너무 오랜 시간이 걸려서 답답했던 적이 있으신가요?

이런 문제는 데이터 분석가와 데이터 과학자들이 매일 마주하는 현실입니다. 특히 기가바이트 단위의 데이터를 다룰 때, Pandas는 성능과 메모리 효율성에서 한계를 보입니다.

바로 이럴 때 필요한 것이 Polars입니다. Rust로 작성된 이 차세대 데이터프레임 라이브러리는 Pandas보다 10배에서 100배까지 빠른 성능을 제공하면서도 훨씬 적은 메모리를 사용합니다.

개요

간단히 말해서, Polars는 고성능 데이터 처리를 위해 설계된 Python 라이브러리입니다. Rust로 작성되어 메모리 안전성과 속도를 동시에 보장합니다.

왜 이 라이브러리가 필요한지 실무 관점에서 살펴보면, 데이터 분석 파이프라인에서 가장 많은 시간을 차지하는 부분이 바로 데이터 로딩과 전처리입니다. Polars를 사용하면 이 시간을 획기적으로 단축할 수 있습니다.

예를 들어, 수백만 행의 CSV 파일을 읽고 그룹화 연산을 수행하는 경우 Pandas로는 몇 분이 걸리지만 Polars로는 몇 초면 충분합니다. 기존에는 Pandas의 느린 속도를 감수하거나 Spark 같은 무거운 분산 처리 시스템을 도입해야 했다면, 이제는 단일 머신에서 Polars만으로도 충분한 성능을 얻을 수 있습니다.

Polars의 핵심 특징은 크게 세 가지입니다. 첫째, Lazy Evaluation을 통해 쿼리를 최적화한 후 실행합니다.

둘째, Apache Arrow 메모리 포맷을 사용하여 제로 카피 연산이 가능합니다. 셋째, 멀티코어를 완벽하게 활용하는 병렬 처리를 지원합니다.

이러한 특징들이 실무에서 데이터 파이프라인의 전체 처리 시간을 크게 단축시켜줍니다.

코드 예제

import polars as pl

# CSV 파일을 빠르게 읽어옵니다
df = pl.read_csv("sales_data.csv")

# 데이터프레임의 첫 5행을 확인합니다
print(df.head())

# 컬럼별 데이터 타입과 통계 정보를 출력합니다
print(df.describe())

# 특정 조건으로 필터링하고 그룹화합니다
result = df.filter(pl.col("sales") > 1000).group_by("category").agg(
    pl.col("sales").sum().alias("total_sales")
)

설명

이것이 하는 일: 위 코드는 Polars의 기본적인 사용법을 보여줍니다. CSV 파일을 읽어서 데이터를 탐색하고, 필터링과 집계 연산을 수행하는 전형적인 데이터 분석 워크플로우입니다.

첫 번째로, pl.read_csv() 함수는 CSV 파일을 메모리로 빠르게 읽어들입니다. Polars는 내부적으로 멀티스레딩을 사용하여 파일을 청크 단위로 병렬 읽기를 수행하기 때문에 Pandas보다 훨씬 빠릅니다.

또한 자동으로 데이터 타입을 추론하여 최적의 메모리 사용을 보장합니다. 그 다음으로, head()describe() 메서드가 실행되면서 데이터의 구조와 통계 정보를 확인할 수 있습니다.

내부에서는 Apache Arrow 포맷으로 데이터가 저장되어 있어, 컬럼 단위 연산이 매우 효율적으로 수행됩니다. 이는 행 기반 저장 방식을 사용하는 전통적인 방법보다 분석 작업에 훨씬 유리합니다.

마지막으로, filter()group_by() 체인이 실행되어 조건에 맞는 데이터를 추출하고 카테고리별 판매액 합계를 계산합니다. Polars는 이러한 연산들을 쿼리 플랜으로 변환하여 최적화한 후, 모든 CPU 코어를 활용하여 병렬로 처리합니다.

pl.col() 표현식은 컬럼을 참조하는 Polars만의 방식으로, SQL과 유사한 직관적인 문법을 제공합니다. 여러분이 이 코드를 사용하면 기존 Pandas 코드를 최소한의 수정으로 마이그레이션할 수 있으면서도 극적인 성능 향상을 경험할 수 있습니다.

특히 대용량 데이터셋에서 메모리 사용량이 크게 줄어들고, 처리 속도가 비약적으로 빨라집니다. 또한 타입 안전성이 강화되어 런타임 에러가 줄어드는 이점도 있습니다.

실전 팁

💡 Polars는 Pandas와 유사한 API를 제공하지만, pl.col() 표현식을 사용하는 것이 더 효율적입니다. 이는 쿼리 최적화를 가능하게 합니다.

💡 대용량 파일을 다룰 때는 scan_csv() 대신 read_csv()를 사용하여 Lazy 모드로 작업하면 메모리를 절약할 수 있습니다.

💡 체인 메서드를 사용할 때 중간 결과를 변수에 저장하지 말고 한 번에 연결하세요. Polars가 전체 파이프라인을 최적화할 수 있습니다.

💡 성능 비교를 위해 %%timeit 매직 커맨드를 사용하여 Pandas와 직접 벤치마크해보세요. 차이에 놀라실 겁니다.

💡 Polars 공식 문서의 "Coming from Pandas" 섹션을 참고하면 마이그레이션이 훨씬 쉬워집니다.


2. 개발 환경 설정하기

시작하며

여러분이 새로운 라이브러리를 배우려고 할 때 가장 먼저 마주하는 벽이 무엇인가요? 바로 환경 설정입니다.

버전 충돌, 의존성 문제, 설치 오류 등으로 시작도 하기 전에 좌절하신 경험이 있을 겁니다. 이런 문제는 특히 데이터 과학 라이브러리에서 더 심각합니다.

NumPy, Pandas, Matplotlib 등 수많은 의존성 패키지들이 서로 맞물려 있어서, 하나만 잘못 설치해도 전체 환경이 망가질 수 있습니다. 바로 이럴 때 필요한 것이 체계적인 환경 설정 전략입니다.

가상환경을 사용하고 올바른 도구를 선택하면 이러한 문제를 미리 방지할 수 있습니다.

개요

간단히 말해서, Polars 개발 환경 설정은 Python 가상환경을 만들고 pip나 conda로 Polars를 설치하는 과정입니다. 하지만 올바른 방법을 알고 시작하는 것이 중요합니다.

왜 가상환경이 필요한지 실무 관점에서 살펴보면, 프로젝트마다 요구하는 라이브러리 버전이 다를 수 있기 때문입니다. A 프로젝트는 Pandas 1.5를 사용하고, B 프로젝트는 Pandas 2.0을 사용한다면 가상환경 없이는 두 프로젝트를 동시에 관리할 수 없습니다.

예를 들어, 레거시 코드베이스를 유지하면서 새로운 프로젝트를 Polars로 시작하는 경우에 가상환경은 필수적입니다. 기존에는 시스템 전역에 패키지를 설치하고 버전 충돌이 발생하면 재설치하는 방식으로 문제를 해결했다면, 이제는 프로젝트별로 독립된 환경을 만들어서 깔끔하게 관리할 수 있습니다.

환경 설정의 핵심은 세 가지입니다. 첫째, venv나 conda를 사용한 가상환경 생성.

둘째, requirements.txt나 environment.yml을 통한 의존성 관리. 셋째, Jupyter 노트북이나 VSCode 같은 적합한 개발 도구 선택.

이러한 요소들이 갖춰져야 안정적인 개발 환경에서 Polars를 효과적으로 학습하고 사용할 수 있습니다.

코드 예제

# 가상환경 생성 (터미널에서 실행)
# python -m venv polars_env

# 가상환경 활성화 (Windows)
# polars_env\Scripts\activate

# 가상환경 활성화 (Mac/Linux)
# source polars_env/bin/activate

# Polars 설치
# pip install polars

# 추가 유용한 패키지들 함께 설치
# pip install polars[all] jupyter matplotlib seaborn

# 설치 확인
import polars as pl
print(f"Polars version: {pl.__version__}")

설명

이것이 하는 일: 위 코드는 완전한 Polars 개발 환경을 구축하는 단계별 명령어를 보여줍니다. 가상환경 생성부터 패키지 설치, 그리고 설치 확인까지 전체 프로세스를 다룹니다.

첫 번째로, python -m venv 명령어는 현재 디렉토리에 독립된 Python 환경을 만듭니다. 이 환경은 시스템에 설치된 Python과 완전히 분리되어 있어서, 여기서 설치하는 패키지들이 다른 프로젝트에 영향을 주지 않습니다.

polars_env라는 이름의 폴더가 생성되며, 그 안에 Python 인터프리터와 pip가 복사됩니다. 그 다음으로, activate 스크립트를 실행하면서 현재 터미널 세션이 새로 만든 가상환경을 사용하도록 전환됩니다.

내부적으로는 PATH 환경 변수가 수정되어 가상환경의 Python이 우선적으로 실행됩니다. 프롬프트 앞에 (polars_env)가 표시되면 성공적으로 활성화된 것입니다.

마지막으로, pip install polars[all] 명령어가 Polars와 모든 선택적 의존성을 설치합니다. [all] 옵션은 Excel 파일 읽기, 데이터베이스 연결, 시각화 등 추가 기능에 필요한 패키지들을 함께 설치합니다.

Jupyter를 추가로 설치하면 노트북 환경에서 대화형으로 코드를 실행할 수 있고, matplotlib과 seaborn은 데이터 시각화에 사용됩니다. 여러분이 이 과정을 따라하면 어떤 컴퓨터에서든 동일한 환경을 재현할 수 있습니다.

팀 프로젝트에서 "내 컴퓨터에서는 되는데요" 같은 상황을 방지할 수 있고, 새로운 팀원이 합류했을 때도 빠르게 온보딩할 수 있습니다. 또한 나중에 pip freeze > requirements.txt로 현재 환경을 저장해두면 언제든지 같은 환경을 복원할 수 있습니다.

실전 팁

💡 conda 사용자라면 conda create -n polars_env python=3.11 polars -c conda-forge로 한 번에 환경과 패키지를 설치할 수 있습니다.

💡 VSCode를 사용한다면 Ctrl+Shift+P에서 "Python: Select Interpreter"로 가상환경을 선택하세요. 자동완성과 타입 힌트가 정확해집니다.

💡 requirements.txt에 버전을 명시할 때 polars==0.20.0 처럼 정확한 버전을 지정하면 재현성이 높아집니다.

💡 M1/M2 Mac 사용자는 Rosetta 없이 네이티브 ARM64 버전이 자동 설치되므로 성능이 더 좋습니다.

💡 Docker를 사용한다면 FROM python:3.11-slim 베이스 이미지에 Polars를 설치하여 컨테이너화된 환경을 만들 수 있습니다.


3. 첫 번째 데이터 로딩하기

시작하며

여러분이 데이터 분석을 시작할 때 가장 먼저 하는 일이 무엇인가요? 바로 데이터를 불러오는 것입니다.

하지만 CSV, Excel, Parquet, JSON 등 다양한 포맷의 데이터를 어떻게 효율적으로 읽어야 할지 막막하셨을 겁니다. 이런 문제는 데이터 크기가 커질수록 더 심각해집니다.

수 기가바이트의 CSV 파일을 Pandas로 읽으려다가 메모리 부족으로 실패하거나, 몇 분씩 기다려야 하는 상황은 생산성을 크게 떨어뜨립니다. 바로 이럴 때 필요한 것이 Polars의 강력한 데이터 로딩 기능입니다.

다양한 파일 포맷을 빠르게 읽고, 필요한 컬럼만 선택적으로 로딩하여 메모리를 절약할 수 있습니다.

개요

간단히 말해서, Polars는 read_csv(), read_excel(), read_parquet() 등 다양한 함수로 여러 포맷의 데이터를 읽을 수 있습니다. 각 함수는 해당 포맷에 최적화되어 있습니다.

왜 Polars의 데이터 로딩이 특별한지 실무 관점에서 살펴보면, 멀티스레드를 활용한 병렬 읽기와 메모리 효율적인 Arrow 포맷 덕분에 속도와 메모리 사용량 모두에서 우수한 성능을 보입니다. 예를 들어, 1GB 크기의 CSV 파일을 읽을 때 Pandas는 3-4GB의 메모리를 사용하지만, Polars는 1.5GB 정도만 사용하면서도 훨씬 빠르게 처리합니다.

기존에는 큰 파일을 청크로 나눠서 읽거나 서버의 메모리를 증설해야 했다면, 이제는 Polars의 효율적인 로딩으로 일반 노트북에서도 대용량 데이터를 다룰 수 있습니다. 데이터 로딩의 핵심 기능은 세 가지입니다.

첫째, 컬럼 선택 옵션으로 필요한 데이터만 읽기. 둘째, 타입 추론과 수동 타입 지정으로 메모리 최적화.

셋째, Lazy 모드를 통한 쿼리 최적화. 이러한 기능들이 실제 프로젝트에서 데이터 파이프라인의 첫 단계를 효율적으로 만들어줍니다.

코드 예제

import polars as pl

# CSV 파일 읽기 - 기본 방식
df = pl.read_csv("sales_data.csv")

# 특정 컬럼만 선택해서 읽기 (메모리 절약)
df_selected = pl.read_csv(
    "sales_data.csv",
    columns=["date", "product", "sales", "quantity"]
)

# 데이터 타입을 명시적으로 지정하기
df_typed = pl.read_csv(
    "sales_data.csv",
    dtypes={"product_id": pl.Int32, "price": pl.Float64}
)

# Lazy 모드로 읽기 (대용량 파일에 유용)
lazy_df = pl.scan_csv("large_file.csv")
result = lazy_df.filter(pl.col("sales") > 1000).collect()

설명

이것이 하는 일: 위 코드는 Polars로 데이터를 로딩하는 여러 방법을 보여줍니다. 기본 읽기부터 고급 최적화 기법까지 실무에서 자주 사용하는 패턴들을 다룹니다.

첫 번째로, read_csv() 함수는 CSV 파일 전체를 메모리로 읽어들입니다. Polars는 내부적으로 파일을 스캔하여 각 컬럼의 데이터 타입을 자동으로 추론합니다.

이 과정에서 멀티스레딩을 사용하여 여러 청크를 동시에 읽고 파싱하므로, 싱글스레드로 동작하는 Pandas보다 훨씬 빠릅니다. 그 다음으로, columns 파라미터를 사용하면 필요한 컬럼만 선택적으로 로딩할 수 있습니다.

50개의 컬럼이 있는 파일에서 4개만 필요하다면, 메모리 사용량을 90% 이상 줄일 수 있습니다. 이는 특히 클라우드 환경에서 비용 절감에도 도움이 됩니다.

Polars는 파일을 읽는 단계에서부터 불필요한 컬럼을 무시하므로 I/O 성능도 향상됩니다. 세 번째로, dtypes 파라미터로 데이터 타입을 명시하면 자동 추론의 오버헤드를 제거하고 정확한 타입을 보장할 수 있습니다.

예를 들어, 제품 ID가 숫자로만 구성되어 있어도 실제로는 카테고리 데이터이므로 Int32로 지정하는 것이 Float64로 자동 추론되는 것보다 메모리 효율적입니다. 마지막으로, scan_csv()는 Lazy 모드로 파일을 읽습니다.

이는 실제로 데이터를 메모리에 로드하지 않고 쿼리 플랜만 작성합니다. 이후 filter() 같은 연산을 체인으로 연결하고 마지막에 collect()를 호출하면, Polars가 전체 파이프라인을 분석하여 최적화된 실행 계획을 만듭니다.

예를 들어, 필터 조건을 만족하는 행만 읽도록 최적화하여 불필요한 데이터 로딩을 피합니다. 여러분이 이러한 로딩 방법들을 상황에 맞게 선택하면 데이터 파이프라인의 시작점을 최적화할 수 있습니다.

탐색적 분석에는 기본 read_csv()로 시작하고, 프로덕션 환경에서는 컬럼 선택과 타입 지정을 활용하세요. 정말 큰 파일을 다룬다면 Lazy 모드가 게임 체인저가 될 것입니다.

실전 팁

💡 Excel 파일은 read_excel("file.xlsx", sheet_name="Sheet1")로 읽을 수 있습니다. 단, pip install polars[xlsx] 설치가 필요합니다.

💡 Parquet 포맷은 CSV보다 훨씬 빠르고 용량도 작습니다. 가능하면 df.write_parquet("file.parquet")로 저장하고 재사용하세요.

💡 구분자가 탭인 TSV 파일은 read_csv("file.tsv", separator="\t")로 읽을 수 있습니다.

💡 인코딩 문제가 있다면 encoding="cp949" 또는 encoding="euc-kr" 파라미터를 시도해보세요 (한글 데이터).

💡 n_rows=1000 파라미터로 처음 1000행만 읽어서 데이터 구조를 먼저 파악한 후 전체 로딩 전략을 세우는 것이 좋습니다.


4. 데이터프레임 구조 이해하기

시작하며

여러분이 새로운 데이터셋을 받았을 때 가장 먼저 하는 일이 무엇인가요? 컬럼이 몇 개인지, 각 컬럼의 타입은 무엇인지, 결측치는 있는지 파악하는 것입니다.

하지만 수십 개의 컬럼과 수백만 행의 데이터에서 이 정보를 빠르게 얻기는 쉽지 않습니다. 이런 데이터 구조 파악은 모든 분석의 첫 단계입니다.

데이터 타입이 잘못 인식되었거나, 예상치 못한 결측치가 있다면 이후 모든 분석 결과가 왜곡될 수 있습니다. 실제로 많은 데이터 과학 프로젝트에서 발생하는 오류의 대부분이 이 초기 탐색 단계를 소홀히 했기 때문입니다.

바로 이럴 때 필요한 것이 Polars의 데이터프레임 탐색 메서드들입니다. 몇 줄의 코드로 데이터의 전체 구조를 빠르게 파악할 수 있습니다.

개요

간단히 말해서, Polars 데이터프레임은 컬럼 이름, 데이터 타입, 행 개수 등의 메타데이터와 실제 데이터를 담고 있는 2차원 테이블 구조입니다. 각 컬럼은 동일한 타입의 데이터를 담은 Series입니다.

왜 데이터프레임 구조를 정확히 이해해야 하는지 실무 관점에서 살펴보면, 잘못된 타입 인식은 연산 오류나 성능 저하로 이어집니다. 예를 들어, 날짜 컬럼이 문자열로 인식되면 시계열 분석이 불가능하고, 카테고리 데이터가 문자열로 저장되면 불필요하게 많은 메모리를 사용합니다.

기존에는 df.info(), df.dtypes, df.shape 등 여러 명령어를 각각 실행해야 했다면, Polars에서는 describe(), schema, glimpse() 같은 메서드로 한 번에 종합적인 정보를 얻을 수 있습니다. 데이터프레임 구조 파악의 핵심 요소는 네 가지입니다.

첫째, shape과 컬럼 정보로 전체 크기 확인. 둘째, 각 컬럼의 데이터 타입 확인.

셋째, 기술 통계량으로 데이터 분포 파악. 넷째, 결측치와 유니크 값 개수 확인.

이러한 정보들이 데이터 전처리와 분석 전략을 수립하는 기반이 됩니다.

코드 예제

import polars as pl

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

# 데이터프레임 기본 정보 확인
print(f"행 개수: {df.height}, 열 개수: {df.width}")
print(f"전체 크기: {df.shape}")

# 각 컬럼의 데이터 타입 확인
print("\n데이터 타입:")
print(df.schema)

# 첫 5행과 마지막 5행 확인
print("\n처음 5행:")
print(df.head())
print("\n마지막 5행:")
print(df.tail())

# 기술 통계량 확인 (수치형 컬럼)
print("\n기술 통계:")
print(df.describe())

# 각 컬럼의 결측치 개수 확인
print("\n결측치 개수:")
print(df.null_count())

설명

이것이 하는 일: 위 코드는 새로운 데이터셋을 받았을 때 가장 먼저 실행해야 할 탐색 명령어들을 보여줍니다. 데이터의 크기, 타입, 분포, 품질을 종합적으로 파악하는 체크리스트입니다.

첫 번째로, heightwidth 속성은 데이터프레임의 차원을 알려줍니다. Pandas에서 len(df)len(df.columns)를 각각 호출하는 것보다 의미가 명확합니다.

shape는 튜플로 (행, 열) 정보를 반환하여 한눈에 데이터 크기를 파악할 수 있습니다. 예를 들어, (1000000, 50)이면 백만 행에 50개 컬럼이 있다는 의미입니다.

그 다음으로, schema 속성은 딕셔너리 형태로 각 컬럼의 이름과 데이터 타입을 보여줍니다. Polars는 Utf8(문자열), Int64(정수), Float64(실수), Date(날짜), Boolean(참/거짓) 등 풍부한 타입 시스템을 가지고 있습니다.

타입이 예상과 다르다면 cast() 메서드로 변환하거나 로딩 시 dtypes 파라미터로 명시해야 합니다. 세 번째로, head()tail()은 데이터의 앞뒤를 샘플링하여 실제 값들을 확인합니다.

중간 부분만 보고 싶다면 df.slice(100, 5)로 100번째 행부터 5개를 볼 수 있습니다. 이는 데이터가 시간 순서대로 정렬되어 있을 때 시작과 끝의 패턴을 비교하는 데 유용합니다.

네 번째로, describe()는 수치형 컬럼에 대해 평균, 표준편차, 최소/최대값, 사분위수를 계산합니다. 이를 통해 데이터의 범위와 분포를 파악하고 이상치를 발견할 수 있습니다.

예를 들어, 나이 컬럼의 최대값이 200이라면 데이터 오류일 가능성이 높습니다. 마지막으로, null_count()는 각 컬럼의 결측치 개수를 세어줍니다.

결측치가 많은 컬럼은 제거하거나 대체 전략을 세워야 합니다. Polars는 None이 아닌 null을 사용하며, 이는 Arrow의 null 표현과 일치하여 성능이 최적화되어 있습니다.

여러분이 이 탐색 과정을 습관화하면 데이터 관련 버그를 조기에 발견하고 올바른 전처리 전략을 수립할 수 있습니다. 특히 팀 프로젝트에서는 이러한 정보를 문서화하여 공유하면 협업이 훨씬 수월해집니다.

Jupyter 노트북의 첫 셀에 이 코드들을 넣어두면 항상 최신 데이터 상태를 확인할 수 있습니다.

실전 팁

💡 df.glimpse()는 각 컬럼의 타입과 샘플 값을 세로로 보여주는 유용한 메서드입니다. 컬럼이 많을 때 추천합니다.

💡 문자열 컬럼의 유니크 값 개수는 df.select(pl.col("category").n_unique())로 확인할 수 있습니다.

💡 메모리 사용량을 확인하려면 df.estimated_size("mb")를 사용하세요. 최적화 전후를 비교할 때 유용합니다.

💡 Pandas와 달리 Polars는 인덱스가 없습니다. 필요하면 with_row_count() 메서드로 행 번호 컬럼을 추가할 수 있습니다.

💡 대용량 데이터프레임은 sample(n=1000)로 무작위 샘플을 추출하여 먼저 탐색하면 빠르게 감을 잡을 수 있습니다.


5. 컬럼 선택과 필터링 기초

시작하며

여러분이 데이터 분석을 하다 보면 전체 데이터가 아닌 특정 부분만 필요한 경우가 대부분입니다. 예를 들어, 100개의 컬럼 중 5개만 필요하거나, 전체 거래 중 특정 기간이나 특정 조건을 만족하는 데이터만 분석해야 하는 상황 말이죠.

이런 작업은 데이터 분석의 가장 기본이면서도 가장 자주 사용되는 연산입니다. 하지만 잘못된 방식으로 선택하고 필터링하면 코드가 복잡해지고 성능도 나빠집니다.

특히 대용량 데이터에서는 효율적인 선택과 필터링이 전체 분석 시간을 좌우합니다. 바로 이럴 때 필요한 것이 Polars의 표현식 기반 컬럼 선택과 필터링입니다.

SQL처럼 직관적이면서도 Python의 유연성을 모두 갖춘 방식으로 데이터를 다룰 수 있습니다.

개요

간단히 말해서, Polars에서 컬럼 선택은 select() 메서드를, 행 필터링은 filter() 메서드를 사용합니다. 두 연산 모두 pl.col() 표현식을 통해 컬럼을 참조합니다.

왜 표현식 기반 방식이 중요한지 실무 관점에서 살펴보면, 이 방식은 쿼리 최적화를 가능하게 합니다. Polars는 여러 연산을 체인으로 연결했을 때 전체 파이프라인을 분석하여 불필요한 중간 연산을 제거하고 최적의 실행 순서를 찾아냅니다.

예를 들어, 필터를 먼저 적용한 후 컬럼을 선택하는 것이 효율적이라고 판단하면 자동으로 순서를 바꿔서 실행합니다. 기존 Pandas에서는 df[['col1', 'col2']] 같은 인덱싱 방식을 사용했다면, Polars에서는 df.select([pl.col('col1'), pl.col('col2')]) 처럼 명시적인 표현식을 사용합니다.

이는 코드의 의도를 더 명확하게 만들어줍니다. 컬럼 선택과 필터링의 핵심 패턴은 네 가지입니다.

첫째, 개별 컬럼 선택과 다중 컬럼 선택. 둘째, 타입 기반 컬럼 선택 (모든 숫자형 컬럼만 등).

셋째, 조건 기반 필터링 (비교, 논리 연산). 넷째, 메서드 체이닝으로 여러 연산 결합.

이러한 패턴들을 마스터하면 복잡한 데이터 추출 작업을 간결하게 표현할 수 있습니다.

코드 예제

import polars as pl

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

# 특정 컬럼들만 선택
selected = df.select([
    pl.col("date"),
    pl.col("product"),
    pl.col("sales")
])

# 모든 숫자형 컬럼 선택
numeric = df.select(pl.col(pl.NUMERIC_DTYPES))

# 조건으로 행 필터링
high_sales = df.filter(pl.col("sales") > 10000)

# 여러 조건 결합 (AND)
filtered = df.filter(
    (pl.col("sales") > 5000) &
    (pl.col("category") == "Electronics")
)

# 체인으로 선택과 필터링 결합
result = df.filter(pl.col("date") >= "2024-01-01").select([
    pl.col("product"),
    pl.col("sales"),
    (pl.col("sales") * 0.1).alias("commission")  # 새 컬럼 계산
])

설명

이것이 하는 일: 위 코드는 Polars에서 가장 자주 사용하는 데이터 부분집합 추출 패턴들을 보여줍니다. 실제 분석 작업의 80% 이상이 이러한 선택과 필터링 조합으로 이루어집니다.

첫 번째로, select() 메서드는 리스트로 전달된 표현식들을 평가하여 새로운 데이터프레임을 반환합니다. pl.col("컬럼명") 표현식은 해당 컬럼을 참조하는 Lazy한 객체로, 실제 실행 시점에 최적화됩니다.

원본 데이터프레임은 변경되지 않으며 (불변성), 새로운 데이터프레임이 생성됩니다. 이는 실수로 원본 데이터를 수정하는 버그를 방지합니다.

그 다음으로, pl.col(pl.NUMERIC_DTYPES) 같은 타입 기반 선택은 매우 강력합니다. 수십 개의 컬럼 중에서 숫자형만 골라내야 할 때 일일이 이름을 나열할 필요가 없습니다.

정규표현식으로 컬럼 이름을 매칭하는 pl.col("^amount_.*$")도 가능합니다. 이는 컬럼 이름이 패턴을 따르는 잘 설계된 데이터셋에서 매우 유용합니다.

세 번째로, filter() 메서드는 불리언 조건을 만족하는 행만 추출합니다. pl.col("sales") > 10000은 각 행의 sales 값을 10000과 비교하여 True/False 시리즈를 만들고, True인 행만 선택합니다.

이 과정은 벡터화되어 있어 매우 빠르게 실행됩니다. Pandas의 df[df['sales'] > 10000]와 비슷하지만, Polars는 내부적으로 SIMD 연산을 사용하여 더 빠릅니다.

네 번째로, 여러 조건을 결합할 때는 &(AND), |(OR), ~(NOT) 연산자를 사용합니다. 주의할 점은 각 조건을 괄호로 묶어야 한다는 것입니다.

Python의 연산자 우선순위 때문입니다. & 대신 and를 쓰면 에러가 발생하니 조심하세요.

마지막으로, 메서드 체이닝은 Polars의 진수입니다. filter()select()를 연결하면서 새 컬럼도 계산할 수 있습니다.

alias()는 계산된 컬럼에 이름을 부여합니다. 전체 파이프라인이 하나의 흐름으로 읽혀서 코드의 가독성이 크게 향상됩니다.

여러분이 이러한 패턴들을 자유자재로 사용하면 복잡한 데이터 추출 요구사항을 몇 줄의 코드로 해결할 수 있습니다. 예를 들어, "2024년 1월 이후 전자제품 카테고리에서 판매액이 5000 이상인 거래의 제품명과 수수료를 보여주세요" 같은 요청을 위 코드처럼 간결하게 표현할 수 있습니다.

실전 팁

💡 select() 안에서 여러 컬럼을 한번에 변환하려면 pl.col(["col1", "col2"]).cast(pl.Float64)처럼 리스트를 전달하세요.

💡 문자열 컬럼의 패턴 매칭은 filter(pl.col("product").str.contains("iPhone"))로 할 수 있습니다.

💡 NULL 값을 필터링하려면 filter(pl.col("column").is_not_null())을 사용하세요.

💡 상위 N개 행만 원한다면 filter() 대신 head(N) 또는 limit(N)이 더 효율적입니다.

💡 복잡한 조건은 변수로 분리하면 가독성이 좋아집니다: cond1 = pl.col("sales") > 5000, df.filter(cond1 & cond2)


6. 데이터 정렬과 순위 매기기

시작하며

여러분이 판매 실적을 분석할 때 "매출 상위 10개 제품은 무엇인가요?"라는 질문을 받아본 적 있나요? 혹은 시계열 데이터를 날짜 순으로 정렬해야 하는 상황은요?

이런 정렬 작업은 데이터 분석에서 빠질 수 없는 핵심 연산입니다. 이런 작업은 단순해 보이지만, 대용량 데이터에서는 정렬이 가장 시간을 많이 잡아먹는 연산 중 하나입니다.

수백만 행을 정렬하는 데 몇 분씩 걸린다면 탐색적 분석이 불가능해집니다. 또한 여러 컬럼을 기준으로 복합 정렬을 해야 하는 경우도 많습니다.

바로 이럴 때 필요한 것이 Polars의 효율적인 정렬 기능입니다. 멀티스레드를 활용한 병렬 정렬로 빠른 속도를 제공하며, 직관적인 API로 복잡한 정렬도 쉽게 표현할 수 있습니다.

개요

간단히 말해서, Polars에서 정렬은 sort() 메서드를 사용하며, 하나 또는 여러 컬럼을 기준으로 오름차순이나 내림차순 정렬이 가능합니다. 순위는 rank() 메서드로 매길 수 있습니다.

왜 Polars의 정렬이 빠른지 실무 관점에서 살펴보면, 내부적으로 멀티스레드 정렬 알고리즘을 사용하기 때문입니다. 싱글스레드로 동작하는 Pandas와 달리 모든 CPU 코어를 활용하여 정렬합니다.

예를 들어, 천만 행 데이터를 정렬할 때 Pandas는 30초가 걸리지만 Polars는 4코어 머신에서 5초면 충분합니다. 기존에는 df.sort_values(by='column') 같은 Pandas 방식을 사용했다면, Polars에서는 df.sort('column') 또는 표현식을 사용한 df.sort(pl.col('column').desc())로 더 명시적으로 작성합니다.

정렬의 핵심 기능은 네 가지입니다. 첫째, 단일/다중 컬럼 정렬.

둘째, 오름차순/내림차순 제어. 셋째, NULL 값의 위치 제어 (처음 또는 끝).

넷째, 순위 매기기와 타이 처리 방법 선택. 이러한 옵션들이 다양한 정렬 요구사항을 충족시켜줍니다.

코드 예제

import polars as pl

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

# 단일 컬럼 오름차순 정렬
sorted_asc = df.sort("sales")

# 단일 컬럼 내림차순 정렬
sorted_desc = df.sort("sales", descending=True)

# 여러 컬럼으로 정렬 (첫 번째 기준, 두 번째 기준)
multi_sorted = df.sort(["category", "sales"], descending=[False, True])

# 표현식을 사용한 정렬
expr_sorted = df.sort(pl.col("sales").desc())

# 순위 매기기
ranked = df.with_columns([
    pl.col("sales").rank(method="ordinal").alias("sales_rank"),
    pl.col("sales").rank(method="dense", descending=True).alias("dense_rank")
])

# 상위 10개만 추출
top10 = df.sort("sales", descending=True).head(10)

설명

이것이 하는 일: 위 코드는 데이터 정렬의 다양한 패턴과 순위 매기기 방법을 보여줍니다. 실무에서 "Top N" 분석이나 시계열 데이터 정리에 필수적인 기능들입니다.

첫 번째로, 기본 sort() 메서드는 지정된 컬럼을 기준으로 전체 데이터프레임을 정렬합니다. 기본값은 오름차순(작은 값부터 큰 값)이며, 원본은 변경하지 않고 새로운 정렬된 데이터프레임을 반환합니다.

Polars는 정렬 시 인덱스를 재배치하는 Pandas와 달리 인덱스 개념이 없어서 정렬이 더 단순하고 빠릅니다. 그 다음으로, descending=True 파라미터는 내림차순(큰 값부터 작은 값)으로 정렬합니다.

매출 상위 제품을 찾거나 최신 날짜를 먼저 보고 싶을 때 사용합니다. 내부적으로는 비교 연산의 방향만 바뀌므로 성능 차이는 거의 없습니다.

세 번째로, 다중 컬럼 정렬은 리스트로 컬럼 이름들을 전달합니다. 첫 번째 컬럼으로 먼저 정렬하고, 같은 값들은 두 번째 컬럼으로 정렬합니다.

descending 파라미터도 리스트로 각 컬럼마다 방향을 지정할 수 있습니다. 예를 들어, 카테고리별로 그룹화하면서 각 그룹 내에서는 매출 높은 순으로 보고 싶을 때 유용합니다.

네 번째로, 표현식 기반 정렬은 pl.col("sales").desc() 같은 방식으로 더 복잡한 정렬 기준을 만들 수 있습니다. 예를 들어, pl.col("name").str.len_chars() 로 문자열 길이 순으로 정렬하거나, 계산된 값으로 정렬할 수 있습니다.

다섯 번째로, rank() 메서드는 각 값의 순위를 계산합니다. method="ordinal"은 1, 2, 3 같이 연속된 순위를 부여하고, method="dense"는 같은 값에 같은 순위를 주되 다음 순위를 건너뛰지 않습니다.

예를 들어, 같은 점수가 2명이면 둘 다 1등이고 다음은 2등이 됩니다 (dense 방식). with_columns()는 기존 컬럼을 유지하면서 새 컬럼을 추가합니다.

마지막으로, 정렬 후 head(10)으로 상위 10개만 추출하는 패턴은 실무에서 매우 자주 사용됩니다. 전체를 정렬한 후 잘라내므로, 정말 상위 N개만 필요하다면 top_k(10, by="sales") 메서드가 더 효율적입니다.

이는 부분 정렬 알고리즘을 사용하여 전체 정렬보다 빠릅니다. 여러분이 이러한 정렬 기법들을 활용하면 데이터 탐색 시 원하는 관점으로 데이터를 빠르게 재배치할 수 있습니다.

시계열 분석에서 날짜 순 정렬, 판매 분석에서 매출 순 정렬, 고객 분석에서 구매액 순 정렬 등 다양한 시나리오에 적용할 수 있습니다.

실전 팁

💡 NULL 값의 위치를 제어하려면 nulls_last=True 파라미터를 사용하세요. 기본값은 NULL이 먼저 옵니다.

💡 정렬된 데이터프레임을 변수에 저장하지 않고 바로 체인으로 연결하면 메모리를 절약할 수 있습니다.

💡 is_sorted() 메서드로 데이터가 이미 정렬되어 있는지 확인할 수 있습니다. 불필요한 재정렬을 피하세요.

💡 대용량 데이터에서 상위 N개만 필요하면 top_k(N, by="column")을 사용하세요. 전체 정렬보다 훨씬 빠릅니다.

💡 문자열을 자연스러운 순서로 정렬하려면 (예: file1, file2, file10) 별도의 로직이 필요하니 주의하세요.


7. 그룹화와 집계 연산

시작하며

여러분이 "카테고리별 평균 판매액은 얼마인가요?" 또는 "지역별 고객 수를 알려주세요" 같은 질문을 받았을 때 어떻게 하시나요? 이런 질문들은 모두 그룹화(grouping)와 집계(aggregation) 연산으로 해결할 수 있습니다.

이런 연산은 데이터 분석의 핵심입니다. SQL의 GROUP BY와 같은 개념으로, 데이터를 특정 기준으로 그룹화한 후 각 그룹에 대한 통계를 계산합니다.

하지만 Pandas의 groupby는 대용량 데이터에서 느리고, 문법도 직관적이지 않아서 복잡한 집계를 표현하기 어렵습니다. 바로 이럴 때 필요한 것이 Polars의 강력한 그룹화 기능입니다.

SQL처럼 명확한 문법과 병렬 처리로 빠른 성능을 동시에 제공합니다.

개요

간단히 말해서, Polars에서 그룹화는 group_by() 메서드로 하고, 그 뒤에 agg() 메서드로 집계 함수들을 지정합니다. 여러 집계를 동시에 수행할 수 있습니다.

왜 Polars의 그룹화가 강력한지 실무 관점에서 살펴보면, 표현식 시스템 덕분에 복잡한 집계도 간결하게 표현할 수 있기 때문입니다. 하나의 agg() 안에서 평균, 합계, 개수, 최대/최소값 등을 동시에 계산하면서, 각각에 조건을 붙이거나 변환을 적용할 수 있습니다.

예를 들어, "카테고리별 평균 판매액과 총 판매액, 그리고 5000 이상 거래 건수"를 한 번에 구할 수 있습니다. 기존 Pandas에서는 df.groupby('category')['sales'].mean() 같은 방식이었다면, Polars에서는 df.group_by('category').agg(pl.col('sales').mean())로 더 명시적으로 작성합니다.

또한 Pandas는 groupby 결과가 특수한 객체인 반면, Polars는 그냥 데이터프레임을 반환하여 이후 처리가 간단합니다. 그룹화와 집계의 핵심 패턴은 다섯 가지입니다.

첫째, 단일/다중 컬럼 그룹화. 둘째, 다양한 집계 함수 (sum, mean, count, min, max 등).

셋째, 여러 집계를 동시에 수행. 넷째, 조건부 집계 (필터를 적용한 집계).

다섯째, 그룹별 순위나 누적 계산. 이러한 패턴들이 거의 모든 그룹 분석 요구사항을 충족시켜줍니다.

코드 예제

import polars as pl

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

# 기본 그룹화와 집계
category_sales = df.group_by("category").agg(
    pl.col("sales").sum().alias("total_sales")
)

# 여러 집계 함수 동시 적용
multi_agg = df.group_by("category").agg([
    pl.col("sales").sum().alias("total_sales"),
    pl.col("sales").mean().alias("avg_sales"),
    pl.col("sales").count().alias("transaction_count"),
    pl.col("sales").max().alias("max_sales")
])

# 다중 컬럼으로 그룹화
region_category = df.group_by(["region", "category"]).agg(
    pl.col("sales").sum().alias("total_sales")
)

# 조건부 집계 - 특정 조건을 만족하는 것만 계산
conditional = df.group_by("category").agg([
    pl.col("sales").filter(pl.col("sales") > 5000).count().alias("high_value_count"),
    pl.col("sales").filter(pl.col("sales") > 5000).sum().alias("high_value_total")
])

설명

이것이 하는 일: 위 코드는 데이터를 그룹화하고 각 그룹의 통계를 계산하는 다양한 방법을 보여줍니다. 이는 "분류별 요약"이 필요한 모든 비즈니스 질문에 답하는 핵심 기법입니다.

첫 번째로, group_by("category")는 category 컬럼의 고유한 값들로 데이터를 그룹으로 나눕니다. 예를 들어, Electronics, Clothing, Food 세 카테고리가 있다면 세 개의 그룹이 만들어집니다.

이 시점에서는 아직 계산이 일어나지 않고 그룹화 계획만 세워집니다. 그 다음으로, agg() 메서드가 각 그룹에 집계 함수를 적용합니다.

pl.col("sales").sum()은 각 그룹의 sales 값들을 모두 더합니다. alias("total_sales")는 결과 컬럼의 이름을 지정합니다.

Polars는 내부적으로 해시 테이블을 사용하여 효율적으로 그룹을 관리하고, 각 그룹의 집계를 병렬로 계산합니다. 세 번째로, 여러 집계를 리스트로 전달하면 한 번의 그룹화로 여러 통계를 동시에 얻을 수 있습니다.

이는 매우 효율적인데, Polars가 데이터를 한 번만 스캔하면서 모든 집계를 계산하기 때문입니다. Pandas에서는 각 집계마다 별도의 groupby를 호출해야 하는 경우가 많아 성능이 떨어집니다.

네 번째로, 다중 컬럼 그룹화는 계층적 분류를 만듭니다. ["region", "category"]로 그룹화하면 지역과 카테고리의 모든 조합이 그룹이 됩니다.

예를 들어, (서울, Electronics), (서울, Clothing), (부산, Electronics) 같은 그룹들이 생성됩니다. 이는 피벗 테이블이나 다차원 분석의 기초가 됩니다.

마지막으로, 조건부 집계는 매우 강력한 기능입니다. pl.col("sales").filter(...).count()는 각 그룹 내에서 조건을 만족하는 행만 세어줍니다.

이를 통해 "카테고리별로 고액 거래(5000 이상)가 몇 건이고 총액은 얼마인가?" 같은 복잡한 질문에 답할 수 있습니다. 전통적인 방법으로는 여러 단계의 필터링과 그룹화가 필요했지만, Polars는 한 번에 표현할 수 있습니다.

여러분이 이러한 그룹화 기법들을 마스터하면 비즈니스 인텔리전스 대시보드에 필요한 거의 모든 집계 쿼리를 작성할 수 있습니다. 매출 리포트, 고객 세그먼트 분석, 제품 성과 비교 등 실무에서 매일 마주하는 분석 작업들이 몇 줄의 코드로 해결됩니다.

실전 팁

💡 그룹별 행 개수만 필요하면 agg(pl.count()) 또는 더 간단하게 len()을 사용할 수 있습니다.

💡 그룹화 후 정렬하려면 group_by().agg().sort()로 체인을 연결하세요. 예: 매출 순으로 카테고리 정렬.

💡 maintain_order=True 파라미터를 group_by에 전달하면 그룹의 원래 순서를 유지합니다. 성능은 약간 떨어집니다.

💡 Pandas의 value_counts()는 Polars에서 group_by("col").agg(pl.count()) 또는 col.value_counts()로 대체할 수 있습니다.

💡 그룹별 첫 번째/마지막 값은 first(), last() 메서드로 얻을 수 있습니다. 시계열 데이터에서 유용합니다.


8. Lazy 모드와 쿼리 최적화

시작하며

여러분이 복잡한 데이터 파이프라인을 작성했는데, 실행 시간이 예상보다 훨씬 오래 걸린 경험이 있나요? 여러 단계의 필터링, 조인, 집계를 거치다 보면 중간 결과들이 메모리를 낭비하고 불필요한 연산이 반복되기 쉽습니다.

이런 문제는 특히 대용량 데이터 처리에서 심각합니다. 각 단계마다 전체 데이터를 처리하고 중간 결과를 저장하면, 메모리 부족이 발생하거나 처리 시간이 기하급수적으로 늘어납니다.

실제로 많은 데이터 파이프라인에서 최적화되지 않은 쿼리가 성능 병목의 주요 원인입니다. 바로 이럴 때 필요한 것이 Polars의 Lazy 모드입니다.

쿼리를 바로 실행하지 않고 계획을 세운 후, 전체를 분석하여 최적화한 뒤 한 번에 실행합니다.

개요

간단히 말해서, Lazy 모드는 scan_csv() 같은 함수로 시작하여 쿼리를 구성하고, 마지막에 collect()를 호출해서 실제 실행하는 방식입니다. 중간 과정은 실행 계획만 만들어집니다.

왜 Lazy 모드가 중요한지 실무 관점에서 살펴보면, 쿼리 최적화 엔진이 전체 파이프라인을 보고 불필요한 연산을 제거하거나 순서를 재배치할 수 있기 때문입니다. 예를 들어, 필터를 나중에 적용했더라도 엔진이 분석하여 더 일찍 적용하는 것이 효율적이라고 판단하면 자동으로 순서를 바꿉니다.

또한 사용하지 않는 컬럼은 아예 읽지 않습니다 (Projection Pushdown). 기존 Eager 모드(즉시 실행)에서는 read_csv() 후 각 메서드 호출마다 즉시 연산이 수행되었다면, Lazy 모드에서는 scan_csv() 후 모든 변환을 연결하고 마지막에 한 번에 실행합니다.

이는 SQL 데이터베이스의 쿼리 플래너와 유사한 개념입니다. Lazy 모드의 핵심 이점은 네 가지입니다.

첫째, Projection Pushdown - 필요한 컬럼만 읽기. 둘째, Predicate Pushdown - 필터를 가능한 한 일찍 적용.

셋째, 불필요한 중간 결과 제거. 넷째, 메모리 효율적인 스트리밍 처리.

이러한 최적화들이 자동으로 적용되어 코드를 바꾸지 않고도 성능이 크게 향상됩니다.

코드 예제

import polars as pl

# Lazy 모드로 CSV 스캔 (아직 읽지 않음)
lazy_df = pl.scan_csv("large_sales_data.csv")

# 쿼리 체인 구성 (아직 실행되지 않음)
query = (
    lazy_df
    .filter(pl.col("date") >= "2024-01-01")  # 날짜 필터
    .filter(pl.col("sales") > 1000)           # 판매액 필터
    .select([                                  # 필요한 컬럼만 선택
        pl.col("date"),
        pl.col("product"),
        pl.col("sales"),
        (pl.col("sales") * 0.1).alias("commission")
    ])
    .group_by("product")                       # 제품별 그룹화
    .agg([
        pl.col("sales").sum().alias("total_sales"),
        pl.col("commission").sum().alias("total_commission")
    ])
    .sort("total_sales", descending=True)      # 매출 순 정렬
    .head(10)                                  # 상위 10개
)

# 실행 계획 확인 (최적화 내용 보기)
print(query.explain())

# 실제 실행
result = query.collect()

설명

이것이 하는 일: 위 코드는 Lazy 모드의 전형적인 사용 패턴을 보여줍니다. 복잡한 데이터 파이프라인을 구성하고, 최적화 엔진의 도움을 받아 효율적으로 실행하는 방법입니다.

첫 번째로, scan_csv()는 파일을 실제로 읽지 않고 메타데이터만 확인합니다. 컬럼 이름과 타입을 파악하지만 데이터는 메모리에 로드하지 않습니다.

이는 수 기가바이트 파일도 즉시 "스캔"할 수 있다는 의미입니다. 반환되는 LazyFrame 객체는 쿼리 계획을 담고 있는 컨테이너입니다.

그 다음으로, 각 메서드 호출 (filter(), select() 등)은 즉시 실행되지 않고 쿼리 그래프에 노드로 추가됩니다. 이 시점에서는 데이터가 처리되지 않으므로 매우 빠릅니다.

여러분은 마치 SQL 쿼리를 작성하듯이 로직을 표현하기만 하면 됩니다. 세 번째로, explain() 메서드는 최적화된 실행 계획을 보여줍니다.

여기서 Polars가 어떤 최적화를 적용했는지 확인할 수 있습니다. 예를 들어, "FILTER pushed down to CSV scan"이라는 메시지가 나오면 필터 조건이 파일 읽기 단계로 이동되어 불필요한 행을 아예 읽지 않는다는 의미입니다.

또한 "PROJECT 3/50"이라고 나오면 50개 컬럼 중 3개만 읽는다는 뜻입니다. 네 번째로, collect()를 호출하는 순간 실제 실행이 시작됩니다.

Polars는 최적화된 계획에 따라 파일을 읽고, 필터를 적용하고, 필요한 컬럼만 추출하고, 그룹화와 집계를 수행합니다. 모든 단계가 멀티스레드로 병렬 처리되며, 가능한 경우 파이프라이닝을 통해 중간 결과를 메모리에 완전히 저장하지 않고 스트리밍합니다.

이 예제에서 Lazy 모드의 이점을 구체적으로 살펴보면: (1) 날짜와 판매액 필터가 CSV 읽기 단계로 이동되어 조건에 맞지 않는 행은 아예 파싱하지 않습니다. (2) select()에서 4개 컬럼만 지정했으므로 나머지 컬럼은 읽지 않습니다.

(3) head(10)이 있으므로 정렬 시 부분 정렬 알고리즘을 사용할 수 있습니다. 이러한 최적화들이 자동으로 적용되어 Eager 모드보다 5-10배 빠를 수 있습니다.

여러분이 Lazy 모드를 사용하면 복잡한 데이터 파이프라인에서 최고의 성능을 얻을 수 있습니다. 특히 프로덕션 환경에서 매일 실행되는 ETL 파이프라인, 대시보드 백엔드 쿼리, 배치 분석 작업에 Lazy 모드를 적용하면 인프라 비용을 크게 절감할 수 있습니다.

실전 팁

💡 Lazy 모드와 Eager 모드는 섞어 사용할 수 있습니다. collect()로 Lazy를 Eager로 바꾸고, lazy()로 Eager를 Lazy로 바꿀 수 있습니다.

💡 explain(optimized=True)로 최적화 후 계획을, explain(optimized=False)로 최적화 전 계획을 볼 수 있습니다. 비교해보면 학습에 도움됩니다.

💡 scan_parquet(), scan_ipc(), scan_ndjson() 등 다른 파일 포맷도 Lazy 스캔을 지원합니다.

💡 쿼리가 너무 복잡하면 중간에 collect()를 호출해서 체크포인트를 만드는 것도 전략입니다. 디버깅이 쉬워집니다.

💡 streaming=True 파라미터를 collect(streaming=True)에 전달하면 메모리보다 큰 데이터도 스트리밍으로 처리할 수 있습니다 (실험적 기능).


9. Jupyter 노트북에서 Polars 사용하기

시작하며

여러분이 데이터 분석을 할 때 가장 많이 사용하는 도구가 무엇인가요? 아마 Jupyter 노트북일 겁니다.

코드를 단계별로 실행하고, 중간 결과를 확인하고, 시각화를 바로 볼 수 있는 대화형 환경은 탐색적 데이터 분석에 최적입니다. 하지만 Jupyter에서 대용량 데이터를 다루면 몇 가지 문제가 생깁니다.

데이터프레임을 출력할 때 너무 많은 행이 표시되어 스크롤이 끝없이 길어지거나, 연산이 오래 걸려도 진행 상황을 알 수 없어서 답답합니다. 또한 여러 실험을 하다 보면 메모리가 부족해지는 경우도 많습니다.

바로 이럴 때 필요한 것이 Jupyter에 최적화된 Polars 설정과 사용 패턴입니다. 출력 형식을 조정하고, 유용한 매직 커맨드를 활용하면 훨씬 생산적인 분석 환경을 만들 수 있습니다.

개요

간단히 말해서, Polars는 Jupyter 노트북에서 자동으로 깔끔한 HTML 테이블로 표시되며, 다양한 설정으로 출력을 제어할 수 있습니다. 또한 Pandas와의 상호 변환도 간단하게 지원합니다.

왜 Jupyter에서 Polars를 제대로 설정해야 하는지 실무 관점에서 살펴보면, 기본 설정으로는 대용량 데이터프레임의 극히 일부만 보이거나, 반대로 너무 많이 보여서 불편할 수 있기 때문입니다. 예를 들어, 100만 행 데이터프레임을 출력했을 때 처음 5행과 마지막 5행만 보면서 중간 패턴을 파악하기는 어렵습니다.

기존 Pandas에서는 pd.set_option()으로 출력 옵션을 설정했다면, Polars에서는 pl.Config.set_tbl_rows()와 같은 메서드로 더 구조화된 방식으로 설정합니다. Jupyter에서 Polars를 효과적으로 사용하는 핵심 요소는 다섯 가지입니다.

첫째, 출력 행/컬럼 수 조정. 둘째, 시각화 라이브러리와의 통합.

셋째, 메모리 관리와 변수 정리. 넷째, 진행 상황 표시.

다섯째, Pandas와의 상호 변환. 이러한 요소들이 갖춰져야 효율적인 분석 워크플로우를 구축할 수 있습니다.

코드 예제

import polars as pl
import matplotlib.pyplot as plt

# Polars 출력 설정 (노트북 시작 시 실행)
pl.Config.set_tbl_rows(20)  # 표시할 행 수 (기본 10)
pl.Config.set_tbl_cols(10)  # 표시할 컬럼 수 (기본 8)
pl.Config.set_fmt_str_lengths(100)  # 문자열 최대 표시 길이

# 데이터 로드 및 탐색
df = pl.read_csv("sales_data.csv")
df  # 자동으로 HTML 테이블로 렌더링됨

# 시각화를 위해 Pandas로 변환 (일부 라이브러리 호환성)
pandas_df = df.to_pandas()
pandas_df.plot(x="date", y="sales", kind="line")
plt.show()

# 또는 Polars에서 직접 데이터 추출하여 시각화
plt.figure(figsize=(10, 6))
plt.plot(df["date"], df["sales"])
plt.xlabel("Date")
plt.ylabel("Sales")
plt.title("Sales Trend")
plt.show()

# Pandas에서 Polars로 변환
pandas_data = pandas_df  # 기존 Pandas 데이터프레임
polars_data = pl.from_pandas(pandas_data)

# 메모리 사용량 확인
print(f"Estimated size: {df.estimated_size('mb'):.2f} MB")

설명

이것이 하는 일: 위 코드는 Jupyter 노트북에서 Polars를 최적으로 사용하기 위한 설정과 패턴을 보여줍니다. 데이터 탐색부터 시각화, 다른 라이브러리와의 통합까지 다룹니다.

첫 번째로, pl.Config.set_tbl_rows(20)은 데이터프레임을 출력할 때 보여줄 행 수를 설정합니다. 기본값 10보다 많이 보고 싶다면 늘리고, 출력을 간결하게 하고 싶다면 줄일 수 있습니다.

이 설정은 현재 Python 세션 동안 유지되므로, 노트북의 첫 셀에 넣어두면 편리합니다. Jupyter는 Polars 데이터프레임을 자동으로 인식하여 보기 좋은 HTML 테이블로 렌더링합니다.

그 다음으로, set_tbl_cols()set_fmt_str_lengths()는 각각 표시할 컬럼 수와 문자열의 최대 길이를 제어합니다. 컬럼이 많은 와이드 데이터셋에서는 set_tbl_cols(20)처럼 늘려서 더 많이 보거나, 반대로 중요한 컬럼만 선택한 후 출력하는 것이 좋습니다.

문자열이 잘리는 것을 방지하려면 set_fmt_str_lengths()를 크게 설정하세요. 세 번째로, Jupyter에서 데이터프레임 변수를 마지막 줄에 쓰면 자동으로 출력됩니다.

print(df) 대신 그냥 df만 써도 되며, 이 방식이 더 깔끔한 HTML 렌더링을 제공합니다. 여러 데이터프레임을 동시에 보고 싶다면 display(df1), display(df2) 함수를 사용하세요.

네 번째로, 시각화 라이브러리 대부분은 Pandas를 기대하므로 to_pandas()로 변환합니다. 이 변환은 제로 카피가 가능한 경우가 많아서 빠릅니다.

하지만 Polars 데이터프레임에서 직접 NumPy 배열을 추출하는 것도 가능합니다. df["column"].to_numpy() 또는 df.select(pl.col("column")).to_series()로 개별 컬럼을 가져올 수 있습니다.

다섯 번째로, pl.from_pandas()는 반대 방향 변환으로, 기존 Pandas 기반 코드와 통합할 때 유용합니다. 예를 들어, scikit-learn 같은 라이브러리는 Pandas를 반환하는 경우가 많은데, 이를 Polars로 변환하여 이후 처리에서 성능 이점을 얻을 수 있습니다.

변환 시 데이터 타입이 자동으로 매핑되지만, 복잡한 타입은 확인이 필요합니다. 마지막으로, estimated_size()는 데이터프레임이 메모리에서 차지하는 용량을 추정합니다.

Jupyter에서 여러 실험을 하다 보면 메모리가 부족해질 수 있는데, 이 메서드로 큰 객체를 찾아서 del df_large 같은 명령으로 삭제하면 메모리를 확보할 수 있습니다. 또한 %who DataFrame 매직 커맨드로 현재 네임스페이스의 데이터프레임들을 확인할 수 있습니다.

여러분이 이러한 Jupyter 사용 패턴을 익히면 탐색적 데이터 분석이 훨씬 수월해집니다. 설정을 한 번 해두면 모든 노트북에서 일관된 출력 형식을 유지할 수 있고, 필요에 따라 Pandas 에코시스템과 자유롭게 전환할 수 있습니다.

노트북을 팀과 공유할 때도 출력 형식이 깔끔하게 유지되어 협업에 도움이 됩니다.

실전 팁

💡 %%time 매직 커맨드를 셀 첫 줄에 넣으면 실행 시간을 측정할 수 있습니다. Pandas와 성능 비교할 때 유용합니다.

💡 긴 연산을 실행할 때는 from tqdm.auto import tqdm과 함께 진행 상황 바를 추가할 수 있습니다 (커스텀 루프에서).

💡 %load_ext autoreload%autoreload 2를 설정하면 외부 Python 파일을 수정했을 때 자동으로 리로드됩니다.

💡 Plotly를 사용하면 인터랙티브 차트를 만들 수 있습니다. df.to_pandas().plot(backend='plotly') 시도해보세요.

💡 출력 설정을 자주 바꾼다면 pl.Config의 컨텍스트 매니저를 사용하세요: with pl.Config(tbl_rows=50): display(df)


10. VSCode에서 Polars 개발 환경 구축하기

시작하며

여러분이 데이터 분석 프로젝트를 본격적으로 진행할 때, Jupyter 노트북만으로는 부족한 순간이 옵니다. 재사용 가능한 함수를 만들거나, 모듈을 구조화하거나, 버전 관리를 하려면 제대로 된 IDE가 필요합니다.

VSCode는 가볍고 확장성이 뛰어나서 Python 개발에 가장 인기 있는 에디터입니다. 하지만 기본 설정으로는 Polars 개발에 최적화되어 있지 않습니다.

자동완성이 제대로 작동하지 않거나, 타입 힌트가 표시되지 않으면 개발 생산성이 크게 떨어집니다. 바로 이럴 때 필요한 것이 Polars를 위한 VSCode 최적 설정입니다.

Python 확장, 타입 체커, 린터를 적절히 구성하면 프로페셔널한 개발 환경을 만들 수 있습니다.

개요

간단히 말해서, VSCode에서 Polars를 개발하려면 Python 확장을 설치하고, 가상환경을 선택하고, Pylance를 타입 체커로 설정하면 됩니다. 추가로 Black이나 Ruff 같은 포매터를 설정하면 코드 품질도 향상됩니다.

왜 제대로 된 개발 환경이 중요한지 실무 관점에서 살펴보면, 자동완성과 타입 힌트가 있으면 API를 외우지 않아도 되고 오타로 인한 버그를 줄일 수 있기 때문입니다. 예를 들어, pl.col()을 입력할 때 사용 가능한 메서드들이 자동으로 제안되면 문서를 일일이 찾아보지 않아도 됩니다.

기존에는 메모장이나 기본 에디터로 Python을 작성했다면, VSCode에서는 실시간 오류 표시, 디버거, 통합 터미널, Git 통합 등 현대적인 개발 도구를 모두 사용할 수 있습니다. VSCode Polars 개발 환경의 핵심 구성 요소는 다섯 가지입니다.

첫째, Python 확장과 가상환경 선택. 둘째, Pylance 타입 체커 설정.

셋째, 코드 포매터 (Black/Ruff). 넷째, 린터 (Flake8/Ruff).

다섯째, Jupyter 노트북 지원. 이러한 요소들이 통합되어 강력한 개발 환경을 만들어줍니다.

코드 예제

# VSCode settings.json 설정 예시 (프로젝트 루트의 .vscode/settings.json)
{
    "python.defaultInterpreterPath": "./polars_env/bin/python",
    "python.linting.enabled": true,
    "python.linting.ruffEnabled": true,
    "python.formatting.provider": "black",
    "editor.formatOnSave": true,
    "python.analysis.typeCheckingMode": "basic",
    "python.analysis.autoImportCompletions": true,
    "[python]": {
        "editor.defaultFormatter": "ms-python.black-formatter",
        "editor.codeActionsOnSave": {
            "source.organizeImports": true
        }
    }
}

# 간단한 Polars 스크립트 예시 (VSCode에서 작성)
import polars as pl

def load_and_summarize(file_path: str) -> pl.DataFrame:
    """CSV 파일을 로드하고 카테고리별 요약을 반환합니다."""
    df = pl.read_csv(file_path)

    summary = df.group_by("category").agg([
        pl.col("sales").sum().alias("total_sales"),
        pl.col("sales").mean().alias("avg_sales")
    ])

    return summary.sort("total_sales", descending=True)

if __name__ == "__main__":
    result = load_and_summarize("sales_data.csv")
    print(result)

설명

이것이 하는 일: 위 설정과 코드는 VSCode에서 Polars 프로젝트를 개발하기 위한 완전한 환경을 보여줍니다. 설정 파일부터 실제 코드 작성까지 프로페셔널한 개발 워크플로우를 다룹니다.

첫 번째로, settings.json 파일은 프로젝트별 VSCode 설정을 담고 있습니다. .vscode 폴더에 이 파일을 만들면 팀원들이 프로젝트를 클론했을 때 같은 설정이 자동으로 적용됩니다.

python.defaultInterpreterPath는 가상환경의 Python 경로를 지정하여 올바른 패키지가 인식되도록 합니다. 그 다음으로, 린팅과 포매팅 설정이 코드 품질을 자동으로 관리합니다.

ruffEnabled는 빠른 린터인 Ruff를 활성화하고, formatOnSave는 파일을 저장할 때마다 Black 포매터가 자동으로 코드를 정리합니다. 이를 통해 일관된 코드 스타일을 유지하고, PEP 8 규칙을 따르며, 잠재적인 버그를 조기에 발견할 수 있습니다.

세 번째로, typeCheckingMode: "basic"은 Pylance의 타입 체킹을 활성화합니다. Polars는 타입 힌트가 잘 되어 있어서, 잘못된 타입을 전달하면 코드를 실행하기 전에 빨간 밑줄로 경고해줍니다.

예를 들어, pl.col(123) 처럼 숫자를 전달하면 문자열을 기대한다고 알려줍니다. 네 번째로, autoImportCompletions는 자동완성 시 필요한 import 문을 자동으로 추가해줍니다.

pl.DataFrame을 타이핑하면 import polars as pl이 자동으로 파일 상단에 추가되는 식입니다. source.organizeImports는 사용하지 않는 import를 제거하고 정렬합니다.

다섯 번째로, 예시 코드는 타입 힌트를 사용한 함수를 보여줍니다. file_path: str-> pl.DataFrame으로 입력과 출력 타입을 명시하면, 이 함수를 호출하는 곳에서 VSCode가 자동완성을 제공합니다.

Docstring(세 개의 따옴표 주석)은 함수에 마우스를 올렸을 때 툴팁으로 표시됩니다. 여섯 번째로, if __name__ == "__main__" 블록은 이 파일을 직접 실행했을 때만 작동하는 코드입니다.

다른 파일에서 이 모듈을 import 했을 때는 실행되지 않으므로, 재사용 가능한 함수와 테스트 코드를 같은 파일에 둘 수 있습니다. VSCode에서 F5를 누르면 디버거로 실행되어 브레이크포인트를 걸고 단계별로 실행할 수 있습니다.

여러분이 이러한 VSCode 환경을 구축하면 단순히 스크립트를 작성하는 수준을 넘어서 본격적인 소프트웨어 개발을 할 수 있습니다. 여러 모듈로 구성된 데이터 파이프라인, 테스트 코드, 버전 관리까지 통합하여 프로덕션 수준의 프로젝트를 만들 수 있습니다.

실전 팁

💡 Python 확장 설치 시 "Pylance"가 자동으로 포함됩니다. 별도 설치 불필요합니다.

💡 Ctrl+Shift+P (Mac: Cmd+Shift+P)에서 "Python: Select Interpreter"로 가상환경을 선택하세요.

💡 Ctrl+Space로 자동완성을 강제로 트리거할 수 있습니다. Polars 메서드를 탐색할 때 유용합니다.

💡 .env 파일에 환경 변수를 저장하면 VSCode가 자동으로 로드합니다. 데이터베이스 연결 정보 등에 활용하세요.

💡 "Jupyter" 확장을 설치하면 VSCode 안에서 .ipynb 파일을 열고 셀 단위로 실행할 수 있습니다. 별도의 Jupyter 서버 불필요합니다.


#Polars#DataFrames#데이터분석#환경설정#Python#데이터분석,Python,Polars

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.