메뉴 닫기

pandas rolling.apply 엔진 완전정복, Numba와 Cython으로 윈도우·리샘플 성능 끌어올리기

pandas rolling.apply 엔진 완전정복, Numba와 Cython으로 윈도우·리샘플 성능 끌어올리기

🚀 실무 데이터 처리 속도를 높이는 rolling().apply(func, engine=’numba’|’cython’) 핵심 가이드

시간 기반 윈도우 계산과 리샘플을 함께 다루다 보면, 반복 계산이 쌓이며 처리 시간이 급격히 늘어나곤 합니다.
복잡한 사용자 정의 통계를 매번 넘파이 루프로 돌리면 병목이 생기고, 전처리 파이프라인 전체가 지연되죠.
이럴 때 강력한 선택지가 바로 .rolling(…).apply(func, engine=’numba’|’cython’) 입니다.
같은 로직이라도 엔진 한 줄로 실행 경로가 바뀌어 체감 성능이 달라지고, 윈도우 크기와 스텝, 리샘플 주기 조합에서도 안정적으로 동작할 수 있습니다.
실제 코드에 적용할 때 흔히 헷갈리는 raw, engine_kwargs, method 같은 옵션들도 함께 정리해 드립니다.

이 글은 파이썬 pandas의 윈도우·리샘플 맥락에서 rolling().apply에 전달하는 engine 값으로 ‘numba’ 또는 ‘cython’을 선택하는 방법을 중심으로 구성했습니다.
핵심 정보는 다음과 같습니다.
pandas의 .rolling(…).apply(func, engine=’numba’|’cython’)을 사용하면 사용자 정의 함수 기반의 롤링 집계를 고속으로 수행할 수 있습니다.
또한 리샘플(resample)과 조합할 때의 주의점, 매개변수별 동작 차이, 성능 최적화 팁을 실제 코드 형태로 이해하기 쉽게 안내합니다.



🔗 pandas rolling apply 엔진 개요

시계열이나 순차 데이터에서 고정 길이 또는 시간 기반 창을 이동하며 통계를 계산할 때 .rolling(…).apply(func, engine=’numba’|’cython’)은 사용자 정의 함수(UDF)를 고속으로 실행하기 위한 핵심 인터페이스입니다.
기본 개념은 윈도우로 잘린 1차원 배열을 매 스텝마다 함수에 전달하고, 함수가 반환한 스칼라를 결과 시리즈의 해당 위치에 기록하는 방식입니다.
여기에 engine 옵션을 활용하면 파이썬 루프 병목을 줄이고 네이티브 수준의 속도를 끌어낼 수 있습니다.
특히 대용량 데이터에서 집계나 커스텀 지표(예: 이동 상관, 왜도, 지수화 지표)를 계산할 때 체감 차이가 큽니다.

두 엔진의 역할을 간단히 정리하면 다음과 같습니다.
‘numba’는 JIT 컴파일을 통해 파이썬 함수를 기계어로 변환해 반복 계산을 가속합니다.
‘cython’은 판다스의 Cython 구현 경로를 활용하는 실행 방식으로, 파이썬 레벨 호출 대비 오버헤드를 줄입니다.
두 경우 모두 raw=True로 넘파이 배열을 직접 받는 형태의 UDF에서 최적 성능을 기대할 수 있고, min_periods, center, closed 같은 롤링 옵션과 함께 동일하게 동작합니다.

  • ⚙️핵심 문법: Series/DataFrame.rolling(window, min_periods, center, win_type, step, closed).apply(func, raw=True, engine=’numba’|’cython’, engine_kwargs={…})
  • 🚀numba 경로: 1차원 numpy 배열을 입력받는 순수 함수가 유리합니다. 분기와 루프가 많을수록 효과가 큽니다.
  • 🧱cython 경로: 별도 JIT 설정 없이 사용 가능하며, 단순 집계나 경량 UDF에서 호출 오버헤드를 줄이는 데 적합합니다.
  • 🧪raw=True 권장: 판다스 객체 대신 ndarray를 직접 전달해 불필요한 래핑 비용을 피합니다.
  • 🧯NaN 처리: 윈도우 내 결측은 그대로 배열에 포함됩니다. 함수 내부에서 무시·대체 전략을 직접 구현하세요.
