본문 바로가기
Python/시계열 데이터 분석

[시계열 분석] 시계열 데이터 패턴의 추출 실습(1)

by 춘춘주모 2021. 2. 24.
반응형

ch03.데이터분석 준비하기 시계열 데이터패턴 추출 - 05. 시계열 데이터패턴의 추출 실습하기1-1

ch03.데이터분석 준비하기 시계열 데이터패턴 추출 - 05. 시계열 데이터패턴의 추출 실습하기1-2

ch03.데이터분석 준비하기 시계열 데이터패턴 추출 - 05. 시계열 데이터패턴의 추출 실습하기2-1

 

소스 및 데이터 출처 : github.com/cheonbi/OnlineTSA

 

cheonbi/OnlineTSA

Online Course of Time Series Analysis. Contribute to cheonbi/OnlineTSA development by creating an account on GitHub.

github.com

1. Import Library : 모듈 설치 및 불러오기

pip 업그레이드를 기본적으로 해줍니다. 다음 필요한 라이브러리들을 불러와주는데, 설치가 미리 되어있지 않다면 install 후 진행하도록 합니다. google colab 환경에서 실행하면 웬만한 기본 라이브러리는 설치되어 있어 편하다는 장점이 있습니다.

!python -m pip install --user --upgrade pip

# Ignore the warnings
import warnings
# warnings.filterwarnings('always')
warnings.filterwarnings('ignore')

# System related and data input controls
import os

# Data manipulation and visualization
import pandas as pd
pd.options.display.float_format = '{:,.2f}'.format #소수점 2자리 까지는 보여라 
pd.options.display.max_rows = 10 #최대 개수로 보여줄 행 개수 지정 
pd.options.display.max_columns = 20
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# Modeling algorithms
# General
import statsmodels.api as sm #통계 패키지
from scipy import stats #통계 모델링 

# Model selection
from sklearn.model_selection import train_test_split

# Evaluation metrics 평가 지표들
# for regression
from sklearn.metrics import mean_squared_log_error, mean_squared_error,  r2_score, mean_absolute_error

2. Data Loading : 분석에 쓸 데이터 불러오기

해당 데이터는 캐글의 자전거 수요 데이터로 위의 링크에서 받을 수 있습니다. 이 데이터는 말 그대로 자전거 수요에 상관이 있을만한 실수형, categorical 데이터들로 구성되어 있습니다.

 

실행하는 로컬 경로와 같은 곳에서 실행했다면 바로 아래와 같이 데이터를 불러올 수 있습니다. 경로가 다르다면 location을 해당 경로로 수정해주면 됩니다. 

# raw_all.values.flatten()

# location = 'https://raw.githubusercontent.com/cheonbi/DataScience/master/Data/Bike_Sharing_Demand_Full.csv'
location = './Data/BikeSharingDemand/Bike_Sharing_Demand_Full.csv'
raw_all = pd.read_csv(location)
raw_all

데이터는 위와 같은 형식입니다. 마지막 세열은 사용자에 대한 정보로서 casual + registered = count의 선형 결합으로 이뤄져 있습니다.(미가입+가입자 = 총 이용자)

 

3. Feature Engineering : 데이터에서 시계열 패턴 추출하기

데이터의 형태, 차원, 앞 5행, 뒤 5행, 기초 통계량, 데이터 이름과 형식 확인 간단하게 확인하기

주석처리 풀고 원하는 명령 사용 가능합니다.

# raw_all.shape
# raw_all.ndim
# raw_all.head()
# raw_all.tail()
#raw_all.describe(include='all').T
raw_all.info()

3.1 데이터 준비하기 

  • 현재 datetime 열이 object 타입으로 되어있어 datetime 형식으로 변환하기 

# string to datetime
if 'datetime' in raw_all.columns:
    raw_all['datetime'] = pd.to_datetime(raw_all['datetime'])
    raw_all['DateTime'] = pd.to_datetime(raw_all['datetime'])
raw_all.info()

datetime64 자료형으로 변환된 것을 확인 

  • DateTime으로 인덱스 변경하기

# set index as datetime column
if raw_all.index.dtype == 'int64':
    raw_all.set_index('DateTime', inplace=True)
