courses
저처럼 Python으로 데이터 분석을 자주 하다 보면 CSV 파일을 읽는 작업이 가장 흔하다는 것을 금방 알게 됩니다. 하지만 데이터셋이 커질수록 이 방법은 느려지거나 메모리를 많이 사용할 수 있습니다.
Polars는 Pandas의 고성능 대안으로 설계된 빠르고 현대적인 Python용 DataFrame 라이브러리입니다. 속도와 낮은 메모리 사용에 초점을 맞춰 구축되었기 때문에 전통적인 도구보다 훨씬 부드럽게 대규모 데이터셋을 처리할 수 있습니다.
Polars의 핵심 함수인 pl.read_csv()는 CSV 파일을 DataFrame으로 로드하는 간단한 해결책을 제공하며, 파싱, 데이터 타입, 메모리 사용을 제어하는 옵션이 내장되어 있습니다.
이 가이드에서는 Polars의 pl.read_csv() 함수를 사용해 CSV 파일을 읽고, 파싱을 제어하며, 대용량 데이터셋을 처리하는 방법을 보여드리겠습니다. 처음 시작한다면 Polars로 데이터를 조작하고 인사이트를 도출하는 방법을 배우기 위해 Introduction to Polars 과정을 확인해 보세요.
pl.read_csv()의 기본 사용법
더 자세히 보기 전에, 환경 설정 방법을 배우기 위해 Python Polars 튜토리얼을 확인하세요.
이제 pl.read_csv() 함수가 어떻게 동작하는지 살펴보겠습니다:
# import the module
import polars as pl
# Load CSV into a Polars DataFrame
fuel_data = pl.read_csv("Fuel_Consumption_2000-2022.csv")
# Preview first few rows
print(fuel_data.head())
위 예시에서 Polars는 파일을 읽어 DataFrame으로 로드합니다. 이 함수는 pandas에서 얻을 수 있는 것과 유사한 표 형식의 데이터 구조인 Polars DataFrame을 반환합니다.
기본적으로 pl.read_csv()는 다음과 같습니다.
-
첫 번째 행에 열 이름이 있다고 가정합니다(
has_header=True). -
열의 데이터 타입을 자동으로 추론합니다.
-
구분 기호로 쉼표(
,)를 사용합니다. -
파일 전체를 메모리로 읽어옵니다.
pl.read_csv()의 일반적인 매개변수
이제 pl.read_csv()가 데이터를 어떻게 로드하는지 알았으니, Polars가 데이터를 파싱하는 방식을 아래 매개변수로 어떻게 조정할 수 있는지 살펴보겠습니다.
파일 경로와 소스
Polars는 다양한 소스에서 데이터를 로드할 수 있습니다. 로컬 문자열 경로, Pathlib 객체, 또는 URL을 전달할 수 있습니다. 예를 들어, 아래 코드는 여러 해의 서로 다른 국가의 GDP를 담은 대형 웹 csv 파일을 읽습니다.
# Reading from a URL
url = "https://raw.githubusercontent.com/datasets/gdp/master/data/gdp.csv"
gdp_data = pl.read_csv(url)
구분 기호와 구분자
모든 CSV 파일이 쉼표를 사용하지는 않습니다. separator 인수를 사용해 탭, 세미콜론, 파이프, 기타 문자들을 처리할 수 있습니다. 아래 예시는 파일을 읽을 때 구분 기호를 지정하는 방법을 보여줍니다.
# Reading a Semicolon-separated file
sales_data = pl.read_csv("2024_sales.csv", separator=";")
# Reading a Tab-separated file (TSV)
orders_data = pl.read_csv("all_orders.tsv", separator="\t")
헤더 처리
앞서 본 것처럼 Polars는 데이터의 첫 번째 행에 열 이름이 있다고 가정합니다. 파일에 헤더 행이 없다면 아래와 같이 has_header=False 매개변수를 사용하세요. Polars는 column_1, column_2, column_3 등의 열 이름을 자동으로 부여합니다.
# Load file without a header
orders_data = pl.read_csv("all_orders.csv", has_header=False)
특정 열 이름을 제공해 열을 바꿀 수도 있습니다. 예를 들어:
# Providing specific column names
sales_data = pl.read_csv("sales_April.csv", new_columns=[
"OrderDate", "OrderNumber", "ProductKey", "SalespersonKey", "Salesperson"])
인코딩
CSV 파일마다 텍스트 인코딩이 다를 수 있습니다. 이상한 문자가 보이거나 오류가 발생하면 인코딩을 지정하세요:
# Load CSV into a Polars DataFrame
fuel_data = pl.read_csv("Fuel_Consumption_2000-2022.csv", encoding="utf8")
다른 일반적인 인코딩 옵션으로 ”latin1”과 ”utf8-lossy”가 있으며, 잘못된 문자를 적절히 처리합니다.
Polars에서 CSV를 읽을 때 열 선택하기
대용량 CSV 파일을 다룰 때 모든 열이 필요한 것은 아닙니다. Polars에서는 columns 매개변수를 사용해 필요한 열만 로드할 수 있습니다.
# Load only specific columns
fuel_data = pl.read_csv("Fuel_Consumption_2000-2022.csv",
columns=["YEAR", "MAKE", "MODEL"]
)
파일을 로드할 때 필요한 열만 선택하면 불필요한 데이터를 로드하지 않아 메모리 사용량이 줄어듭니다. 이 방법은 파일 읽기 속도도 높이고 전체 파이프라인 성능을 개선합니다.
Polars CSV에서 데이터 타입(스키마) 다루기
Polars는 강타입 라이브러리이므로 각 열은 모든 정수 또는 모든 문자열처럼 일관된 데이터 타입을 가져야 합니다.
자동 타입 추론
기본적으로 Polars는 데이터를 검사해 열 타입을 자동으로 결정합니다. 기본 방식은 대부분의 경우 잘 동작하지만, 때로는 열을 잘못 해석해 ID를 문자열이 아닌 정수로 처리하는 등의 문제가 생길 수 있습니다.
스키마를 수동으로 지정하기
DataFrame의 일관성을 원한다면 데이터 스키마를 수동으로 지정하는 것이 좋습니다. 이렇게 하면 여러 파일에서 일관성을 보장하고, 후속 처리에서 타입 관련 오류를 피할 수 있습니다.
대부분의 경우 schema_overrides를 사용해 특정 열의 데이터 타입만 지정하고 나머지는 Polars가 추론하도록 두면 됩니다.
import polars as pl
# Manually overriding specific data types
empl_data = pl.read_csv(
"all_employees.csv",
schema_overrides={
"id": pl.Int64,
"name": pl.String,
"age": pl.Int64,
"salary": pl.Float64
}
)
또는 schema를 사용해 DataFrame의 전체 구조를 정의하세요.
# Manually define the full schema
empl_data = pl.read_csv(
"all_employees.csv",
schema={
"id": pl.Int64,
"name": pl.String,
"age": pl.Int64,
"salary": pl.Float64
}
)
Polars CSV에서 누락값 처리 방법
기본적으로 Polars는 이러한 누락값을 자동으로 감지하여 null로 표현합니다. 때로는 ”NA”, ”N/A”, ”missing” 같은 특정 문자열로 누락값을 표시하기도 합니다. 이런 경우 null_values로 정의해 null로 처리하도록 할 수 있습니다.
# Treating "N/A" and "EMPTY" as nulls
survey_data = pl.read_csv(
"survey_results.csv",
null_values=["N/A", "EMPTY", "null"]
)
누락값을 올바르게 처리하면 데이터 타입의 일관성을 보장하고 잘못된 계산을 방지해, 분석과 모델링 중 오류를 줄일 수 있습니다.
Polars로 대용량 CSV 파일 읽기
사용 가능한 RAM을 초과하는 대용량 데이터셋을 다룰 때, Polars는 파일 전체를 한 번에 로드하지 않고 데이터를 처리해 진가를 발휘합니다.
scan_csv()로 지연 로딩
read_csv()처럼 즉시 메모리에 데이터를 로드하는 대신, Polars는 지연 대안인 scan_csv()를 제공하여 최적화된 쿼리 플랜을 구축하고 필요한 작업만 실행합니다.
아래 예시에서 pl.scan_csv는 파일을 로드하지 않고 CSV를 스캔한 뒤, 자동차 브랜드가 “ACURA”인 행에서 “YEAR”, “MAKE”, “MODEL” 열만 가져오는 쿼리를 구성하고, 그 쿼리를 실행합니다. 이렇게 하면 전체 파일이 아니라 필터링된 일부 데이터만 메모리에 로드됩니다.
# Lazily reference the file (no data loaded yet)
fuel_consumption = pl.scan_csv("Fuel_Consumption_2000-2022.csv")
fuel_consumption_filtered = fuel_consumption.select(
["YEAR", "MAKE", "MODEL"]).filter(pl.col("MAKE") == "ACURA")
# Execute the query and load only the required data
fuel_consumption_acura = fuel_consumption_filtered.collect()
scan_csv() 사용을 권장하는 경우:
- 메모리에 여유롭게 담기 어려운 대용량 파일을 다룰 때
- 필터링, 선택, 집계 등 여러 변환을 적용할 때
- 실행 전에 쿼리 최적화를 원할 때
스트리밍과 메모리 효율
Polars는 데이터를 청크로 처리하는 스트리밍도 지원해, 파일 크기와 무관하게 피크 메모리를 일정하게 유지합니다.
대형 CSV 파일의 경우, 먼저 scan_csv()로 지연 쿼리를 생성하세요. scan_csv()를 호출해도 파일이 완전히 로드되지는 않습니다.
# Read data in chunks
fuel_consumption = pl.scan_csv("Fuel_Consumption_2000-2022.csv",
low_memory=True)
위 방법은 여전히 read_csv를 사용하므로, 최선의 접근은 지연 실행과 스트리밍을 결합하는 것입니다.
아래 예시에서 Polars는 파이프라인으로 파일을 배치 단위로 행별 처리하며, 어떤 시점에도 메모리에는 필터링된 행(“CYLINDERS” > 3)만 유지합니다.
# Lazily reference the file (no data loaded yet)
fuel_consumption = pl.scan_csv("Fuel_Consumption_2000-2022.csv")
result = fuel_consumption.filter(pl.col("CYLINDERS") > 3)
# Stream execution instead of loading everything at once
fuel_consumption_high = result.collect(engine="streaming")
Pandas 대비 성능 이점
pandas와 비교하면, 다음 섹션에서 보듯 Polars는 CSV 읽기와 처리에서 더 나은 성능을 보이는 경우가 많습니다.
Polars read_csv vs Pandas read_csv
아래 표는 Python에서 csv를 읽을 때 Polars와 Pandas의 차이를 요약합니다.
|
항목 |
Polars |
Pandas |
|
속도 |
멀티스레딩과 최적화된 파싱으로 더 빠름 |
대용량 파일에서 더 느림(대부분 단일 스레드) |
|
메모리 사용 |
더 낮은 메모리 사용; 지연 + 스트리밍 지원 |
전체 데이터셋을 즉시 메모리에 로드 |
|
문법 |
|
|
|
실행 모델 |
지연 실행 지원( |
즉시(이터) 실행만 지원 |
|
최적화 |
내장 쿼리 최적화(프로젝션, 필터링) |
자동 최적화가 제한적 |
|
권장 사용 사례 |
대용량 데이터셋, 성능이 중요한 워크플로 |
소규모 데이터셋, 빠른 분석 |
분석에 어떤 도구가 더 적합한지 판단하려면 Pandas와 Polars의 차이점을 다룬 기사를 읽어 보시길 권합니다.
다양한 소스에서 CSV 읽기
앞서 본 것처럼, Polars를 사용하면 서로 다른 소스에서 csv 파일을 읽을 수 있습니다. 위 예시들에서 로컬 파일과 URL에서 csv를 읽는 방법을 보여드렸습니다.
또한 Polars는 압축된 CSV 파일을 자동으로 처리할 수 있습니다. 예를 들어, 아래 코드는 .gzip 파일에서 데이터를 읽습니다.
# Read gzip-compressed CSV
census_data = pl.read_csv("2018_census.csv.gz")
일반적인 오류와 문제 해결
Polars처럼 고성능 엔진을 사용하더라도, 특히 csv 파일을 읽을 때 몇 가지 문제에 부딪힐 수 있습니다. 아래는 제가 겪은 일반적인 문제와 해결 방법입니다.
-
인코딩 오류:
UnicodeDecodeError같은 오류가 표시될 수 있는데, 이는 파일이 UTF-8 인코딩을 사용하지 않는다는 의미입니다. 오래된 파일이나 특정 버전의 Excel에서 내보낸 CSV에서 흔한 문제입니다. 이를 해결하려면 적절한 인코딩을 설정하거나, 문자가 불일치하거나 깨진 경우”utf8-lossy”를 사용하세요. -
구분 기호 문제: DataFrame이 로드되었지만 모든 데이터가 하나의 열에 몰려 있다면, Polars가 구분 기호를 인식하지 못한 것입니다. 이 문제를 해결하려면
separator인수를 사용해 세미콜론(;), 탭(\t), 파이프(|) 등 올바른 구분 기호를 명시적으로 설정하세요. -
잘못된 데이터 타입: 가끔 Polars가 열 타입을 잘못 추론해 숫자형 ID를 문자열로 읽거나 파싱 오류가 발생할 수 있습니다. 이 경우
schema_overrides로 특정 열의 데이터 타입을 지정하세요. 모든 열의 타입을 정의하고 싶다면schema를 사용하세요. 또한infer_schema_length를 늘려 Polars가 스키마를 추론하기 전에 더 많은 행을 검사하도록 할 수 있습니다. -
대용량 파일 메모리 문제: 큰 파일을 로드하는 도중 Python 프로세스가 중단된다면 RAM이 고갈된 것입니다. 이를 해결하려면 메모리 사용을 줄이고 성능을 개선하기 위해
read_csv()에서scan_csv()로 전환해 지연 로딩을 사용하세요. 데이터셋의 열 수를 줄여 로드하거나, 스트림 실행으로 데이터를 청크 단위로 로드하는 방법도 있습니다.
결론
Polars의 read_csv() 함수는 사용법은 간단하지만 실무 작업에 충분히 강력합니다. 제 생각에, 데이터셋이 커질수록 Polars의 장점이 분명해집니다.
다음 단계로, 다양한 활용 사례를 알아보려면 Polars GPU 엔진에 대한 블로그를 확인하세요. 실습을 시작할 준비가 되었다면, 실제 분석 과제를 해결하고 계산을 적용하며 스포츠 분석 문제에 기본 ML 기법을 적용해 보는 Super Bowl Analytics with Polars 코드 따라 하기를 살펴보세요.
FAQs
Polars에서 read_csv()와 scan_csv()의 차이는 무엇인가요?
read_csv()는 데이터를 즉시 메모리에 로드하는 반면, scan_csv()는 지연 실행을 사용하며 .collect()를 호출할 때만 데이터를 처리합니다.
read_csv() 대신 scan_csv()는 언제 사용해야 하나요?
대규모 데이터셋이거나 변환을 체이닝할 때는 scan_csv()를 사용하세요. 실행을 최적화하고 메모리 사용량을 줄여줍니다.
Polars는 CSV 파일에서 특정 열만 읽을 수 있나요?
네, read_csv()에서 columns=[...] 매개변수를 사용하거나, scan_csv()의 지연 쿼리에서 열을 선택하면 됩니다.
Polars CSV 파일에서 누락값은 어떻게 처리하나요?
Polars는 기본적으로 빈 값을 null로 처리하며, null_values=를 사용해 사용자 지정 누락값 표식을 정의할 수 있습니다.
Polars는 압축된 CSV 파일을 지원하나요?
네, 수동 추출 없이 .gz와 .zip 같은 압축 포맷을 읽을 수 있습니다.