CODE BLOCK
import numpy as np
import pandas as pd
from numba import njit

# 사용자 정의 통계: 중앙값-평균 절대편차(MAD) 기반 지표
@njit(cache=True, fastmath=True)
def robust_score(a):
    # a는 1D ndarray, NaN 포함 가능
    x = a[~np.isnan(a)]
    if x.size == 0:
        return np.nan
    med = np.median(x)
    mad = np.median(np.abs(x - med))
    return (med / (mad if mad != 0 else 1.0))

s = pd.Series(np.random.randn(1_0000)).mask(np.random.rand(1_0000) < 0.05, np.nan)

# 핵심: rolling.apply + engine
out_numba = s.rolling(window=50, min_periods=10).apply(
    robust_score, raw=True, engine="numba",
    engine_kwargs={"parallel": False}
)

# 대안: cython 경로 (추가 설치 불필요)
out_cython = s.rolling(window=50, min_periods=10).apply(
    robust_score, raw=True, engine="cython"
)

💡 TIP: engine=’numba’를 사용할 때는 numba 설치가 필요합니다.
함수는 파이썬 객체 연산을 피하고, 넘파이 연산 위주로 작성하면 최적화가 수월합니다.
반복 내 분기가 많거나 윈도우가 큰 경우 JIT의 이점이 커집니다.

⚠️ 주의: UDF가 스칼라를 반환하지 않거나, 파이썬 객체(리스트, dict 등)를 생성·반환하면 성능이 급격히 떨어지거나 에러가 발생할 수 있습니다.
또한 자료형이 혼합된 시리즈는 자동 형변환으로 오버헤드가 커집니다.
가능하면 float64 또는 int64로 정규화하세요.

💬 핵심 요약:
.rolling(…).apply(func, engine=’numba’|’cython’)은 파이썬 레벨 반복을 네이티브 경로로 우회해 대규모 윈도우 계산을 실용 속도로 끌어올립니다.
성능의 80%는 raw=True와 잘 설계된 순수 함수에서 결정됩니다.

🛠️ numba와 cython 선택 기준과 성능

pandas의 rolling().apply에서 제공하는 두 가지 엔진, ‘numba’‘cython’은 모두 성능 최적화를 목표로 하지만 적용 방식과 장단점이 다릅니다.
단순 집계 함수에서는 cython이 빠를 수 있지만, 복잡한 사용자 정의 함수에서는 numba가 압도적으로 유리한 경우가 많습니다.
따라서 데이터 크기와 연산 특성에 따라 알맞은 엔진을 선택하는 것이 중요합니다.

⚡ numba 엔진의 특징

numba는 파이썬 함수를 LLVM 기반 JIT으로 컴파일해, 반복문이나 조건문이 많은 연산에서 강력한 성능을 발휘합니다.
첫 실행 시 컴파일 비용이 발생하지만 이후 반복 호출에서는 캐시된 기계어 코드가 사용되므로 큰 윈도우를 처리할 때 효율적입니다.
또한 병렬 실행을 지원해 대규모 연산을 멀티코어에서 가속할 수 있습니다.

🔧 cython 엔진의 특징

cython 엔진은 판다스 내부에 이미 구현된 최적화 루프를 활용합니다.
따라서 별도의 JIT 설치가 필요 없고, 일반적인 집계 함수에서 안정적인 성능을 제공합니다.
하지만 복잡한 로직을 담은 사용자 정의 함수에는 적용 범위가 제한적입니다.
즉, 반복문과 조건 분기 처리에는 numba 대비 이점이 적습니다.

엔진 특징 적합한 경우
numba JIT 컴파일, 복잡한 UDF 최적화 대규모 데이터, 반복문·조건문 많은 로직
cython 내장 최적화, 설치 불필요 단순 집계, 경량 사용자 함수
CODE BLOCK
import pandas as pd
import numpy as np

s = pd.Series(np.random.randn(100000))

