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

[PYTHON] Pandas apply 함수와 벡터화 연산의 100배 성능 차이 및 최적화 해결 방법

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

Pandas apply 함수와 벡터화 연산
Pandas apply 함수와 벡터화 연산

 

 

데이터 사이언스와 금융 알고리즘 개발 분야에서 파이썬(Python)의 Pandas 라이브러리는 표준과도 같습니다. 하지만 많은 개발자들이 데이터프레임을 다룰 때 가장 큰 성능 병목 지점을 만드는데, 그것이 바로 apply 함수의 오남용입니다. 본 아티클에서는 apply 함수와 벡터화(Vectorization) 연산의 근본적인 메커니즘 차이를 분석하고, 실무에서 연산 속도를 극대화할 수 있는 7가지 실전 해결 방법을 제시합니다.


1. 데이터 처리의 패러다임: 반복문 vs 벡터화

파이썬은 인터프리터 언어 특성상 순환문(Loop)이 매우 느립니다. Pandas의 apply 함수는 사용자 편의성을 제공하지만, 내부적으로는 파이썬 수준의 반복문을 실행하기 때문에 대용량 데이터에서 치명적인 성능 저하를 유발합니다. 반면, 벡터화 연산은 저수준 C와 Fortran으로 최적화된 NumPy 아키텍처를 활용하여 CPU의 SIMD(Single Instruction, Multiple Data) 기능을 사용합니다.

비교 항목 Pandas apply() 함수 벡터화 연산 (Vectorization) 비고
연산 방식 Row-wise 또는 Column-wise 반복 배열 전체에 대한 일시 연산 근본적인 아키텍처 차이
실행 속도 느림 (데이터량에 비례해 급증) 매우 빠름 (최대 100~1000배) 실무 성능의 핵심
메모리 효율 객체 생성 오버헤드 발생 연속된 메모리 블록 활용 L1/L2 캐시 최적화
가독성 사용자 정의 함수 적용 용이 수학적 표기법과 유사 코드가 간결해짐

2. 성능 최적화를 위한 7가지 실무 해결 Example

개발자가 현업에서 대규모 데이터를 처리할 때 즉시 적용하여 드라마틱한 속도 향상을 경험할 수 있는 코드 예제입니다.

Example 1: 단순 산술 연산의 벡터화 (Speed Up: 50x+)

가장 흔한 실수인 apply(lambda x: ...) 형태를 NumPy 내장 연산으로 대체하는 방법입니다.


import pandas as pd
import numpy as np
import time

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

# [Bad] apply 방식
start = time.time()
df['C'] = df.apply(lambda row: row['A'] + row['B'], axis=1)
print(f"Apply speed: {time.time() - start:.4f}s")

# [Good] 벡터화 방식
start = time.time()
df['C'] = df['A'] + df['B']
print(f"Vectorization speed: {time.time() - start:.4f}s")

Example 2: 조건부 로직 (If-Else) 해결을 위한 np.where

복잡한 if-else 문을 apply로 처리하면 속도가 매우 느려집니다. np.where는 이를 획기적으로 해결합니다.


# [Bad] 함수 정의 후 apply
def logic(row):
    if row['A'] > 0: return row['A'] * 2
    else: return row['A'] / 2

# [Good] np.where 사용
df['D'] = np.where(df['A'] > 0, df['A'] * 2, df['A'] / 2)

Example 3: 다중 조건 처리를 위한 np.select

3가지 이상의 조건이 겹칠 때 apply 대신 사용하는 마법 같은 함수입니다.


conditions = [
    (df['A'] >= 1),
    (df['A'] < 1) & (df['A'] >= 0),
    (df['A'] < 0)
]
choices = ['High', 'Medium', 'Low']

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

Example 4: 문자열 연산의 벡터화 (.str 접근자)

문자열 처리도 파이썬 반복문 대신 Pandas 내장 벡터화 문자열 메서드를 사용해야 합니다.


df['Name'] = ['user_' + str(i) for i in range(1000000)]

# [Good] 벡터화 문자열 처리
df['Upper_Name'] = df['Name'].str.upper()
df['Is_User'] = df['Name'].str.contains('user')

Example 5: 수학 함수 및 로그 변환 최적화

math.log 대신 np.log를 사용하면 전체 열을 한 번에 계산할 수 있습니다.


# [Good] NumPy 로그 벡터화
df['Log_A'] = np.log(df['A'].abs() + 1)

Example 6: 그룹 연산 최적화 (Transform vs Apply)

그룹화 후 연산 시 apply보다 transform이나 agg가 내부적으로 더 고도로 최적화되어 있습니다.


# [Good] 그룹 평균 차이 계산
df['Group_Mean_Diff'] = df.groupby('Grade')['A'].transform(lambda x: x - x.mean())

Example 7: 최후의 수단, Numba JIT 컴파일

벡터화가 불가능한 복잡한 로직이라면 numba 라이브러리를 통해 파이썬 코드를 기계어로 컴파일합니다.


from numba import jit

@jit(nopython=True)
def heavy_calc(a_vals, b_vals):
    res = np.empty(a_vals.shape)
    for i in range(len(a_vals)):
        res[i] = a_vals[i] ** 2 + b_vals[i] # 루프지만 기계어로 실행됨
    return res

df['E'] = heavy_calc(df['A'].values, df['B'].values)

3. 왜 벡터화가 100배 더 빠른가? (기술적 분석)

이 성능 차이의 핵심은 오버헤드(Overhead)에 있습니다. apply 함수가 호출될 때마다 파이썬은 각 행을 시리즈 객체로 감싸고(Boxing), 함수를 호출하며, 결과를 다시 언박싱하는 과정을 수백만 번 반복합니다. 반면 벡터화 연산은 메모리상에 연속적으로 배치된 C-배열을 따라가며 CPU 레지스터에서 즉시 연산되므로 불필요한 객체 생성이 전혀 없습니다.


4. 결론 및 요약

데이터가 커질수록 apply와 벡터화의 간극은 기하급수적으로 벌어집니다. 1,000만 행 이상의 데이터를 다루는 터보퀀트나 실시간 분석 시스템에서는 단 하나의 apply가 전체 파이프라인의 병목이 될 수 있습니다. 항상 "이 로직을 NumPy 함수로 대체할 수 있는가?"를 먼저 고민하십시오.

 

내용 출처:
1. "Python for Data Analysis", Wes McKinney (Pandas Creator).
2. NumPy Documentation - Array Programming (https://numpy.org/doc/).
3. Effective Pandas, Matt Harrison.

Python, Pandas, Vectorization, Apply Function, Performance Optimization, NumPy, Data Engineering.

728x90