raw_all
# bring back
# if raw_all.index.dtype != 'int64':
#     raw_all.reset_index(drop=False, inplace=True)
# raw_all
  • null 값 확인하기

raw_all.describe(include='all').T
raw_all.isnull()
raw_all.isnull().sum()

 

3.2 데이터 형 변환(인덱스를 시간으로, freq 설정) 

현재 데이터의 인덱스가 설정은 되어 있으나 정확히 어떤 형태인지 정해져 있지 않기 때문에 설정이 필요하다. 

asfreq('H')를 통해 '시간' 기준으로 행 인덱스를 설정해주고, 어떤 시간대(어떤 로우가 비었는지)의 데이터가 없는지 확인

raw_all.asfreq('H')[raw_all.asfreq('H').isnull().sum(axis=1) > 0]

다른 기준으로도 데이터를 전환할 수 있다. 다음과 같이 Day, Week 등으로 전환하고 확인 가능.

raw_all.index
raw_all.asfreq('D')
# raw_all.asfreq('W')
# raw_all.asfreq('H')
# raw_all.asfreq('H').isnull().sum()
# raw_all.asfreq('H')[raw_all.asfreq('H').isnull().sum(axis=1) > 0]
# raw_all.asfreq('H').head(100)

이 실습에서는 시간 기준으로 설정해주고, nan이 있다면 ffill 설정을 통해 앞에 있는 데이터로 채워주기로 한다. (날짜는 뒤로 흐르기 때문에)

# setting frequency of time series data
raw_all = raw_all.asfreq('H', method='ffill')
raw_all.isnull().sum()
raw_all.index

4. 추세, 계절성 추출하기

4.1 목표 변수 y 시각화를 통해 확인하기

아래와 같은 설정으로 원하는 변수를 특정 기간에 대해 line plot을 그려 확인해 본다.

여기서는 전체 count와 가입, 미가입을 시각화에 둘을 더한 것이 파란색 count 변수임을 보여주고 있다. 

raw_all[['count','registered','casual']].plot(kind='line', figsize=(20,6), linewidth=3, fontsize=20,
                                              xlim=('2012-01-01', '2012-06-01'), ylim=(0,1000))
plt.title('Time Series of Target', fontsize=20)
plt.xlabel('Index', fontsize=15)
plt.ylabel('Demand', fontsize=15)
plt.show()

실습에서는 y만을 목표 변수로 할 것이기 때문에 다시 시각화해본다.

# line plot of Y
raw_all[['count']].plot(kind='line', figsize=(20,6), linewidth=3, fontsize=20,
                                              xlim=('2012-01-01', '2012-03-01'), ylim=(0,1000))
plt.title('Time Series of Target', fontsize=20)
plt.xlabel('Index', fontsize=15)
plt.ylabel('Demand', fontsize=15)
plt.show()

 

뭔가 패턴이 있는 것 같기도 하고, 추세나 계절성이 보이는 듯도 하다. 이를 다음 실습에서 알아보도록 합니다.

4.2 데이터를 추세, 계절성, 잔차로 분리하기

sm.tsa.seasonal_decompose를 통해 분리, 이때 addtive는 이것들이 더해져 데이터를 이룰 것이라는 가정을 깔고 가는 것입니다.

(*더하는 관계가 아니라, 곱의 관계라면 model = multiplicative 옵션을 통해 설정합니다.)

 

헷갈린다면 시각화 결과를 통해 각 변수의 분포 특징을 확인하고 합의 관계가 맞는지, 곱의 관계가 맞는지 A/B Test 형식처럼 결정해도 좋습니다. 

# split data as trend + seasonal + residual
plt.rcParams['figure.figsize'] = (14, 9)
sm.tsa.seasonal_decompose(raw_all['count'], model='additive').plot()
plt.show()

plot 들을 살펴보면 각 요소의 대략적인 분포를 확인할 수 있습니다. 이 분포가 더해져 원래 count의 분포 정도를 이룰 수 있음을 대략적으로 미루어볼 수 있습니다.(0~1000)

 

관측값에서 추세와 계절성을 빼면 잔차가 나옴을 다음 코드를 통해 확인해봅니다. nan을 빼면 17520행 정도는 같다는 결과를 확인했습니다.

