본문으로 건너뛰기
김신건의 로그

[Pandas] shift / diff / pct_change

· 수정 · 📖 약 1분 · 536자/단어 #python #pandas #shift #diff #time-series
Pandas shift / diff, Pandas shift, Pandas diff, Pandas pct_change, 시차 pandas

정의

  • shift(n) : 값을 n 칸 밀기 (lag/lead 생성)
  • diff(n) : n 칸 차이 (= s - s.shift(n))
  • pct_change(n) : 변화율 (= s.diff(n) / s.shift(n))

시계열 분석의 기본 도구. 전일 대비 / 전월 대비 / lag feature 생성 등에 광범위하게 쓰인다.

사용 상황

상황방법예시
전일 대비 변화량 계산diff(1)매일 판매량 변화
전월 대비 변화율 계산pct_change(1)월 매출 증감률
주간 동기 비교shift(7) / diff(7)7일 전 대비
ML lag feature 생성shift(1), shift(7)과거 값을 feature 로
미래 타깃 생성shift(-1)내일 close 를 타깃으로
그룹별 이전 값 비교groupby().shift(1)user 별 이전 주문

shift 시각화

flowchart LR
    subgraph "원본 Series"
        A0["0: 10"]
        A1["1: 12"]
        A2["2: 15"]
        A3["3: 14"]
    end
    subgraph "shift(1) - lag"
        B0["0: NaN"]
        B1["1: 10"]
        B2["2: 12"]
        B3["3: 15"]
    end
    subgraph "shift(-1) - lead"
        C0["0: 12"]
        C1["1: 15"]
        C2["2: 14"]
        C3["3: NaN"]
    end
    A0 -.-> B1
    A1 -.-> B2
    A2 -.-> B3
    A1 -.-> C0
    A2 -.-> C1
    A3 -.-> C2

shift(1) 은 값을 한 칸 아래로 (과거 값 참조). shift(-1) 은 한 칸 위로 (미래 값 참조).

shift

s.shift(1)                  # 한 칸 아래로 (lag, NaN 채움)
s.shift(-1)                 # 한 칸 위로 (lead)
s.shift(7)                  # 일주일 전 값
s.shift(1, fill_value=0)    # NaN 대신 0
python
import pandas as pd
s = pd.Series([10, 12, 15, 14, 18])
print('original:', s.tolist())
print('shift(1):', s.shift(1).tolist())
print('shift(-1):', s.shift(-1).tolist())
결과
original: [10, 12, 15, 14, 18]
shift(1): [nan, 10.0, 12.0, 15.0, 14.0]
shift(-1): [12.0, 15.0, 14.0, 18.0, nan]
indexsshift(1)shift(-1)
010NaN12
1121015
2151214
3141518
41814NaN

diff

s.diff()        # = s - s.shift(1), 1차 차분
s.diff(2)       # 2 step 차분
s.diff(-1)      # 다음 값 - 현재 값 (역방향)
python
import pandas as pd
s = pd.Series([10, 12, 15, 14, 18])
print(s.diff().tolist())
print(s.diff(2).tolist())
결과
[nan, 2.0, 3.0, -1.0, 4.0]
[nan, nan, 5.0, 2.0, 3.0]

2차 차분

s.diff().diff()     # 2차 차분: 변화량의 변화량
# 시계열 stationarity 확보에 사용

pct_change (변화율)

s.pct_change()        # 직전 대비 비율 변화
s.pct_change(7)       # 7 step 변화율 (주간 단위 등)
s.pct_change(fill_method=None)    # NaN 을 채우지 않음 (pandas 2.2+)
python
import pandas as pd
s = pd.Series([100, 120, 144, 180])
print(s.pct_change().round(3).tolist())
결과
[nan, 0.2, 0.2, 0.25]

20% → 20% → 25% 증가.

fill_method 주의 (pandas 2.2+)

# pandas 2.2 부터 fill_method 기본값 변경
# NaN 을 앞 값으로 채워 계산하는 동작이 deprecated
s.pct_change()                      # FutureWarning (pandas 2.2+)
s.pct_change(fill_method=None)      # 명시적, 경고 없음

time-based shift

DatetimeIndex 일 때 freq 지정 가능.

import pandas as pd

ts = pd.Series(
    [100, 110, 120],
    index=pd.to_datetime(['2024-01-01', '2024-01-03', '2024-01-07'])
)

ts.shift(1, freq='D')      # 1 일 이동 (날짜 이동, 값은 그대로)
ts.shift(1, freq='W')      # 1 주 이동
ts.shift(-1, freq='ME')    # 한 달 뒤로

freq 지정 시 값이 이동하는 게 아니라 인덱스(날짜) 가 이동한다.

자주 쓰는 패턴

전월 대비 매출

df['mom'] = df['monthly_sales'].pct_change()
df['yoy'] = df['monthly_sales'].pct_change(12)   # 전년 동월 대비

lag features (ML)

for lag in [1, 7, 14, 30]:
    df[f'sales_lag{lag}'] = df['sales'].shift(lag)

