Apply는 dataframe의 각 행이나 열에 User-defined function을 실행할 때 사용하는 흔한 옵션입니다. axis=1로 실행하게 되면 각 row에 대해서 연산을 수행하게 됩니다. 하지만, 데이터 사이즈가 큰 경우 메모리 접근 방식과 및 벡터화 되지 않은 연산으로 성능 문제가 발생하기 쉽습니다. 이 글에서는 성능 이슈가 발생하는 원인과 해결책을 간단하게 정리해봤습니다.
apply 함수의 성능 이슈 원인
1. 메모리 접근 패턴
pandas DataFrame은 열(column)-기반의 데이터 저장 구조를 사용합니다. 따라서 열 단위로 데이터에 접근하는 것이 메모리에서 연속적이므로 빠릅니다. 그러나 apply를 사용하여 행(row) 단위로 함수를 적용할 때, 각 행의 데이터는 여러 열에서 가져와져야 하므로 메모리 접근이 연속적이지 않습니다. 이로 인해 캐시 미스와 같은 비효율적인 메모리 접근 패턴이 발생할 수 있습니다.
2. 벡터화 되지 않은 연산
pandas와 numpy는 벡터화된 연산을 사용하여 데이터를 빠르게 처리합니다. 그러나 apply 메서드를 사용하면 일반적으로 벡터화되지 않은 연산이 수행됩니다. 이는 성능에 부정적인 영향을 미칠 수 있습니다.
3. 데이터 타입 체크
apply 함수 내에서 데이터 타입 변환이나 체크가 발생할 경우, 이는 추가적인 오버헤드를 발생시킬 수 있습니다.
4. Python 함수 호출 오버헤드
apply 메서드는 각 행 또는 열에 대해 Python 함수를 반복적으로 호출합니다. 이러한 반복적인 함수 호출은 Python의 인터프리터 오버헤드를 초래할 수 있습니다.
성능 이슈를 해결하는 방법
코드와 함께 살펴보면 아래와 같습니다.
- 최대한 pandas 자체의 벡터 연산 활용하기 (Vectorized Function)
- swifter 사용하기
import pandas as pd
import numpy as np
import swifter
def user_func(x) -> int:
total = sum(x)
return total
df = pd.DataFrame(np.random.randint(0, 100, size=(3000000, 3)), columns=('a','b','c'))
df2 = df.copy()
df3= df.copy()
%%timeit -n 3 -r 1
df['total'] = df.apply(func=user_func, axis=1)
6.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 3 loops each)
%%timeit -n 3 -r 1
df2['total'] = df2.sum(axis=1)
173 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 3 loops each)
%%timeit -n 3 -r 1
df3['total'] = df3.swifter.apply(func=user_func, axis=1)
Dask Apply: 0%| | 0/20 [00:00<?, ?it/s]
Dask Apply: 0%| | 0/20 [00:00<?, ?it/s]
Dask Apply: 0%| | 0/20 [00:00<?, ?it/s]
4.86 s ± 0 ns per loop (mean ± std. dev. of 1 run, 3 loops each)
Reference
'Computer Engineering > Data Engineering' 카테고리의 다른 글
Airflow KubernetesPodOperator 예제 코드 및 설명 (0) | 2023.07.22 |
---|---|
초기 스타트업에서 Data Engineer는 어떤 일을 하나요? (0) | 2023.07.10 |
Pandas DataFrame 조인 및 병합 / pandas merge, join, concat, (0) | 2023.07.02 |
Airflow CeleryKubernetesExecutor 사용하기 (0) | 2023.06.16 |
Pandas pivot_table 예제 및 설명 (0) | 2023.05.01 |