4.3 데이터를 추세, 계절성, 잔차로 분리하기(데이터에 적용하기)

추세와 계절성을 생성하고, 앞 뒤에 비는 데이터를 ffill, bfill로 채우고 데이터 프레임을 생성.

# fill nan as some values of data
result = sm.tsa.seasonal_decompose(raw_all['count'], model='additive')
Y_trend = pd.DataFrame(result.trend)
Y_trend.fillna(method='ffill', inplace=True)
Y_trend.fillna(method='bfill', inplace=True)
Y_trend.columns = ['count_trend']
Y_trend.fillna(method='ffill', inplace=True)
Y_trend.fillna(method='bfill', inplace=True)
Y_trend.columns = ['count_trend']
Y_seasonal = pd.DataFrame(result.seasonal)
Y_seasonal.fillna(method='ffill', inplace=True)
Y_seasonal.fillna(method='bfill', inplace=True)
Y_seasonal.columns = ['count_seasonal']

Y_seasonal

생성한 데이터 프레임을 합치고, 원래 데이터 프레임에 추가하기

Y_trend.fillna(method='ffill', inplace=True)
Y_trend.fillna(method='bfill', inplace=True)
Y_trend.columns = ['count_trend']
Y_seasonal = pd.DataFrame(result.seasonal)
Y_seasonal.fillna(method='ffill', inplace=True)
Y_seasonal.fillna(method='bfill', inplace=True)
Y_seasonal.columns = ['count_seasonal']

# merging several columns
pd.concat([raw_all, Y_trend, Y_seasonal], axis=1).isnull().sum()
# pd.concat([raw_all, Y_seasonal], axis=1).isnull().sum()
if 'count_trend' not in raw_all.columns:
    if 'count_seasonal' not in raw_all.columns:
        raw_all = pd.concat([raw_all, Y_trend, Y_seasonal], axis=1)
raw_all

뒤에 확인하면 새 컬럼이 생성된 것을 확인할 수 있다.

4.4 이동평균 생성, 시각화

원래 데이터(시간)와, 시간*24(하루), 일주일을 각각 이동 평균하고 concat 하며 시각화

# comparison of several moving average values
pd.concat([raw_all[['count']],
           raw_all[['count']].rolling(24).mean(),
           raw_all[['count']].rolling(24*7).mean()], axis=1).plot(kind='line', figsize=(20,6), linewidth=3, fontsize=20,
                                                                  xlim=('2012-01-01', '2013-01-01'), ylim=(0,1000))
plt.title('Time Series of Target', fontsize=20)
plt.xlabel('Index', fontsize=15)
plt.ylabel('Demand', fontsize=15)
plt.show()

원래 데이터 프레임에 생성한 이동평균값들을 합칩니다.

# fill nan as some values and merging
Y_count_Day = raw_all[['count']].rolling(24).mean() #하루 이동평균 
Y_count_Day.fillna(method='ffill', inplace=True) #이동평균을 내며 없는 값이 생겨 채우기 
Y_count_Day.fillna(method='bfill', inplace=True)
Y_count_Day.columns = ['count_Day']
Y_count_Week = raw_all[['count']].rolling(24*7).mean() #일주일 이동평균 
Y_count_Week.fillna(method='ffill', inplace=True) #없으면 채워주기 
Y_count_Week.fillna(method='bfill', inplace=True)
Y_count_Week.columns = ['count_Week']
if 'count_Day' not in raw_all.columns: #없으면 합치기
    raw_all = pd.concat([raw_all, Y_count_Day], axis=1)
if 'count_Week' not in raw_all.columns:
    raw_all = pd.concat([raw_all, Y_count_Week], axis=1)
raw_all

4.5 차이(diff) 값 구해서 넣기

# diff of Y and merging
Y_diff = raw_all[['count']].diff()
Y_diff.fillna(method='ffill', inplace=True)
Y_diff.fillna(method='bfill', inplace=True)
Y_diff.columns = ['count_diff']
if 'count_diff' not in raw_all.columns:
    raw_all = pd.concat([raw_all, Y_diff], axis=1)
raw_all

 

강의 소개 링크 : https://bit.ly/3czfg42

반응형

댓글