본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] Pandas apply 함수 성능 문제 해결과 Vectorized Operation 전환을 위한 7가지 전략

by Papa Martino V 2026. 4. 26.
728x90

Vectorized Operation
Vectorized Operation

 

데이터 사이언스와 분석 업무에서 Pandas는 대체 불가능한 도구입니다. 하지만 데이터의 규모가 커질수록 많은 개발자가 apply() 함수의 늪에 빠지곤 합니다. apply()는 유연하고 사용하기 편리하지만, 내부적으로는 파이썬의 루프를 그대로 사용하기 때문에 대용량 데이터 처리 시 치명적인 성능 저하를 유발합니다. 본 포스팅에서는 apply() 함수가 왜 느린지 그 구조적 원인을 분석하고, 이를 Vectorized Operation(벡터화 연산)으로 전환하여 성능을 최대 수백 배까지 끌어올리는 구체적인 방법 7가지를 실무 예제와 함께 살펴봅니다.


1. 왜 Pandas의 apply()는 느린가?

Pandas의 apply()는 본질적으로 "Python-level Loop"입니다. Pandas는 내부적으로 C로 작성되어 매우 빠르지만, apply()를 호출하는 순간 각 행(row) 또는 열(column)을 파이썬 객체로 변환하여 사용자 정의 함수를 호출합니다. 이 과정에서 발생하는 Overhead가 성능 저하의 주범입니다.

반면, Vectorized Operation은 NumPy를 기반으로 하여 데이터를 연속된 메모리 블록에 배치하고, CPU의 SIMD(Single Instruction, Multiple Data) 기능을 활용해 한 번의 명령으로 여러 데이터를 처리합니다.


2. 성능 비교 요약 (Loop vs Apply vs Vectorization)

데이터 1,000만 건을 기준으로 각 처리 방식의 특징과 성능 차이를 비교한 표입니다.

비교 항목 Python for-loop Pandas apply() Vectorized Operation
처리 속도 매우 느림 (최악) 느림 (오버헤드 존재) 매우 빠름 (최적)
가독성 직관적이나 코드가 길어짐 매우 높음 (간결함) 높음 (수학적 표현)
메모리 효율 낮음 보통 매우 높음
추천 상황 사용 금지 복잡한 로직/소량 데이터 대용량 데이터 처리 필수

3. 실무 적용을 위한 7가지 벡터화 전환 Example

실제 현업 프로젝트에서 자주 마주치는 apply() 패턴들을 어떻게 벡터화된 방식으로 바꾸는지 코드를 통해 확인해 보세요.

Example 1: 단순 산술 연산의 최적화

가장 기초적이지만 빈번한 실수입니다. 행 단위 계산을 직접 수식으로 표현합니다.

import pandas as pd
import numpy as np

df = pd.DataFrame({'A': np.random.randn(1000000), 'B': np.random.randn(1000000)})

# [Slow] apply 방식
# df['C'] = df.apply(lambda x: x['A'] + x['B'], axis=1)

# [Fast] Vectorized 방식
df['C'] = df['A'] + df['B']

Example 2: 조건부 로직 (If-Else)의 벡터화

if-else 문을 사용하기 위해 apply()를 쓰는 경우가 많습니다. 이때는 np.where가 해결책입니다.

# [Slow] apply 방식
# df['Status'] = df['A'].apply(lambda x: 'High' if x > 0 else 'Low')

# [Fast] np.where 방식 (벡터화)
df['Status'] = np.where(df['A'] > 0, 'High', 'Low')

Example 3: 다중 조건문 처리 (np.select)

조건이 3개 이상일 때 apply() 대신 np.select를 사용하면 가독성과 성능을 모두 잡을 수 있습니다.

conditions = [
    (df['A'] >= 1),
    (df['A'] < 1) & (df['A'] >= 0),
    (df['A'] < 0)
]
choices = ['Excellent', 'Good', 'Bad']

# [Fast] np.select 활용
df['Grade'] = np.select(conditions, choices, default='Unknown')

Example 4: 문자열 데이터 처리 (str accessor)

문자열 파싱 작업 시 apply(lambda x: x.split(...)) 대신 Pandas의 내장 문자열 벡터화 함수를 사용하세요.

df['Email'] = ["user_test@gmail.com"] * 1000000

# [Slow] apply(lambda x: x.split('@')[0])
# [Fast] str 접근자 사용
df['ID'] = df['Email'].str.split('@').str[0]

Example 5: 날짜 데이터 연산 최적화

날짜 차이 계산이나 요일 추출 시 dt 접근자를 활용하면 루프 없이 한 번에 계산됩니다.

df['Date'] = pd.to_datetime(['2024-01-01'] * 1000000)

# [Fast] dt 접근자 활용
df['Year'] = df['Date'].dt.year
df['Is_Month_End'] = df['Date'].dt.is_month_end

Example 6: 수치 데이터의 클리핑 (Clipping)

특정 범위를 벗어나는 데이터를 상한/하한값으로 고정할 때 apply 대신 clip을 사용합니다.

# [Fast] 벡터화된 clip 함수
df['A_Clipped'] = df['A'].clip(lower=-1.0, upper=1.0)

Example 7: 그룹별 연산 (Transform vs Apply)

그룹 통계량을 원래 인덱스에 맞춰 확장할 때는 groupby().apply()보다 transform()이 훨씬 빠릅니다.

# [Slow] df.groupby('Category')['Value'].apply(lambda x: x - x.mean())
# [Fast] transform 방식
df['Mean_Diff'] = df.groupby('Category')['Value'].transform(lambda x: x - x.mean())

4. 결론 및 권장 사항

데이터 분석 파이프라인에서 apply() 함수를 완전히 제거하는 것은 불가능할 수도 있습니다. 하지만 수치 연산, 조건문, 문자열 처리, 날짜 연산 등 Pandas와 NumPy가 이미 최적화된 함수(Vectorized functions)를 제공하는 영역에서는 반드시 전환이 필요합니다. 성능 최적화는 단순히 코드가 빨리 돌아가는 것을 넘어, 인프라 비용 절감과 분석 사이클의 단축이라는 비즈니스 가치를 창출합니다. 오늘 소개한 7가지 기법을 통해 여러분의 파이썬 코드를 한 단계 업그레이드해 보세요.

 

[참고 문헌 및 출처]
1. Pandas Official Documentation: "Enhancing performance" section.
2. Wes McKinney, "Python for Data Analysis", O'Reilly Media.
3. NumPy Documentation: "Array programming and vectorization".
4. Real Python: "Fast, Flexible, Pandas: Optimizing Your Data Analysis".

728x90