# numba 엔진 사용
s.rolling(100).apply(lambda x: np.sum(x**2), raw=True, engine="numba")

# cython 엔진 사용
s.rolling(100).apply(lambda x: np.sum(x**2), raw=True, engine="cython")

💎 핵심 포인트:
데이터 크기가 작거나 단순 계산일 때는 cython, 복잡한 로직과 대규모 데이터일 때는 numba가 유리합니다.



⚙️ raw, engine_kwargs, method 매개변수 정리

pandas의 rolling().apply를 활용할 때는 엔진 선택과 함께 반드시 고려해야 하는 매개변수가 있습니다.
대표적으로 raw, engine_kwargs, method 옵션이 있는데, 이들은 계산 효율성과 결과값에 직접적인 영향을 줍니다.
옵션을 올바르게 이해하고 적용하지 않으면 속도 저하, 잘못된 값 반환 같은 문제가 생길 수 있습니다.

📌 raw 매개변수

raw는 사용자 정의 함수로 전달되는 입력 형식을 제어합니다.
기본값은 False로, pandas Series가 전달됩니다.
하지만 True로 설정하면 넘파이 ndarray가 전달되며, engine=’numba’와 조합할 때 필수적입니다.
이 경우 pandas 메타데이터 처리가 줄어들어 속도가 크게 개선됩니다.

📌 engine_kwargs 매개변수

engine_kwargs는 선택한 엔진에 전달되는 세부 옵션을 담는 딕셔너리입니다.
예를 들어 numba 엔진의 경우 {“parallel”: True, “nopython”: True}와 같이 병렬 실행 여부나 최적화 전략을 지정할 수 있습니다.
적절한 옵션 조정으로 추가적인 성능 향상을 기대할 수 있습니다.

📌 method 매개변수

method는 rolling 적용 시 병렬화 전략 등을 선택할 수 있는 인자입니다.
현재는 대부분의 경우 기본값이 적합하지만, 일부 특수한 연산에서는 최적화된 경로를 지정할 수 있습니다.
특히 대용량 데이터를 처리할 때 pandas 버전에 따라 추가적인 옵션이 제공되기도 하므로, 공식 문서 확인이 필요합니다.

CODE BLOCK
import pandas as pd
import numpy as np

s = pd.Series(np.random.randn(10000))

# raw=True 로 ndarray 전달
result1 = s.rolling(50).apply(lambda x: np.mean(x), raw=True, engine="cython")

# engine_kwargs 로 numba 설정
result2 = s.rolling(50).apply(
    lambda x: np.mean(x), 
    raw=True, 
    engine="numba", 
    engine_kwargs={"parallel": True, "nopython": True}
)

💎 핵심 포인트:
raw는 입력 형태, engine_kwargs는 세부 실행 옵션, method는 실행 전략을 결정하는 중요한 인자입니다. 잘못 설정하면 속도와 결과 모두 영향을 받을 수 있습니다.

🔌 윈도우와 리샘플 조합 패턴과 주의점

시계열 데이터를 다룰 때 rollingresample을 함께 사용하는 경우가 많습니다.
예를 들어 초 단위 데이터를 분 단위로 리샘플한 뒤 이동평균을 계산하거나, 일 단위 데이터를 주 단위로 집계 후 사용자 정의 함수를 적용하는 방식이 일반적입니다.
이 과정에서 rolling.apply(func, engine=…)를 병행하면 성능은 올라가지만, 처리 순서와 윈도우 기준에 따라 예상과 다른 결과가 나올 수 있어 주의가 필요합니다.

📌 리샘플 후 롤링

먼저 resample로 시간 구간을 재정의한 뒤, 결과에 rolling을 적용하는 방식입니다.
이 경우 구간별 집계가 먼저 이뤄지고, 그 결과 시리즈에서 이동 계산이 진행됩니다.
즉, 샘플링 단위를 기준으로 이동 통계가 계산되므로 구간 평균이나 합계를 기반으로 2차 가공할 때 유리합니다.

📌 롤링 후 리샘플

