이미지 로딩 중...
AI Generated
2025. 11. 14. · 0 Views
Polars 소개 및 설치 완벽 가이드
Pandas보다 10배 이상 빠른 차세대 데이터 처리 라이브러리 Polars를 소개합니다. Polars의 핵심 특징, 설치 방법, 그리고 실무에서 바로 활용할 수 있는 기본 사용법을 초급자도 이해할 수 있도록 친근하게 설명합니다.
목차
- Polars란 무엇인가
- Polars 설치하기
- DataFrame 생성 및 기본 구조
- CSV 파일 읽기와 쓰기
- 컬럼 선택과 필터링
- 데이터 정렬과 정렬 기준
- 컬럼 추가 및 변환
- 그룹화와 집계
- Eager vs Lazy API
- 조인 연산
1. Polars란 무엇인가
시작하며
여러분이 대용량 CSV 파일을 Pandas로 처리할 때 컴퓨터가 느려지고 메모리가 부족해서 작업이 중단된 경험이 있나요? 수백만 개의 데이터를 처리하려고 하면 점점 느려지고, 심지어 프로그램이 멈춰버리기도 합니다.
이런 문제는 데이터 분석 현장에서 매우 자주 발생합니다. Pandas는 훌륭한 라이브러리지만, 대용량 데이터 처리에서는 성능과 메모리 효율성에 한계가 있습니다.
특히 실시간 데이터 처리나 대규모 배치 작업에서는 이러한 문제가 더욱 두드러집니다. 바로 이럴 때 필요한 것이 Polars입니다.
Polars는 Rust로 작성된 차세대 데이터 처리 라이브러리로, Pandas보다 10배 이상 빠른 성능을 제공하면서도 훨씬 적은 메모리를 사용합니다.
개요
간단히 말해서, Polars는 고성능 데이터 처리를 위한 Python 라이브러리입니다. Rust 언어로 작성되어 있어 C/C++ 수준의 성능을 제공하면서도 Python의 편리함을 그대로 유지합니다.
Polars가 필요한 이유는 명확합니다. 데이터 크기가 기하급수적으로 증가하는 현대 환경에서 기존 도구의 성능 한계를 극복해야 하기 때문입니다.
예를 들어, 수백만 건의 고객 로그 데이터를 실시간으로 분석해야 하거나, 여러 개의 대용량 파일을 조인해야 하는 경우에 매우 유용합니다. 기존에는 Pandas로 작업하면서 느린 성능을 감수하거나 Dask 같은 분산 처리 라이브러리를 사용해야 했다면, 이제는 Polars 하나로 빠른 성능과 간편한 사용성을 모두 얻을 수 있습니다.
Polars의 핵심 특징은 세 가지입니다: 멀티스레딩을 통한 병렬 처리, 최적화된 메모리 관리, 그리고 Lazy Evaluation을 통한 쿼리 최적화입니다. 이러한 특징들이 결합되어 실무에서 체감할 수 있는 성능 향상을 제공합니다.
코드 예제
# Polars 임포트 및 기본 DataFrame 생성
import polars as pl
# CSV 파일 읽기 (멀티스레딩 자동 활용)
df = pl.read_csv('large_data.csv')
# 기본 정보 확인
print(df.head())
print(df.shape) # (행 개수, 열 개수)
print(df.columns) # 컬럼명 리스트
# 데이터 타입 확인
print(df.dtypes)
설명
Polars가 하는 일은 데이터를 빠르고 효율적으로 읽고, 변환하고, 분석하는 것입니다. Rust의 강력한 성능과 Python의 편리함을 결합하여 데이터 과학자와 엔지니어에게 최고의 도구를 제공합니다.
첫 번째로, import polars as pl로 라이브러리를 임포트합니다. 관례적으로 pl이라는 축약어를 사용하며, 이는 Pandas의 pd와 유사합니다.
이렇게 짧은 이름을 사용하면 코드가 간결해지고 가독성이 높아집니다. 그 다음으로, pl.read_csv()를 사용하여 CSV 파일을 읽어옵니다.
이 과정에서 Polars는 자동으로 여러 CPU 코어를 활용하여 병렬로 데이터를 읽어들입니다. Pandas와 달리 별도의 설정 없이도 멀티스레딩이 적용되어, 대용량 파일을 훨씬 빠르게 로드할 수 있습니다.
DataFrame이 생성되면 head(), shape, columns, dtypes 메서드로 데이터의 기본 정보를 확인할 수 있습니다. 이러한 API는 Pandas와 매우 유사하여 기존 Pandas 사용자라면 쉽게 적응할 수 있습니다.
여러분이 이 코드를 사용하면 수백만 개의 데이터를 포함한 파일도 몇 초 안에 로드하고 탐색할 수 있습니다. 실무에서는 데이터 로딩 시간이 단축되어 전체 분석 파이프라인의 효율성이 크게 향상되며, 메모리 사용량도 줄어들어 더 큰 데이터셋을 처리할 수 있게 됩니다.
실전 팁
💡 Polars는 Arrow 메모리 포맷을 사용하여 다른 데이터 도구(Spark, DuckDB 등)와 제로카피로 데이터를 공유할 수 있습니다
💡 대용량 파일을 다룰 때는 pl.scan_csv()로 Lazy API를 사용하면 메모리에 전체 데이터를 로드하지 않고도 작업할 수 있습니다
💡 Pandas DataFrame이 이미 있다면 pl.from_pandas(df)로 쉽게 변환할 수 있으며, 반대로 df.to_pandas()로 Pandas로 되돌릴 수도 있습니다
💡 기본적으로 모든 문자열은 UTF-8로 처리되므로 인코딩 문제를 겪을 가능성이 훨씬 적습니다
💡 Polars는 NULL 값 처리가 명확하고 효율적이므로 결측치가 많은 데이터셋에서 특히 유용합니다
2. Polars 설치하기
시작하며
여러분이 새로운 프로젝트를 시작할 때 라이브러리 설치에서부터 막히는 경험을 해본 적 있나요? 의존성 충돌이 발생하거나, 설치는 되었는데 제대로 작동하지 않는 경우가 있습니다.
Polars는 이런 문제를 최소화하도록 설계되었습니다. 순수 Python 바인딩으로 제공되며, 복잡한 컴파일 과정이나 시스템 라이브러리 의존성이 거의 없습니다.
대부분의 환경에서 pip 한 줄로 설치가 완료됩니다. 이 섹션에서는 Polars를 다양한 환경에서 설치하는 방법과, 선택적 기능을 추가하는 방법을 알아보겠습니다.
또한 설치 후 정상적으로 작동하는지 확인하는 방법도 함께 살펴보겠습니다.
개요
간단히 말해서, Polars 설치는 pip 명령어 하나로 완료됩니다. 하지만 여러분의 프로젝트 요구사항에 따라 다양한 옵션을 선택할 수 있습니다.
기본 설치는 매우 간단하지만, 특정 기능이 필요한 경우에는 추가 패키지를 설치해야 할 수 있습니다. 예를 들어, Excel 파일을 다루거나, 데이터베이스와 연동하거나, 시각화 기능을 사용하려는 경우 관련 의존성을 함께 설치하는 것이 좋습니다.
기존에는 데이터 분석 환경을 구축할 때 Pandas, NumPy, SciPy 등 여러 패키지를 설치하고 버전 호환성을 맞춰야 했다면, 이제는 Polars 하나로 대부분의 데이터 처리 작업을 수행할 수 있습니다. Polars는 PyPI에 바이너리 패키지로 배포되므로 빌드 시간이 필요 없으며, Windows, macOS, Linux 모든 주요 플랫폼을 지원합니다.
가상환경에서도 문제없이 작동하며, Docker 컨테이너나 클라우드 환경에서도 쉽게 설치할 수 있습니다.
코드 예제
# 기본 설치 (가장 일반적)
# pip install polars
# 모든 선택적 기능 포함 설치
# pip install 'polars[all]'
# 특정 기능만 선택 설치
# pip install 'polars[numpy,pandas,pyarrow]'
# 설치 확인
import polars as pl
print(f"Polars version: {pl.__version__}")
# 간단한 동작 테스트
test_df = pl.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]})
print(test_df)
설명
Polars를 설치하는 과정은 매우 직관적입니다. Python이 설치된 환경이라면 어디서든 동일한 방식으로 설치할 수 있습니다.
첫 번째로, 기본 설치는 터미널이나 명령 프롬프트에서 pip install polars를 실행하면 됩니다. 이 명령어는 Polars의 핵심 기능만 설치하며, 대부분의 일반적인 데이터 처리 작업에 충분합니다.
설치 시간은 인터넷 속도에 따라 다르지만 보통 1분 이내에 완료됩니다. 만약 Excel 파일 읽기, 시각화, 또는 다른 라이브러리와의 통합이 필요하다면 pip install 'polars[all]'을 사용합니다.
이 명령어는 모든 선택적 의존성을 함께 설치하여 Polars의 모든 기능을 사용할 수 있게 합니다. 대괄호 안에 numpy, pandas, pyarrow 같은 특정 기능만 명시할 수도 있습니다.
설치가 완료되면 Python 인터프리터나 Jupyter Notebook에서 import polars as pl로 임포트하여 테스트합니다. 버전 정보를 출력해보고 간단한 DataFrame을 생성해봄으로써 정상적으로 설치되었는지 확인할 수 있습니다.
여러분이 이 과정을 따라하면 몇 분 안에 Polars를 사용할 준비가 완료됩니다. 실무에서는 requirements.txt나 pyproject.toml 파일에 polars를 추가하여 팀원들이 동일한 환경을 쉽게 구축할 수 있으며, CI/CD 파이프라인에서도 자동으로 설치되도록 설정할 수 있습니다.
실전 팁
💡 가상환경(venv, conda)을 사용하면 프로젝트별로 독립적인 Polars 버전을 관리할 수 있어 충돌을 방지할 수 있습니다
💡 poetry나 pipenv를 사용하는 경우 poetry add polars 또는 pipenv install polars로 간편하게 설치할 수 있습니다
💡 Jupyter Notebook에서 사용한다면 !pip install polars로 노트북 내에서 직접 설치할 수 있습니다
💡 최신 기능을 사용하려면 pip install --upgrade polars로 정기적으로 업데이트하세요. Polars는 활발히 개발되고 있어 자주 업데이트됩니다
💡 Docker 이미지를 만든다면 requirements.txt에 polars를 추가하고 RUN pip install -r requirements.txt를 사용하세요
3. DataFrame 생성 및 기본 구조
시작하며
여러분이 데이터 분석을 시작할 때 가장 먼저 하는 일은 무엇인가요? 바로 데이터를 메모리에 로드하고 구조를 파악하는 것입니다.
하지만 데이터 소스가 다양하고 형식이 제각각이라면 이 과정이 복잡해질 수 있습니다. Polars는 다양한 방식으로 DataFrame을 생성할 수 있는 유연한 API를 제공합니다.
딕셔너리, 리스트, Pandas DataFrame, 또는 외부 파일로부터 쉽게 DataFrame을 만들 수 있습니다. 이러한 유연성 덕분에 어떤 형태의 데이터든 빠르게 Polars 형식으로 변환할 수 있습니다.
DataFrame은 Polars의 핵심 데이터 구조입니다. 행과 열로 구성된 2차원 테이블 형태로, 각 열은 특정 데이터 타입을 가지며 효율적으로 저장됩니다.
이 섹션에서는 DataFrame을 생성하는 다양한 방법과 기본 구조를 이해해보겠습니다.
개요
간단히 말해서, Polars DataFrame은 열 지향 데이터 구조로, 각 열이 독립적으로 저장되어 매우 효율적인 연산을 가능하게 합니다. 이는 행 지향 구조보다 분석 작업에 훨씬 유리합니다.
DataFrame을 생성하는 방법은 여러 가지가 있지만, 가장 일반적인 방법은 Python 딕셔너리를 사용하는 것입니다. 딕셔너리의 키는 컬럼명이 되고, 값은 해당 컬럼의 데이터가 됩니다.
예를 들어, 사용자 정보를 담은 테이블을 만들거나, 실험 결과를 정리할 때 이 방법이 매우 편리합니다. 기존에는 데이터를 리스트의 리스트로 관리하면서 인덱싱 오류나 타입 불일치 문제를 겪었다면, 이제는 DataFrame으로 구조화하여 타입 안정성과 명확한 컬럼명을 얻을 수 있습니다.
Polars DataFrame의 핵심 특징은 불변성(immutability)과 타입 명확성입니다. 모든 연산은 새로운 DataFrame을 반환하므로 원본 데이터가 의도치 않게 변경될 위험이 없습니다.
또한 각 컬럼의 타입이 명확하게 정의되어 있어 런타임 오류를 줄일 수 있습니다.
코드 예제
import polars as pl
# 딕셔너리로부터 DataFrame 생성
df = pl.DataFrame({
"name": ["Alice", "Bob", "Charlie"],
"age": [25, 30, 35],
"city": ["Seoul", "Busan", "Incheon"]
})
# 기본 정보 확인
print(f"행 개수: {len(df)}")
print(f"열 개수: {df.width}")
print(f"컬럼명: {df.columns}")
print(f"스키마: {df.schema}")
# 첫 몇 행 출력
print(df.head(2))
설명
DataFrame을 생성하는 것은 데이터 분석의 첫 단계이며, Polars는 이 과정을 매우 직관적으로 만들어줍니다. 첫 번째로, pl.DataFrame() 함수에 딕셔너리를 전달합니다.
딕셔너리의 각 키-값 쌍이 DataFrame의 한 컬럼이 됩니다. 예를 들어, "name" 키는 문자열 컬럼이 되고, "age" 키는 정수 컬럼이 됩니다.
Polars는 자동으로 각 컬럼의 최적 데이터 타입을 추론합니다. DataFrame이 생성되면 기본 정보를 확인할 수 있습니다.
len(df)는 행의 개수를, df.width는 열의 개수를 반환합니다. df.columns는 모든 컬럼명을 리스트로 제공하며, df.schema는 각 컬럼의 이름과 데이터 타입을 딕셔너리 형태로 보여줍니다.
head() 메서드는 DataFrame의 처음 n개 행을 출력합니다. 기본값은 5이지만, 인자를 전달하여 원하는 만큼 볼 수 있습니다.
이는 데이터의 구조를 빠르게 파악하는 데 매우 유용합니다. 반대로 tail() 메서드는 마지막 몇 행을 보여줍니다.
여러분이 이 방법으로 DataFrame을 생성하면 데이터의 구조가 명확해지고 타입 안정성이 보장됩니다. 실무에서는 API 응답을 DataFrame으로 변환하거나, 설정 파일을 읽어서 테이블 형태로 관리하거나, 시뮬레이션 결과를 정리하는 등 다양한 상황에서 활용할 수 있습니다.
불변성 덕분에 함수형 프로그래밍 스타일로 안전하게 데이터를 변환할 수 있습니다.
실전 팁
💡 빈 DataFrame을 만들 때는 스키마를 명시적으로 지정하는 것이 좋습니다: pl.DataFrame(schema={"name": pl.Utf8, "age": pl.Int64})
💡 리스트의 리스트로도 DataFrame을 만들 수 있지만, 이 경우 컬럼명을 별도로 지정해야 합니다: pl.DataFrame(data, schema=["col1", "col2"])
💡 describe() 메서드를 사용하면 숫자형 컬럼의 통계 요약(평균, 표준편차 등)을 한눈에 볼 수 있습니다
💡 DataFrame은 불변이므로 원본을 유지하면서 안전하게 실험할 수 있습니다. 변경이 필요하면 새 변수에 할당하세요
💡 glimpse() 메서드는 각 컬럼의 타입과 샘플 값을 보기 좋게 출력하여 데이터 탐색에 유용합니다
4. CSV 파일 읽기와 쓰기
시작하며
여러분이 실무에서 가장 자주 마주치는 데이터 형식은 무엇인가요? 아마도 CSV 파일일 것입니다.
로그 데이터, 데이터베이스 덤프, 엑셀에서 내보낸 파일 등 대부분이 CSV 형식입니다. 하지만 대용량 CSV 파일을 다룰 때는 여러 문제가 발생합니다.
로딩 시간이 오래 걸리고, 메모리가 부족하며, 인코딩 문제로 한글이 깨지기도 합니다. 특히 수백만 행이 넘는 파일은 일반적인 도구로는 열어보기조차 어렵습니다.
Polars는 이러한 문제를 해결하기 위해 최적화된 CSV 읽기/쓰기 기능을 제공합니다. 멀티스레딩을 활용하여 매우 빠르게 파일을 읽고, 자동으로 데이터 타입을 추론하며, 다양한 인코딩을 지원합니다.
이 섹션에서는 실무에서 자주 사용하는 CSV 작업 방법을 알아보겠습니다.
개요
간단히 말해서, Polars의 CSV 처리는 속도와 편의성에서 최고 수준입니다. 복잡한 설정 없이도 대부분의 CSV 파일을 문제없이 읽을 수 있습니다.
CSV 파일을 읽어야 하는 이유는 명확합니다. 대부분의 데이터 소스가 CSV로 제공되기 때문입니다.
예를 들어, 웹 서버 로그를 분석하거나, 판매 데이터를 집계하거나, 센서 데이터를 시각화할 때 CSV를 첫 단계로 로드해야 합니다. 기존에는 Pandas로 CSV를 읽으면서 느린 속도를 감수하거나 dtype을 일일이 지정해야 했다면, 이제는 Polars로 훨씬 빠르게 읽으면서도 자동 타입 추론의 이점을 누릴 수 있습니다.
Polars의 CSV 처리 핵심 특징은 병렬 파싱, 스트리밍 모드, 그리고 유연한 옵션입니다. 멀티 코어를 자동으로 활용하여 파일을 빠르게 읽으며, 메모리가 부족한 경우 청크 단위로 읽을 수 있고, 구분자나 인코딩을 자유롭게 설정할 수 있습니다.
코드 예제
import polars as pl
# 기본 CSV 읽기
df = pl.read_csv('data.csv')
# 옵션을 지정한 읽기
df = pl.read_csv(
'data.csv',
separator=',', # 구분자 지정
has_header=True, # 헤더 포함 여부
encoding='utf-8', # 인코딩
null_values=['NA', ''] # NULL로 처리할 값
)
# DataFrame을 CSV로 저장
df.write_csv('output.csv')
# 압축하여 저장
df.write_csv('output.csv.gz')
설명
CSV 파일을 읽고 쓰는 것은 데이터 분석 워크플로우의 가장 기본적인 작업이며, Polars는 이를 매우 효율적으로 처리합니다. 첫 번째로, pl.read_csv('data.csv')로 파일을 읽습니다.
이 한 줄의 코드로 Polars는 파일을 열고, 헤더를 파싱하고, 각 컬럼의 타입을 추론하고, 데이터를 메모리에 로드합니다. 이 모든 과정이 여러 CPU 코어에서 병렬로 실행되어 Pandas보다 훨씬 빠릅니다.
더 세밀한 제어가 필요하면 다양한 옵션을 지정할 수 있습니다. separator로 구분자를 지정하고(탭 구분 파일이면 '\t'), has_header로 첫 행이 헤더인지 명시하며, encoding으로 문자 인코딩을 설정합니다.
null_values 옵션은 특정 문자열을 NULL로 처리하도록 지시하여 데이터 정제를 자동화합니다. 데이터 처리가 끝나면 write_csv()로 결과를 저장합니다.
파일 경로만 지정하면 되며, 확장자가 .gz나 .zip이면 자동으로 압축하여 저장합니다. 이는 디스크 공간을 절약하고 전송 시간을 줄이는 데 유용합니다.
여러분이 이러한 방법을 사용하면 수 GB 크기의 CSV 파일도 몇 초 안에 로드할 수 있습니다. 실무에서는 매일 생성되는 로그 파일을 자동으로 처리하거나, 여러 CSV 파일을 병합하거나, 분석 결과를 다른 팀과 공유하기 위해 CSV로 내보내는 등의 작업에 활용할 수 있습니다.
특히 배치 처리 스크립트에서 빠른 CSV 처리는 전체 파이프라인 성능에 큰 영향을 줍니다.
실전 팁
💡 대용량 파일은 pl.scan_csv()를 사용하면 Lazy 모드로 읽어서 필요한 부분만 메모리에 로드할 수 있습니다
💡 컬럼이 많은 파일에서 일부만 필요하면 columns 파라미터로 특정 컬럼만 읽어서 메모리와 시간을 절약하세요
💡 데이터 타입 추론이 잘못되면 dtypes 파라미터로 명시적으로 지정할 수 있습니다: dtypes={"age": pl.Int64}
💡 skip_rows와 n_rows 옵션으로 파일의 특정 부분만 읽을 수 있어 테스트나 샘플링에 유용합니다
💡 write_csv()에서 float_precision 옵션으로 소수점 자릿수를 제어하여 파일 크기를 줄일 수 있습니다
5. 컬럼 선택과 필터링
시작하며
여러분이 데이터 분석을 할 때 전체 데이터를 모두 사용하는 경우는 거의 없습니다. 특정 컬럼만 필요하거나, 특정 조건을 만족하는 행만 분석해야 하는 경우가 대부분입니다.
하지만 복잡한 조건으로 데이터를 필터링하다 보면 코드가 길어지고 가독성이 떨어집니다. 여러 조건을 AND/OR로 조합하거나, 문자열 패턴을 검색하거나, 날짜 범위를 지정하는 등의 작업은 생각보다 복잡할 수 있습니다.
Polars는 직관적이고 표현력 있는 선택 및 필터링 API를 제공합니다. select()로 필요한 컬럼만 선택하고, filter()로 조건에 맞는 행만 추출할 수 있습니다.
메서드 체이닝을 통해 여러 작업을 연결하면 코드가 깔끔하고 읽기 쉬워집니다.
개요
간단히 말해서, 컬럼 선택과 필터링은 데이터를 원하는 형태로 줄이는 작업입니다. 불필요한 데이터를 제거하여 처리 속도를 높이고 분석에 집중할 수 있습니다.
이러한 작업이 필요한 이유는 실무 데이터가 항상 분석에 최적화된 형태로 제공되지 않기 때문입니다. 예를 들어, 100개의 컬럼이 있는 고객 데이터베이스에서 이름과 구매액만 필요하거나, 전체 로그 데이터에서 에러가 발생한 건만 분석해야 하는 경우가 있습니다.
기존에는 인덱싱이나 불리언 마스크를 사용하여 복잡한 필터링 로직을 작성해야 했다면, 이제는 Polars의 표현식(expression)을 사용하여 SQL과 유사한 직관적인 방식으로 작성할 수 있습니다. 핵심 특징은 표현식 기반 API, 메서드 체이닝, 그리고 지연 평가입니다.
pl.col() 함수로 컬럼을 참조하고 다양한 조건을 적용할 수 있으며, 여러 작업을 점(.)으로 연결하여 파이프라인처럼 처리할 수 있고, Lazy 모드에서는 전체 작업을 최적화하여 실행합니다.
코드 예제
import polars as pl
df = pl.DataFrame({
"name": ["Alice", "Bob", "Charlie", "David"],
"age": [25, 30, 35, 28],
"city": ["Seoul", "Busan", "Seoul", "Incheon"],
"salary": [50000, 60000, 70000, 55000]
})
# 특정 컬럼 선택
result = df.select(["name", "age"])
# 표현식으로 선택
result = df.select(pl.col("name"), pl.col("age") + 1)
# 조건으로 필터링
result = df.filter(pl.col("age") > 28)
# 여러 조건 조합
result = df.filter(
(pl.col("age") > 25) & (pl.col("city") == "Seoul")
)
설명
컬럼 선택과 필터링은 데이터 전처리의 핵심이며, Polars는 이를 매우 효율적이고 표현력 있게 만들어줍니다. 첫 번째로, select() 메서드로 필요한 컬럼만 선택합니다.
컬럼명을 리스트로 전달하면 해당 컬럼만 포함된 새로운 DataFrame이 반환됩니다. 이는 원본 데이터를 변경하지 않으므로 안전하게 실험할 수 있습니다.
더 강력한 방법은 표현식(expression)을 사용하는 것입니다. pl.col("age")는 age 컬럼을 참조하며, 여기에 산술 연산이나 함수를 적용할 수 있습니다.
예를 들어, pl.col("age") + 1은 모든 나이에 1을 더한 새로운 컬럼을 생성합니다. 이러한 표현식은 매우 유연하여 복잡한 변환도 간결하게 표현할 수 있습니다.
filter() 메서드는 조건을 만족하는 행만 추출합니다. pl.col("age") > 28은 나이가 28보다 큰 행만 반환합니다.
여러 조건을 조합할 때는 &(AND)나 |(OR) 연산자를 사용하며, 각 조건을 괄호로 감싸야 합니다. 이는 연산자 우선순위 때문입니다.
여러분이 이러한 방법을 마스터하면 데이터 탐색과 분석 속도가 크게 향상됩니다. 실무에서는 특정 기간의 데이터만 분석하거나, 특정 카테고리의 제품만 집계하거나, 이상치를 제거하는 등의 작업을 빠르고 정확하게 수행할 수 있습니다.
메서드 체이닝으로 여러 작업을 연결하면 전체 데이터 파이프라인을 한눈에 파악할 수 있어 유지보수가 쉬워집니다.
실전 팁
💡 정규표현식으로 여러 컬럼을 선택할 수 있습니다: df.select(pl.col("^.*_id$"))는 _id로 끝나는 모든 컬럼을 선택합니다
💡 exclude() 메서드로 특정 컬럼을 제외할 수 있습니다: df.select(pl.all().exclude("password"))
💡 is_in() 메서드로 값이 특정 리스트에 포함되는지 확인할 수 있습니다: pl.col("city").is_in(["Seoul", "Busan"])
💡 문자열 필터링은 str 네임스페이스를 사용합니다: pl.col("name").str.contains("Al")로 패턴 검색이 가능합니다
💡 null 값을 필터링할 때는 is_null() 또는 is_not_null() 메서드를 사용하세요
6. 데이터 정렬과 정렬 기준
시작하며
여러분이 데이터 분석 결과를 보고할 때 어떤 순서로 보여주는 것이 가장 효과적일까요? 가장 높은 매출을 기록한 제품부터 보여주거나, 가장 최근 날짜부터 정렬하는 것이 일반적입니다.
데이터 정렬은 단순해 보이지만 실무에서는 매우 중요합니다. 상위 N개 항목을 찾거나, 시계열 데이터를 시간 순으로 정렬하거나, 여러 컬럼을 기준으로 복합 정렬해야 하는 경우가 많습니다.
정렬이 잘못되면 분석 결과 전체가 달라질 수 있습니다. Polars는 빠르고 유연한 정렬 기능을 제공합니다.
sort() 메서드로 한 개 이상의 컬럼을 기준으로 오름차순 또는 내림차순 정렬을 할 수 있으며, null 값의 위치도 제어할 수 있습니다. 이 섹션에서는 다양한 정렬 시나리오를 살펴보겠습니다.
개요
간단히 말해서, 정렬은 데이터를 특정 기준에 따라 재배열하는 작업입니다. 이를 통해 패턴을 발견하거나, 극값을 찾거나, 시각화를 위한 준비를 할 수 있습니다.
정렬이 필요한 이유는 원시 데이터가 보통 특정 순서 없이 저장되기 때문입니다. 예를 들어, 데이터베이스에서 추출한 고객 주문 데이터는 주문 시간 순이 아닐 수 있고, 로그 파일은 여러 서버에서 수집되어 시간 순서가 뒤섞여 있을 수 있습니다.
기존에는 정렬을 위해 여러 단계의 인덱싱과 재배열이 필요했다면, 이제는 Polars의 sort()로 한 번에 처리할 수 있으며, 내부적으로 최적화된 알고리즘을 사용하여 매우 빠릅니다. 정렬의 핵심 특징은 안정성(stable sort), 다중 컬럼 지원, 그리고 null 값 처리입니다.
안정 정렬은 동일한 값의 상대적 순서를 유지하며, 여러 컬럼을 순차적으로 정렬 기준으로 사용할 수 있고, null 값을 처음이나 끝에 배치할지 선택할 수 있습니다.
코드 예제
import polars as pl
df = pl.DataFrame({
"name": ["Alice", "Bob", "Charlie", "David"],
"age": [25, 30, 25, 28],
"salary": [50000, 60000, 55000, 58000]
})
# 단일 컬럼으로 오름차순 정렬
result = df.sort("age")
# 내림차순 정렬
result = df.sort("salary", descending=True)
# 다중 컬럼 정렬 (age 오름차순, salary 내림차순)
result = df.sort(["age", "salary"], descending=[False, True])
# null 값을 마지막에 배치
result = df.sort("age", nulls_last=True)
설명
데이터 정렬은 분석의 기본이며, Polars는 이를 매우 효율적으로 수행합니다. 첫 번째로, sort("age")는 age 컬럼을 기준으로 오름차순 정렬합니다.
기본적으로 작은 값부터 큰 값 순으로 정렬되며, 새로운 DataFrame이 반환됩니다. 원본은 변경되지 않으므로 여러 번 다른 방식으로 정렬을 시도할 수 있습니다.
내림차순으로 정렬하려면 descending=True를 지정합니다. 이는 가장 높은 급여를 받는 직원을 찾거나, 가장 최근 거래를 먼저 보고 싶을 때 유용합니다.
예를 들어, 매출 데이터를 내림차순으로 정렬하면 상위 제품이 먼저 나타납니다. 다중 컬럼 정렬은 첫 번째 기준으로 정렬한 후, 동일한 값에 대해 두 번째 기준을 적용합니다.
sort(["age", "salary"], descending=[False, True])는 나이가 같은 사람들 중에서 급여가 높은 순으로 정렬합니다. descending 파라미터에 리스트를 전달하여 각 컬럼마다 다른 정렬 방향을 지정할 수 있습니다.
null 값이 포함된 데이터를 정렬할 때는 nulls_last 파라미터로 null을 처음에 둘지 마지막에 둘지 결정합니다. 기본적으로 null은 가장 작은 값으로 취급되지만, 분석 목적에 따라 마지막에 두는 것이 더 나을 수 있습니다.
여러분이 정렬을 능숙하게 사용하면 데이터에서 인사이트를 빠르게 추출할 수 있습니다. 실무에서는 Top 10 고객을 찾거나, 시계열 데이터를 시간 순으로 정렬하여 추세를 분석하거나, 카테고리별로 먼저 그룹화한 후 각 그룹 내에서 정렬하는 등의 작업에 활용됩니다.
특히 대용량 데이터에서도 Polars의 정렬은 매우 빠르게 작동합니다.
실전 팁
💡 top_k() 메서드를 사용하면 정렬 없이 상위 K개 항목만 빠르게 찾을 수 있어 성능이 더 좋습니다
💡 by 파라미터로 표현식을 사용할 수도 있습니다: df.sort(pl.col("salary") / pl.col("age"))로 급여 대비 나이 비율로 정렬
💡 정렬은 비용이 큰 연산이므로, 필터링을 먼저 수행하여 데이터 크기를 줄인 후 정렬하는 것이 효율적입니다
💡 시계열 데이터는 sort("timestamp")로 정렬한 후 시간 기반 분석을 수행하세요
💡 maintain_order 파라미터를 True로 설정하면 안정 정렬이 보장되어 동일한 값의 상대적 순서가 유지됩니다
7. 컬럼 추가 및 변환
시작하며
여러분이 데이터를 분석하다 보면 기존 데이터를 바탕으로 새로운 컬럼을 만들어야 할 때가 많습니다. 매출액과 비용으로부터 이익을 계산하거나, 생년월일로부터 나이를 계산하거나, 카테고리를 기준으로 레이블을 붙이는 작업이 그 예입니다.
하지만 기존 컬럼을 안전하게 보존하면서 새로운 컬럼을 추가하는 것은 생각보다 까다로울 수 있습니다. 실수로 원본 데이터를 덮어쓰거나, 데이터 타입이 맞지 않아 오류가 발생하는 경우도 있습니다.
Polars는 with_columns() 메서드로 안전하고 효율적으로 컬럼을 추가하거나 변환할 수 있습니다. 불변성 덕분에 원본은 항상 보존되며, 표현식을 사용하여 복잡한 변환도 간결하게 작성할 수 있습니다.
이 섹션에서는 다양한 컬럼 변환 방법을 알아보겠습니다.
개요
간단히 말해서, 컬럼 추가 및 변환은 기존 데이터로부터 파생된 새로운 정보를 만드는 작업입니다. 이는 피처 엔지니어링의 핵심이며 분석의 질을 높입니다.
컬럼 변환이 필요한 이유는 원시 데이터만으로는 충분한 인사이트를 얻기 어렵기 때문입니다. 예를 들어, 주문 데이터에 수량과 단가가 있다면 총액을 계산해야 실제 매출을 알 수 있고, 타임스탬프가 있다면 요일이나 시간대를 추출해야 패턴을 분석할 수 있습니다.
기존에는 반복문을 돌면서 각 행을 계산하거나 복잡한 apply 함수를 사용해야 했다면, 이제는 Polars의 벡터화된 연산으로 모든 행을 한 번에 처리하여 훨씬 빠릅니다. 핵심 특징은 불변성, 벡터화 연산, 그리고 타입 추론입니다.
모든 변환은 새로운 DataFrame을 반환하고, 전체 컬럼에 대한 연산이 한 번에 실행되며, Polars가 자동으로 결과 컬럼의 타입을 결정합니다.
코드 예제
import polars as pl
df = pl.DataFrame({
"name": ["Alice", "Bob", "Charlie"],
"age": [25, 30, 35],
"salary": [50000, 60000, 70000]
})
# 새로운 컬럼 추가
result = df.with_columns(
# 나이 카테고리 추가
age_group=pl.when(pl.col("age") < 30)
.then(pl.lit("Young"))
.otherwise(pl.lit("Senior"))
)
# 기존 컬럼 변환
result = df.with_columns(
# 급여를 월급으로 변환
(pl.col("salary") / 12).alias("monthly_salary")
)
# 여러 컬럼 동시 추가
result = df.with_columns([
(pl.col("salary") * 1.1).alias("next_year_salary"),
pl.col("name").str.to_uppercase().alias("name_upper")
])
설명
컬럼 추가와 변환은 데이터 전처리의 핵심이며, Polars는 이를 매우 표현력 있게 만들어줍니다. 첫 번째로, with_columns() 메서드에 새로운 컬럼의 이름과 값을 지정합니다.
예를 들어, age_group=처럼 키워드 인자로 컬럼명을 지정하고, 표현식으로 값을 계산합니다. 이렇게 하면 원본 DataFrame에 새로운 컬럼이 추가된 새로운 DataFrame이 반환됩니다.
조건부 로직은 pl.when().then().otherwise() 패턴을 사용합니다. 이는 SQL의 CASE WHEN과 유사하며, 복잡한 if-else 로직을 깔끔하게 표현할 수 있습니다.
예를 들어, 나이가 30 미만이면 "Young", 그렇지 않으면 "Senior"를 할당합니다. 기존 컬럼을 변환할 때는 alias() 메서드로 새로운 이름을 지정합니다.
(pl.col("salary") / 12).alias("monthly_salary")는 연봉을 12로 나누어 월급 컬럼을 만듭니다. alias를 사용하지 않으면 원본 컬럼이 덮어써지므로 주의해야 합니다.
여러 컬럼을 동시에 추가할 때는 리스트로 전달하거나 여러 키워드 인자를 사용합니다. 이렇게 하면 한 번의 연산으로 여러 변환을 적용할 수 있어 효율적입니다.
각 표현식은 독립적으로 평가되므로 순서에 관계없이 작성할 수 있습니다. 여러분이 이러한 기법을 활용하면 데이터를 원하는 형태로 자유롭게 변환할 수 있습니다.
실무에서는 머신러닝을 위한 피처를 생성하거나, 비즈니스 지표를 계산하거나, 데이터를 정규화하는 등의 작업에 사용됩니다. 특히 벡터화된 연산 덕분에 수백만 행의 데이터도 빠르게 처리할 수 있습니다.
실전 팁
💡 str, dt, list 같은 네임스페이스를 사용하면 문자열, 날짜, 리스트 타입에 특화된 메서드를 사용할 수 있습니다
💡 cast() 메서드로 명시적으로 타입을 변환할 수 있습니다: pl.col("age").cast(pl.Float64)
💡 여러 컬럼에 동일한 연산을 적용하려면 pl.col(["col1", "col2"]).method()처럼 리스트로 지정하세요
💡 복잡한 로직은 여러 단계로 나누어 with_columns()를 체이닝하면 가독성이 높아집니다
💡 map_elements()를 사용하면 Python 함수를 적용할 수 있지만, 성능이 느리므로 가능하면 내장 표현식을 사용하세요
8. 그룹화와 집계
시작하며
여러분이 데이터를 분석할 때 가장 자주 하는 작업 중 하나는 무엇인가요? 바로 카테고리별로 데이터를 그룹화하여 평균, 합계, 개수 등을 계산하는 것입니다.
부서별 평균 급여, 지역별 판매량, 날짜별 방문자 수 등을 구하는 작업이 그 예입니다. 하지만 대용량 데이터에서 그룹화 연산은 시간이 오래 걸리고 메모리를 많이 사용합니다.
특히 여러 컬럼으로 그룹화하거나 복잡한 집계 함수를 적용할 때 성능 문제가 발생하기 쉽습니다. Polars는 매우 빠른 그룹화 및 집계 기능을 제공합니다.
group_by() 메서드로 그룹을 나누고 agg()로 집계 함수를 적용할 수 있으며, 여러 집계를 동시에 수행할 수도 있습니다. 내부적으로 해시 기반 알고리즘을 사용하여 대용량 데이터에서도 빠르게 작동합니다.
개요
간단히 말해서, 그룹화와 집계는 데이터를 카테고리별로 나누어 요약 통계를 계산하는 작업입니다. 이는 데이터의 패턴과 트렌드를 발견하는 핵심 방법입니다.
그룹화가 필요한 이유는 전체 데이터의 평균이나 합계보다 세부 카테고리별 통계가 훨씬 유용하기 때문입니다. 예를 들어, 전체 매출 평균보다는 제품 카테고리별 평균 매출이 마케팅 전략을 세우는 데 더 도움이 됩니다.
기존에는 SQL의 GROUP BY를 사용하거나 Pandas의 groupby()를 사용했지만, 대용량 데이터에서는 느렸다면, 이제는 Polars의 최적화된 그룹화로 훨씬 빠르게 처리할 수 있습니다. 핵심 특징은 병렬 처리, 다중 집계, 그리고 표현식 기반 API입니다.
여러 그룹을 동시에 처리하고, 한 번에 여러 통계를 계산하며, 복잡한 집계 로직을 표현식으로 간결하게 작성할 수 있습니다.
코드 예제
import polars as pl
df = pl.DataFrame({
"city": ["Seoul", "Busan", "Seoul", "Busan", "Seoul"],
"category": ["A", "A", "B", "B", "A"],
"sales": [100, 150, 200, 120, 180],
"quantity": [10, 15, 20, 12, 18]
})
# 단일 컬럼 그룹화 및 집계
result = df.group_by("city").agg(
pl.col("sales").sum().alias("total_sales")
)
# 다중 컬럼 그룹화
result = df.group_by(["city", "category"]).agg([
pl.col("sales").mean().alias("avg_sales"),
pl.col("quantity").sum().alias("total_quantity"),
pl.len().alias("count")
])
# 조건부 집계
result = df.group_by("city").agg(
pl.col("sales").filter(pl.col("quantity") > 12).sum()
)
설명
그룹화와 집계는 데이터 분석의 핵심 기법이며, Polars는 이를 매우 효율적으로 수행합니다. 첫 번째로, group_by("city")로 city 컬럼을 기준으로 데이터를 그룹으로 나눕니다.
이렇게 하면 GroupBy 객체가 반환되며, 아직 실제 계산은 수행되지 않습니다. 다음에 집계 함수를 적용할 때 비로소 계산이 실행됩니다.
agg() 메서드에 집계 표현식을 전달합니다. pl.col("sales").sum()은 각 그룹의 sales 합계를 계산하며, alias("total_sales")로 결과 컬럼의 이름을 지정합니다.
결과는 각 그룹당 한 행씩 포함된 새로운 DataFrame입니다. 다중 컬럼 그룹화는 리스트로 여러 컬럼을 지정합니다.
group_by(["city", "category"])는 city와 category의 모든 조합에 대해 그룹을 만듭니다. 여러 집계를 동시에 수행하려면 agg()에 표현식 리스트를 전달합니다.
이렇게 하면 한 번의 스캔으로 모든 통계가 계산되어 효율적입니다. pl.len()은 각 그룹의 행 개수를 반환하는 특수한 표현식입니다.
조건부 집계는 filter()를 집계 전에 적용하여 특정 조건을 만족하는 행만 집계에 포함시킵니다. 예를 들어, 수량이 12보다 큰 주문만 합산하는 것입니다.
여러분이 그룹화와 집계를 마스터하면 데이터에서 의미 있는 인사이트를 빠르게 추출할 수 있습니다. 실무에서는 대시보드를 위한 통계를 생성하거나, A/B 테스트 결과를 비교하거나, 시계열 데이터를 주기별로 집계하는 등의 작업에 활용됩니다.
Polars의 빠른 그룹화 덕분에 수천만 개의 레코드도 몇 초 안에 처리할 수 있습니다.
실전 팁
💡 여러 집계를 수행할 때는 리스트로 한 번에 전달하는 것이 각각 호출하는 것보다 훨씬 빠릅니다
💡 first(), last(), n_unique() 같은 편리한 집계 함수들이 많이 제공됩니다
💡 quantile()로 백분위수를 계산할 수 있어 중앙값이나 사분위수 분석에 유용합니다
💡 maintain_order=True 옵션을 사용하면 그룹의 순서가 원본 데이터의 순서대로 유지됩니다
💡 그룹별로 정렬하려면 group_by() 후에 sort()를 사용하거나, over() 표현식으로 윈도우 함수를 사용할 수 있습니다
9. Eager vs Lazy API
시작하며
여러분이 복잡한 데이터 파이프라인을 작성할 때 각 단계마다 중간 결과를 저장하고 있나요? 이렇게 하면 코드가 직관적이지만, 불필요한 계산과 메모리 사용이 발생할 수 있습니다.
예를 들어, 데이터를 읽고, 필터링하고, 그룹화하고, 정렬하는 작업을 순차적으로 수행한다면 각 단계마다 전체 데이터를 처리해야 합니다. 하지만 만약 시스템이 전체 작업을 미리 보고 최적화할 수 있다면 어떨까요?
Polars는 두 가지 API 모드를 제공합니다: Eager API는 즉시 실행되어 결과를 바로 보여주고, Lazy API는 실행을 지연시켜 전체 쿼리를 최적화한 후 한 번에 실행합니다. Lazy API를 사용하면 쿼리 최적화, 조건부 필터 푸시다운, 프로젝션 푸시다운 등의 최적화가 자동으로 적용되어 성능이 크게 향상됩니다.
개요
간단히 말해서, Eager API는 각 작업을 즉시 실행하고, Lazy API는 모든 작업을 모아서 최적화한 후 실행합니다. Lazy API는 대용량 데이터나 복잡한 파이프라인에서 훨씬 효율적입니다.
Lazy API가 필요한 이유는 데이터베이스 쿼리 최적화와 같은 원리입니다. 전체 작업을 미리 알면 불필요한 컬럼을 읽지 않거나, 필터를 먼저 적용하여 처리할 데이터 양을 줄일 수 있습니다.
예를 들어, 100개 컬럼 중 2개만 사용한다면 나머지는 읽지 않는 것이 효율적입니다. 기존에는 각 단계마다 데이터를 처리하고 중간 결과를 저장해야 했다면, 이제는 Lazy API로 전체 파이프라인을 선언한 후 collect()로 한 번에 실행하여 최적화의 이점을 누릴 수 있습니다.
핵심 특징은 쿼리 최적화, 지연 평가, 그리고 메모리 효율성입니다. Polars가 쿼리 플랜을 분석하여 최적의 실행 계획을 생성하고, 필요한 시점까지 실행을 미루며, 필요한 데이터만 메모리에 로드합니다.
코드 예제
import polars as pl
# Eager API - 즉시 실행
df = pl.read_csv('data.csv')
result = df.filter(pl.col("age") > 30) \
.select(["name", "age"]) \
.sort("age")
# Lazy API - 지연 실행
lf = pl.scan_csv('data.csv') # 아직 읽지 않음
result = lf.filter(pl.col("age") > 30) \
.select(["name", "age"]) \
.sort("age") \
.collect() # 여기서 최적화 후 실행
# 실행 계획 확인
print(lf.filter(pl.col("age") > 30)
.select(["name", "age"])
.explain())
설명
Eager와 Lazy API의 차이를 이해하면 상황에 맞는 최적의 방법을 선택할 수 있습니다. 첫 번째로, Eager API는 우리가 지금까지 사용한 일반적인 방식입니다.
pl.read_csv()로 파일을 읽으면 즉시 메모리에 로드되고, 각 메서드 호출마다 연산이 실행되어 결과가 반환됩니다. 이는 직관적이고 대화형 분석에 적합합니다.
중간 결과를 바로 확인할 수 있어 디버깅이 쉽습니다. Lazy API는 pl.scan_csv()로 시작합니다.
이 함수는 파일을 실제로 읽지 않고, 어떤 파일을 읽을지만 기록합니다. 이후의 모든 메서드 호출(filter, select, sort 등)도 실제로 실행되지 않고 쿼리 플랜에 추가됩니다.
LazyFrame 객체가 반환되며, 이는 실행 계획을 담고 있습니다. collect() 메서드를 호출하는 순간 Polars는 전체 쿼리 플랜을 분석합니다.
예를 들어, "name"과 "age"만 선택했다면 나머지 컬럼은 아예 읽지 않고, 필터 조건을 최대한 앞으로 당겨서 처리할 데이터 양을 줄입니다. 이러한 최적화가 자동으로 적용된 후 한 번에 실행됩니다.
explain() 메서드로 실행 계획을 확인할 수 있습니다. 이는 어떤 최적화가 적용되었는지, 실행 순서가 어떻게 되는지 보여줍니다.
성능 튜닝이나 디버깅 시 매우 유용합니다. 여러분이 Lazy API를 사용하면 특히 대용량 파일이나 복잡한 파이프라인에서 극적인 성능 향상을 경험할 수 있습니다.
실무에서는 ETL 파이프라인, 배치 처리 작업, 또는 여러 파일을 조인하는 복잡한 분석에서 Lazy API를 사용하는 것이 권장됩니다. 반면 탐색적 데이터 분석이나 프로토타이핑에서는 Eager API가 더 편리할 수 있습니다.
실전 팁
💡 Lazy API는 메모리에 들어가지 않는 대용량 파일도 처리할 수 있습니다. 스트리밍 방식으로 처리하기 때문입니다
💡 describe_optimized_plan()으로 최적화된 실행 계획을 자세히 볼 수 있습니다
💡 여러 파일을 읽을 때는 scan_csv()의 glob 패턴을 사용하면 편리합니다: pl.scan_csv("data/*.csv")
💡 Lazy API에서 중간 결과를 확인하려면 fetch()로 샘플 데이터만 가져와서 테스트할 수 있습니다
💡 프로덕션 환경에서는 가능하면 Lazy API를 사용하세요. 쿼리 최적화로 인한 성능 향상이 매우 큽니다
10. 조인 연산
시작하며
여러분이 실무에서 데이터 분석을 할 때 여러 테이블의 데이터를 결합해야 하는 경우가 많습니다. 고객 정보 테이블과 주문 테이블을 결합하거나, 제품 정보와 재고 정보를 합치는 작업이 그 예입니다.
하지만 대용량 데이터의 조인은 매우 느리고 메모리를 많이 사용합니다. 특히 키가 중복되거나 타입이 맞지 않으면 예상치 못한 결과가 나올 수 있습니다.
SQL을 사용하지 않는 환경에서 조인을 올바르게 수행하는 것은 쉽지 않습니다. Polars는 빠르고 안전한 조인 기능을 제공합니다.
join() 메서드로 inner, left, outer, cross 등 다양한 조인 방식을 지원하며, 해시 조인 알고리즘을 사용하여 대용량 데이터에서도 빠르게 작동합니다. 이 섹션에서는 실무에서 자주 사용하는 조인 패턴을 살펴보겠습니다.
개요
간단히 말해서, 조인은 두 개 이상의 DataFrame을 공통 컬럼을 기준으로 결합하는 작업입니다. 이를 통해 분산된 데이터를 통합하여 포괄적인 분석을 수행할 수 있습니다.
조인이 필요한 이유는 실무 데이터가 보통 정규화되어 여러 테이블에 나뉘어 저장되기 때문입니다. 예를 들어, 주문 데이터에는 고객 ID만 있고 고객의 이름과 주소는 별도 테이블에 있다면, 분석을 위해 두 테이블을 결합해야 합니다.
기존에는 SQL의 JOIN을 사용하거나 Pandas의 merge()를 사용했지만 성능이 느렸다면, 이제는 Polars의 최적화된 조인으로 훨씬 빠르게 처리할 수 있습니다. 핵심 특징은 다양한 조인 타입, 자동 키 매칭, 그리고 병렬 처리입니다.
inner, left, outer, semi, anti 등 모든 SQL 조인 타입을 지원하고, 같은 이름의 컬럼을 자동으로 키로 인식하며, 멀티스레딩으로 빠르게 실행됩니다.
코드 예제
import polars as pl
# 고객 데이터
customers = pl.DataFrame({
"customer_id": [1, 2, 3],
"name": ["Alice", "Bob", "Charlie"]
})
# 주문 데이터
orders = pl.DataFrame({
"order_id": [101, 102, 103],
"customer_id": [1, 2, 4],
"amount": [100, 200, 150]
})
# Inner join - 양쪽에 모두 있는 것만
result = customers.join(orders, on="customer_id", how="inner")
# Left join - 왼쪽 모두 + 오른쪽 매칭
result = customers.join(orders, on="customer_id", how="left")
# 다른 컬럼명으로 조인
result = customers.join(orders, left_on="customer_id", right_on="customer_id", how="inner")
설명
조인 연산은 관계형 데이터를 다루는 핵심 기법이며, Polars는 이를 효율적으로 수행합니다. 첫 번째로, 두 개의 DataFrame을 준비합니다.
예제에서는 고객 정보와 주문 정보를 각각 별도의 DataFrame으로 만들었습니다. 실무에서는 이러한 데이터가 보통 다른 파일이나 데이터베이스 테이블에서 옵니다.
join() 메서드로 두 DataFrame을 결합합니다. on 파라미터에 공통 컬럼명을 지정하면 해당 컬럼을 키로 사용하여 조인합니다.
how 파라미터로 조인 타입을 선택합니다. "inner"는 양쪽 모두에 있는 키만 결과에 포함하고, "left"는 왼쪽 DataFrame의 모든 행을 유지하며 오른쪽에서 매칭되는 것을 붙입니다.
Inner join은 가장 일반적인 조인으로, 두 테이블 모두에 존재하는 키만 결과에 포함됩니다. 예제에서 customer_id가 1과 2인 경우만 양쪽에 있으므로 두 행이 반환됩니다.
Left join은 왼쪽(customers) 테이블의 모든 행을 유지하므로, customer_id가 3인 Charlie도 포함되지만 주문 정보는 null로 채워집니다. 컬럼명이 다른 경우 left_on과 right_on으로 각각 지정할 수 있습니다.
여러 컬럼을 키로 사용하려면 리스트로 전달합니다: on=["col1", "col2"]. 이는 복합 키 조인에 유용합니다.
여러분이 조인을 마스터하면 복잡한 데이터 관계를 쉽게 다룰 수 있습니다. 실무에서는 차원 테이블과 팩트 테이블을 결합하거나, 마스터 데이터와 트랜잭션 데이터를 통합하거나, 여러 소스의 데이터를 하나로 모아 분석하는 등의 작업에 활용됩니다.
Polars의 빠른 조인 덕분에 수백만 개의 레코드도 효율적으로 결합할 수 있습니다.
실전 팁
💡 validate 파라미터로 조인의 카디널리티를 검증할 수 있습니다: validate="1:1"은 일대일 관계를 강제합니다
💡 suffix 파라미터로 중복 컬럼명에 추가할 접미사를 지정할 수 있습니다: suffix="_right"
💡 조인 전에 키 컬럼의 중복과 null 값을 확인하여 예상치 못한 결과를 방지하세요
💡 대용량 조인은 Lazy API를 사용하면 조인 순서나 필터 최적화가 자동으로 적용되어 더 빠릅니다
💡 semi join과 anti join은 존재 여부만 확인할 때 유용합니다. semi는 매칭되는 것만, anti는 매칭되지 않는 것만 반환합니다