# diff feature
df['sales_diff1'] = df['sales'].diff(1)
df['sales_diff7'] = df['sales'].diff(7)

룩어헤드 방지

# 오늘 데이터로 내일을 예측하는 모델
df['target'] = df['close'].shift(-1)   # 내일 종가 (타깃)
# 학습 feature 는 shift 안 함 (현재 또는 과거 데이터)
df = df.dropna()    # 마지막 행 NaN 제거

누적 증가율

df['daily_return'] = df['close'].pct_change()
df['cumulative_return'] = (1 + df['daily_return']).cumprod()

그룹별 shift

# 같은 user 안에서 이전 주문 참조
df = df.sort_values(['user_id', 'date'])
df['prev_amount'] = df.groupby('user_id')['amount'].shift(1)
df['amount_diff']  = df.groupby('user_id')['amount'].diff(1)

이동 평균과 결합

# 단기 이동 평균 vs 장기 이동 평균 교차 (golden cross / dead cross)
df['ma5']  = df['close'].rolling(5).mean()
df['ma20'] = df['close'].rolling(20).mean()
df['cross_signal'] = (df['ma5'] > df['ma20']) & (df['ma5'].shift(1) <= df['ma20'].shift(1))

변화 감지

# 값이 바뀐 위치 탐지
df['changed'] = df['status'] != df['status'].shift(1)
df['run_id'] = df['changed'].cumsum()   # 연속 구간 ID

성능

연산속도비고
shift(n)빠름단순 메모리 오프셋
diff(n)빠름내부적으로 shift + 빼기
pct_change(n)빠름diff / shift
groupby().shift(n)중간그룹 기준 분할 후 shift
import pandas as pd
import numpy as np

# 대용량에서도 빠름
df = pd.DataFrame({'value': np.random.randn(1_000_000)})
df['lag1'] = df['value'].shift(1)      # 거의 즉시
df['diff1'] = df['value'].diff(1)      # 거의 즉시

함정

WARNING

shift / diff / pct_change 를 쓸 때 다음 상황을 주의한다.

1. NaN 의 첫/마지막

shift(1) 은 첫 행이 NaN, shift(-1) 은 마지막 행이 NaN.

df['lag1'] = df['value'].shift(1)
df = df.dropna()                    # 첫 행 제거
# 또는
df['lag1'] = df['value'].shift(1, fill_value=0)   # 0 으로 채움

2. 시간 freq 가 일정하지 않을 때

# index 가 불규칙 시계열 (2024-01-01, 01-03, 01-10 등)
df.shift(1)           # 위치 기반: 바로 앞 행
df.shift(1, freq='D') # 시간 기반: 정확히 1일 전 (없으면 NaN)

불규칙 시계열에서는 위치 기반 shift 와 시간 기반 shift 의 결과가 완전히 다르다.

3. groupby + shift 의 정렬

# 정렬 없이 shift 하면 의미가 달라짐
df = df.sort_values(['user_id', 'date'])   # 반드시 정렬 먼저
df['prev'] = df.groupby('user_id')['amount'].shift(1)

4. pct_change 의 inf / 분모 0

s = pd.Series([0, 10, 5])
s.pct_change()   # [NaN, inf, -0.5]
# 분모(이전 값)가 0 이면 inf 발생

5. 타입 변화

# int Series 에 shift(1) 적용 시 float 으로 변환 (NaN 때문)
s = pd.Series([1, 2, 3], dtype='int64')
s.shift(1).dtype    # float64

pandas 2.x Nullable Int 타입 사용 시 Int64 로 선언하면 NaN 을 보존하면서 정수 유지.

s = pd.Series([1, 2, 3], dtype='Int64')   # Nullable Int
s.shift(1)    # [<NA>, 1, 2] dtype: Int64

관련 위키

이 글의 용어 (5개)
[Pandas] 성능 / 메모리 최적화pandas
정의 pandas 의 흔한 성능 함정과 최적화 패턴. 벡터화 + dtype 선택 + 알고리즘 의 조합이 핵심. 사용 상황 | 상황 | 권장 접근 | |:---|:---| | 단순…
[Pandas] cumsum / cummax / cummin / cumprodpandas
정의 누적 (cumulative) 연산. 각 위치까지의 누적 결과를 반환, 결과 길이는 원본과 동일. | 함수 | 의미 | |:---|:---| | | 누적 합 | | | 누적 …
[Pandas] groupbypandas
정의 는 데이터를 그룹으로 나누고 (split), 각 그룹에 함수를 적용 (apply), 결과를 합쳐 (combine) 새 DataFrame 으로 만드는 split-apply-c…
[Pandas] resamplepandas
정의 는 시계열 데이터를 다른 빈도 (월별, 일별, 시간별 등) 로 재샘플링하면서 집계한다. 의 시계열 버전. DatetimeIndex (또는 으로 datetime 컬럼) 이 필…
[Pandas] rolling / expandingpandas
정의 - : 고정 크기 슬라이딩 윈도우 (이동 평균 등) - : 시작부터 누적 윈도우 (cumulative) - : 지수 가중 이동 평균 사용 상황 | 상황 | 적합한 방법 | …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기