오늘부터 시계열 데이터 파트가 시작되었다. 오늘은 시계열 예측에 앞서 시계열 데이터에 대해서 배우고 데이터 자체를 다뤄보는 시간을 가졌다.
| 구분 | 상세 내용 |
|---|---|
| 추세 | • 단기적 변동과 구분되는 개념으로, 데이터 전체를 관통하는 방향성을 나타냄 • 추세를 고려하지 않으면 단기 변동을 과대해석하거나 전체 흐름을 잘못 이해할 위험이 있음 |
| 계절성 | • 일정한 주기를 두고 유사한 패턴이 반복되는 현상을 의미 • 평일/주말 소비 차이, 계절별 에너지 사용량 차이 등이 대표적 • 이를 고려하지 않으면 반복 패턴을 이상치로 오해할 수 있음 |
| 불규칙성 | • 정책 변화, 사회적 사건, 외부 요인 등 예측하기 어려운 우연적 변동을 의미 • 이러한 불규칙성은 시계열 예측 문제를 더욱 복잡하게 만드는 주요 요인 |
| 비정상성 | • 데이터의 통계적 성질(평균, 분산 등)이 시간에 따라 변하는 경우를 의미 • 통계 기반 시계열 모형을 적용하기 전에 반드시 점검하고 해결해야 할 핵심 개념 |
| 구분 | 상세 내용 |
|---|---|
| 일간 | • 전력 사용량, 대중교통 이용량, 도로 교통량, 카페 매출, 주식 거래량 등 |
| 주간 | • 음식 배달 주문, 영화관 관객 수, 소매 매출, 병원 외래 환자 수, 택배 발송량 등 |
| 월간 | • 각종 공과금(세금, 보험료, 통신요금 등) 납부 금액, 기업 예산 집행 금액 등 |
| 연간 | • 여름/겨울 냉난방 사용료, 기상, 패션 의류 판매량, 여행(호텔, 항공권) 수요 등 |
| 구분 | 상세 내용 |
|---|---|
| 문제 정의 및 목표 설정 | • 특정 시점의 값을 예측할 것인지, 향후 일정 기간의 평균이나 누적 값을 예측할 것인지, 또는 특정 이벤트 발생 여부를 판단하는 분류 문제나 이상 탐지 문제로 확장 가능 • 예측 시점과 활용 목적을 사전에 명확히 설정하지 않으면 모형을 적합하는 과정 전체가 흔들릴 수 있으므로 가장 중요한 단계라 할 수 있음 |
| 시계열 데이터의 구조 이해 | • 시간 변수의 형식을 확인하고, 적절한 날짜시간 형태로 변환하여 데이터의 시간 단위와 간격이 일정한지 여부를 파악 • 예측 대상 변수와 함께 활용 가능한 외생변수의 존재 여부를 확인하고 데이터에 포함된 추세, 계절성, 불확실성 등을 탐색 |
| 시간 순서와 연속성 확인 | • 데이터가 시간 기준으로 올바르게 정렬되어 있는지 확인하고, 누락된 시점이 존재하는지 점검 • 필요에 따라 리샘플링을 수행하거나 결측값을 보완해야 하며, 이 과정에서 미래 정보를 사용하지 않도록 주의 |
| 데이터 누수 방지 | • 시계열 데이터는 반드시 특정 시간 기준으로 학습 데이터와 검증 데이터를 분리해야 하며, 과거 데이터를 기반으로 특성을 생성 • 시차 변수나 이동 평균을 생성할 때 미래 시점의 정보가 포함되지 않도록 주의해야 하며, 표준화/정규화 등은 학습 데이터 기준으로만 수행 |
| 시계열 데이터 전처리 | • 결측값은 이전 시점 값이나 보관법 등으로 처리하고, 이상치는 통계적 기준을 활용하여 제거하거나 보정 • 필요에 따라 로그 변환이나 차분을 통해 데이터의 안정성을 확보하고, 시차 변수, 이동 통계량, 날짜 기반 파생 변수 등을 생성하여 모형이 패턴을 학습할 수 있도록 함 |
| 예측 모형 적용 | • ARIMA, SARIMA, Prophet과 같은 통계 기반 모델부터, XGBoost, LightGBM과 같은 머신러닝 모델을 활용하며, 필요에 따라 RNN이나 LSTM과 같은 딥러닝 모델도 적용할 수 있음 • 모델의 성능을 평가할 때 일반적인 랜덤 분할이 아닌 시간 순서를 유지하는 방식의 검증을 사용해야 하며, 예측 목적에 맞는 평가 지표를 선택하는 것이 중요 |
| 구분 | 세부 항목 | 상세 내용 |
|---|---|---|
| 변수 축 | 단변량 | • 시간에 따라 하나의 변수만 관측한 데이터 • 예 : 주가 또는 매출액 등 |
| 다변량 | • 시간에 따라 여러 변수가 동시에 관측한 데이터 • 예 : 매출액, 광고비, 날씨 등 | |
| 개체 축 | 단일 | • 하나의 개체에 대해 시간에 따른 값을 관측한 데이터 • 예 : 특정 매장의 매출액 등 |
| 다중 | • 여러 개체에 대해 각각의 시계열을 동시에 관측한 데이터 • 예 : 여러 매장별 매출액, 상품별 판매량 등 |
pd.date_rangename : 인덱스 이름 지정freq : 간격 지정(기본값 ‘D’)dates = pd.date_range(start='2024-01-01', end='2025-12-31', name='date')
# DatetimeIndex(['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04',
# '2024-01-05', '2024-01-06', '2024-01-07', '2024-01-08',
# '2024-01-09', '2024-01-10',
# ...
# '2025-12-22', '2025-12-23', '2025-12-24', '2025-12-25',
# '2025-12-26', '2025-12-27', '2025-12-28', '2025-12-29',
# '2025-12-30', '2025-12-31'],
# dtype='datetime64[ns]', name='date', length=731, freq='D')
| 구분 | 상세 내용 | 구분 | 상세 내용 |
|---|---|---|---|
| B | 영업일(월요일~금요일) 단위 | W-MON | 매주 월요일(요일 앵커 사용) |
| D | 일 단위 | YE-JAN | 매년 1월 말일(월 앵커 사용) |
| W | 주 단위(일요일 기준 주간 집계) | h | 매시 단위 |
| ME, MS | 매월 말일, 매월 시작일 | bh | 영업일 기준 매시 단위(09~16시) |
| BME, BMS | 영업일 기준 매월 말일, 매월 시작일 | min | 매분 단위 |
| QE, QS | 분기 말일, 분기 시작일 | s | 매초 단위 |
| BQE, BQS | 영업일 기준 분기 말일, 분기 시작일 | ms | 천 분의 1초 단위 |
| YE, YS | 연도 말일, 연도 시작일 | us | 백만 분의 1초 단위 |
| BYE, BYS | 영업일 기준 연도 말일, 연도 시작일 | ns | 십억 분의 1초 단위 |
n = len(dates)
trend = np.linspace(100, 140, n) # 완만한 증가 추세 설정
yearly = -20 * np.cos(2 * np.pi * np.arange(n) / 365) # 연간 계절성 설정
weekly = np.where(dates.dayofweek < 5, 10, -20) # 주간 계절성 설정
noise = np.random.normal(loc=0, scale=3, size=n) # 불확실성 설정
dates = dates.astype(str) # 문자열로 변환
df = pd.DataFrame(data={'date': dates, 'value': trend + yearly + weekly + noise})
drop_dates = ['2024-01-05', '2024-02-10', '2024-03-20'] # 삭제할 날짜 리스트 생성
df = df.loc[~df['date'].isin(drop_dates), :].copy() # 선택한 행 삭제
dup_dates = ['2024-01-15', '2024-03-31', '2024-04-25'] # 중복 추가 날짜 시르트 생성
dup_rows = df.loc[df['date'].isin(dup_dates), :].copy() # 선택한 행 삭저
df = pd.concat(objs=[df, dup_rows], ignore_index=True) # 행 방향으로 결합 후 재할당
df = df.sample(frac=1, random_state=1).reset_index(drop=True) # 순서를 임의로 섞어 정렬되지 않은 상태로 만들기
df.loc[df.sample(n=5, random_state=1).index, 'value'] = np.nan # 일부 값을 결측으로 처리
df['date'] = pd.to_datetime(arg=df['date'], errors='coerce') # 날짜시간형으로 변환
df = df.set_index(keys='date').sort_index() # 인덱스 설정 후 오름차순 정렬하여 재할당
df['year'] = df.index.year # 연도
df['quarter'] = df.index.quarter # 분기
df['month'] = df.index.month # 월
df['day'] = df.index.day # 일
df['dayofweek'] = df.index.day_name(locale='ko_KR') # 요일
df['is_weekend'] = (df.index.dayofweek >= 5) # 주말여부
df = df.filter(items=['value']) # 초기화
df.loc['2024-01-01', :] # 인덱싱
df.loc['2024-01-01':'2024-01-10', :] # 슬라이싱
df.loc[['2024-01-01', '2025-01-01'], :] # 팬시 인덱싱
df.loc[df.index.month == 1, :] # 불리언 인덱싱
# 중복 여부 마스크 생성
dup_mask = df.index.duplicated(keep=False)
dup_rows = df.loc[dup_mask]
# value
# date
# 2024-01-15 94.75
# 2024-01-15 94.75
# 2024-03-31 88.10
# 2024-03-31 88.10
# 2024-04-25 120.32
# 2024-04-25 120.32
df = df.groupby(df.index)[['value']].mean()
first() : 그룹별 첫 번째 행 선택last() : 그룹별 마지막 행 선택nth() : 그룹별 지정한 인덱서 행 선택reindex : method 속성에 결측값 대체법 지정full_idx = pd.date_range(df.index.min(), df.index.max()) # 기대 인덱스 생성 후 할당
miss_idx = full_idx.difference(df.index) # 누락된 인덱스를 생성 후 할당
df = df.reindex(index=full_idx) # 누락된 인덱스를 추가 후 재할당
df.loc[df.isna()]
# 2024-01-05 NaN
# 2024-02-10 NaN
# 2024-03-20 NaN
# 2024-06-15 NaN
# 2024-09-09 NaN
# 2024-12-28 NaN
# 2025-10-10 NaN
# 2025-12-28 NaN
# Name: value, dtype: float64
interpolatelineartimedf['value_ffill'] = df['value'].ffill() # 직전값으로 채움
df['value_linear'] = df['value'].interpolate(method='linear') # 선형보간법으로 채움
df['value_time'] = df['value'].interpolate(method='time') # 시간 간격 비율을 고려한 선형보간법으로 채움
df.loc['2024-01-04':'2024-01-10', :]