반대로 rolling을 먼저 적용한 뒤 결과를 resample하는 방식도 있습니다.
이 경우 원본 시계열에서 세밀한 롤링 계산이 수행된 뒤, 리샘플 구간별로 다운샘플링 또는 업샘플링이 이뤄집니다.
예를 들어 초 단위 센서 데이터를 5초 이동평균으로 매끈하게 만든 뒤, 분 단위로 리샘플해 요약하는 경우가 이에 해당합니다.

CODE BLOCK
import pandas as pd
import numpy as np

# 초 단위 데이터 생성
rng = pd.date_range("2025-01-01", periods=300, freq="S")
s = pd.Series(np.random.randn(len(rng)), index=rng)

# 1. 리샘플 후 롤링
res_roll = (
    s.resample("1min").mean()
     .rolling(3, min_periods=1)
     .apply(np.mean, raw=True, engine="cython")
)

# 2. 롤링 후 리샘플
roll_res = (
    s.rolling("30s", min_periods=10)
     .apply(np.mean, raw=True, engine="numba")
     .resample("1min")
     .mean()
)

⚠️ 주의: 리샘플과 롤링의 적용 순서에 따라 결과 값이 크게 달라질 수 있습니다.
원하는 통계 기준에 따라 순서를 명확히 정의해야 하며, 특히 금융 데이터처럼 시점 민감한 분석에서는 잘못된 해석으로 이어질 수 있습니다.

💎 핵심 포인트:
리샘플과 롤링은 순서에 따라 결과가 달라집니다. “리샘플 → 롤링”은 구간 요약값을 기반으로 이동 통계, “롤링 → 리샘플”은 원본 데이터를 매끄럽게 가공 후 구간 요약을 의미합니다.



💡 실전 예제 rolling.apply와 resample 코드

이제까지 살펴본 rolling.applyresample의 개념을 바탕으로, 실제 데이터 분석 흐름에서 어떻게 활용할 수 있는지 예제를 통해 정리해 보겠습니다.
아래 예시는 금융 시계열 데이터(예: 주가, 환율 등)에 적용할 수 있는 이동 지표 계산 방법을 보여주며, engine=’numba’engine=’cython’을 각각 사용한 경우를 비교합니다.

📈 금융 데이터 이동 지표 계산

아래 코드는 가상의 주가 데이터를 생성한 뒤, 일 단위 리샘플20일 이동평균 및 이동표준편차를 계산하는 예제입니다.
엔진을 다르게 선택했을 때 동작 방식은 동일하지만, 처리 속도와 최적화 가능성에서 차이가 발생합니다.

CODE BLOCK
import pandas as pd
import numpy as np

# 가상 주가 데이터 (분 단위)
rng = pd.date_range("2025-01-01", periods=20000, freq="T")
price = pd.Series(np.random.lognormal(mean=0, sigma=0.01, size=len(rng)), index=rng).cumsum()

# 1일 단위 리샘플 (종가 기준)
daily_close = price.resample("1D").last()

# 20일 이동평균, 이동표준편차 - numba
ma_numba = daily_close.rolling(20).apply(np.mean, raw=True, engine="numba")
std_numba = daily_close.rolling(20).apply(np.std, raw=True, engine="numba")

# 20일 이동평균, 이동표준편차 - cython
ma_cython = daily_close.rolling(20).apply(np.mean, raw=True, engine="cython")
std_cython = daily_close.rolling(20).apply(np.std, raw=True, engine="cython")

🧪 사용자 정의 함수 적용

단순 평균과 표준편차 외에도, 사용자가 정의한 복잡한 함수 역시 엔진 옵션을 통해 빠르게 계산할 수 있습니다.
예를 들어 샤프 지수(Sharpe ratio)를 이동 윈도우에서 계산하는 함수를 적용할 수 있습니다.

CODE BLOCK
def sharpe_ratio(arr):
    arr = arr[~np.isnan(arr)]
    if len(arr) < 2:
        return np.nan
    return arr.mean() / arr.std()

sharpe_roll = daily_close.rolling(30).apply(sharpe_ratio, raw=True, engine="numba")

