Skip to main content

성능 최적화

데이터 크기가 커지면 Pandas의 처리 속도와 메모리 사용량이 병목이 됩니다. eval/query 최적화, 카테고리형 활용, 대용량 처리 전략을 적용하면 같은 하드웨어에서도 훨씬 효율적으로 분석할 수 있습니다.

학습 목표

  • eval()과 query()로 대용량 데이터 연산을 최적화할 수 있다
  • 카테고리형 변환으로 메모리를 절약할 수 있다
  • chunksize와 Parquet으로 대용량 파일을 효율적으로 처리할 수 있다
  • 메모리 프로파일링으로 병목을 식별할 수 있다

왜 중요한가

실무에서는 수십~수백 GB의 데이터를 다루는 경우가 흔합니다. 최적화 없이는 메모리 부족(OOM)이 발생하거나 처리 시간이 과도하게 길어집니다. 적절한 최적화 기법을 적용하면 분석 생산성이 크게 향상됩니다.

eval과 query

import pandas as pd
import numpy as np

# 100만 행 데이터
n = 1_000_000
df = pd.DataFrame({
    'a': np.random.randn(n),
    'b': np.random.randn(n),
    'c': np.random.randn(n),
    'd': np.random.randn(n)
})

# 일반 방식
result = df['a'] + df['b'] * df['c'] - df['d']

# eval 방식 — 중간 배열 생성을 줄여 메모리 효율적
result = df.eval('a + b * c - d')

# 새 열 생성
df.eval('e = a + b * c - d', inplace=True)

# query로 필터링
filtered = df.query('a > 0 and b < 0.5')

# 변수 참조
threshold = 1.5
filtered = df.query('a > @threshold')
eval()query()는 100만 행 이상의 대용량 데이터에서 효과적입니다. 소규모 데이터에서는 일반 연산이 더 빠를 수 있습니다.

메모리 최적화 전략

# 1. dtype 다운캐스트
for col in df.select_dtypes(include=['float64']).columns:
    df[col] = pd.to_numeric(df[col], downcast='float')

for col in df.select_dtypes(include=['int64']).columns:
    df[col] = pd.to_numeric(df[col], downcast='integer')

# 2. 카테고리형 변환
for col in df.select_dtypes(include=['object']).columns:
    if df[col].nunique() / len(df) < 0.5:
        df[col] = df[col].astype('category')

# 3. 불필요한 열 제거
df = df.drop(columns=['unused_col1', 'unused_col2'])

# 4. 메모리 사용량 확인
print(f"메모리: {df.memory_usage(deep=True).sum() / 1024**2:.1f} MB")

대용량 파일 처리

# 방법 1: chunksize
results = []
for chunk in pd.read_csv('large.csv', chunksize=50000):
    processed = chunk[chunk['value'] > 0].groupby('category').sum()
    results.append(processed)
final = pd.concat(results).groupby(level=0).sum()

# 방법 2: Parquet (추천)
# CSV → Parquet 변환 (한 번만)
df.to_parquet('data.parquet', index=False, compression='snappy')

# 필요한 열만 읽기
df = pd.read_parquet('data.parquet', columns=['col1', 'col2'])

# 방법 3: usecols + dtype 지정
df = pd.read_csv('large.csv',
    usecols=['id', 'value', 'category'],
    dtype={'id': 'int32', 'value': 'float32', 'category': 'category'}
)

연산 성능 팁

느린 패턴빠른 대체속도 향상
df.iterrows()벡터화 연산100x+
apply(lambda)np.where()10~50x
df['a'] + df['b']df.eval('a + b')2~5x (대용량)
df[조건1][조건2]df.query()2~3x (대용량)
CSV 반복 읽기Parquet 사용5~10x I/O
object 문자열category 변환메모리 10x+

AI/ML에서의 활용

  • 피처 스토어: 전처리된 데이터를 Parquet으로 저장하여 학습 시 빠르게 로드합니다
  • 대규모 데이터셋: chunksize로 메모리 한계를 넘는 데이터를 처리합니다
  • 실험 효율: dtype 최적화로 더 많은 데이터를 GPU 메모리에 올릴 수 있습니다
  • 파이프라인 최적화: eval/query로 전처리 파이프라인의 속도를 개선합니다
Pandas로 처리할 수 없을 정도로 대용량(수십 GB 이상)이면 Polars(단일 머신)나 Dask(분산 처리)를 고려하세요. 대부분의 분석 작업은 최적화된 Pandas로 충분합니다.
Pandas 공식 문서에서도 inplace=True는 권장하지 않습니다. 가독성이 떨어지고, 실제 메모리 절약 효과도 미미합니다. df = df.method()처럼 할당 방식을 사용하세요.

체크리스트

  • eval()로 복합 산술 연산을 최적화할 수 있다
  • query()로 필터링 성능을 개선할 수 있다
  • dtype 다운캐스트와 카테고리형으로 메모리를 줄일 수 있다
  • chunksize로 대용량 CSV를 분할 처리할 수 있다
  • Parquet 형식의 장점을 활용할 수 있다

다음 문서