# 아래로 값 이동
df['lag_1'] = df['value'].shift(periods=1)
df['lag_2'] = df['value'].shift(periods=2)
df['lag_3'] = df['value'].shift(periods=3)
df['roll_7_avg'] = df['value'].rolling(window=7, min_periods=1).mean() # 7일 이동 평균
df['roll_7_std'] = df['value'].rolling(window=7, min_periods=1).std() # 7일 이동 표준편차
df['expand_avg'] = df['value'].expanding().mean() # 처음부터 누적된 평균 계산
df['diff_1'] = df['value'].diff(periods=1) # 전일 대비 차분 계산
df['pent_1'] = df['value'].pct_change(periods=1) # 전일 대비 증감률 계산
df = df.filter(like='value')
df.resample(rule='W-SUN')['value'].sum() # 주 단위로 합계 계산
df.resample(rule='W-SUN')['value'].sum().rolling(window=4).mean() # 주 단위 합계 4주 이동 평균 계산
df.asfreq(freq='W-FRI') # 특정 요일 선택하여 반환
sns.lineplot(x=df.index, y=df['value'], color='royalblue', linewidth=1)
plt.show()

from statsmodels.tsa.seasonal import seasonal_decompose
result = seasonal_decompose(x=df['value'], model='additive', period=7)
result.plot();

이론에 대해서 배우니까 역시나 분량이 엄청 많은 것 같다. 내일은 오늘 배운 시계열 데이터를 가지고 머신러닝과 다양한 예측 방법을 사용해서 실제로 예측을 해볼 것 같다.