💎 핵심 포인트:
실전에서는 resample과 rolling을 적절히 결합해 원하는 시간 해상도에서 사용자 정의 지표를 빠르게 계산할 수 있습니다. numba는 복잡한 계산에서, cython은 단순 계산에서 특히 유리합니다.

자주 묻는 질문 (FAQ)

numba를 사용하려면 반드시 설치가 필요한가요?
네, numba는 판다스에 기본 포함되지 않기 때문에 pip install numba 명령으로 별도 설치해야 합니다. 설치 후에는 rolling.apply에서 engine=”numba”를 문제없이 사용할 수 있습니다.
cython 엔진은 별도 설치 없이 쓸 수 있나요?
네, cython 엔진은 판다스 내부 구현을 기반으로 동작하기 때문에 별도의 설치 과정이 필요하지 않습니다. 즉시 사용 가능합니다.
raw 옵션을 반드시 True로 설정해야 하나요?
engine=’numba’를 사용할 때는 ndarray를 직접 받도록 raw=True로 설정하는 것이 필수입니다. cython이나 기본 엔진에서는 False도 허용되지만, 성능상 True가 권장됩니다.
engine_kwargs는 어떤 상황에서 쓰나요?
numba 엔진의 경우 parallel=True, nopython=True 같은 고급 옵션을 설정해 추가적인 최적화를 할 수 있습니다. 대규모 데이터셋에서 성능 향상을 위해 유용합니다.
rolling과 resample을 같이 쓰면 순서에 따라 결과가 달라지나요?
네, 리샘플 후 롤링과 롤링 후 리샘플은 결과가 크게 달라집니다. 원하는 기준이 집계 후 이동 통계인지, 원본 데이터 이동 계산 후 요약인지에 따라 올바른 순서를 선택해야 합니다.
모든 사용자 정의 함수에서 numba를 사용할 수 있나요?
아닙니다. numba는 파이썬 객체 연산을 지원하지 않습니다. numpy 기반 연산만 사용하는 순수 함수여야 최적화가 가능합니다.
rolling.apply로 반환하는 값은 반드시 스칼라여야 하나요?
네, apply 함수는 각 윈도우마다 스칼라 값을 반환해야 합니다. 배열이나 리스트를 반환하면 에러가 발생합니다.
대규모 데이터에서는 어떤 엔진이 더 적합할까요?
수백만 행 이상의 데이터에서 복잡한 사용자 정의 함수를 쓴다면 numba가 적합합니다. 단순 집계라면 cython도 충분히 빠르며, 설치 과정이 필요 없어 간편합니다.

🧩 rolling.apply 엔진 선택과 리샘플 조합 핵심 정리

pandas에서 윈도우 계산을 가속하려면 .rolling(…).apply(func, engine=’numba’|’cython’)을 활용하는 것이 핵심입니다.
numba는 JIT 기반으로 복잡한 UDF에 강하고, cython은 설치 없이 단순 집계에 효율적입니다.
raw=True로 넘파이 배열을 직접 받아 불필요한 오버헤드를 줄이는 것이 체감 성능을 좌우합니다.
engine_kwargs로 numba의 parallel 같은 세부 최적화를 제어할 수 있습니다.
리샘플과 롤링의 순서는 결과 해석을 바꾸므로, “리샘플 → 롤링”과 “롤링 → 리샘플”의 의미 차이를 명확히 구분해야 합니다.
또한 rolling.apply의 주요 인자는 func, raw, engine, engine_kwargs이며, 각 윈도우에서 스칼라를 반환하도록 설계해야 합니다.

💎 핵심 포인트 요약:
대용량·복잡 UDF → engine=’numba’ + raw=True 권장.
단순 집계·간편 설정 → engine=’cython’ 유리.
리샘플 순서에 따라 지표 의미가 달라지므로 분석 목적에 맞춰 파이프라인을 고정하세요.


🏷️ 관련 태그 : pandas, rolling apply, numba, cython, 윈도우함수, 리샘플, 시계열분석, 성능최적화, 파이썬데이터분석, 사용자정의함수