728x90

본 내용은 Dacon의 동서발전 태양광 발전량 예측 AI 경진대회에 참가한 프로젝트 내용입니다.

 

주어진 데이터를 통해 태양광 발전량 예측 모델을 만들어 봤습니다.

아래는 Data& Target Data 일부분을  캡처한 그림입니다. 

Data(좌), Target(우)

1. Import and Libraries

!pip install tsfresh

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
from tqdm import tqdm

# Ignore the warnings
import warnings 
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
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
from sklearn import preprocessing

# Modeling algorithms
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor
from sklearn.feature_selection import RFECV
from sklearn import linear_model
from statsmodels.stats.outliers_influence import variance_inflation_factor
import statsmodels.api as sm
from scipy import stats
import tsfresh

# Model selection
from sklearn.model_selection import train_test_split

# Evaluation metrics
from sklearn.metrics import mean_squared_log_error, mean_squared_error, r2_score, mean_absolute_error

from google.colab import drive

 

2. 선형보건법 fcst/ obs 비교 시각화

데이콘에서 제시한 선형보건법 함수와 주어진 데이터인 예보데이터(fcst),실측치(obs)를 활용하여 가장 오차가 작은 예보 시간대를 선택합니다.

for number in [11,14,17,20]:
    inter_obs = obs_to_interpolate(dangjin_obs,False)
    inter_fcst= fcst_to_interpolate(dangjin_fcst,number,False)
    inter_obs2=inter_obs[24:].reset_index(drop=True)
    
    pd.concat([inter_obs2[['Temperature']],
         inter_fcst[0:25608][['Temperature']]], axis=1).plot(kind='line',figsize=(20,10),
                                                   linewidth=3, fontsize=20,
                                                   ylim=(-15,40))
    print('%s일 경우의 그래프'%number)
    plt.title('Temparature plot', fontsize=20)
    plt.xlabel('label', fontsize=15)
    plt.ylabel('Temparature', fontsize=15)
    plt.show()
    
    #잔차
    Resid=((inter_obs2[['Temperature']].values.flatten()-inter_fcst[0:25608][['Temperature']].values.flatten())**2).mean()
    print('Resid',Resid)

*11시 예보 데이터 사용 그래프 => Resid 2.899278434194671

*14시 예보 데이터 사용 그래프=>Resid 2.899264549644704

*17시 예보 데이터 사용 그래프=>Resid 2.81235024211184

*20시 예보 데이터 사용 그래프=>Resid 2.811606550904232 (20시 예보 데이터가 실측치와 가장 근접)

 

 

3. Exploratory Data Analysis

3.1 연도별

for col in concat_df.columns[:4]:
    plt.figure(figsize=(15,6))
    g = sns.boxplot(x='year', y=col, data=concat_df, showfliers=False)
    g.set_title('box plot of %s'%col, size=20)
    g.set_xticklabels(g.get_xticklabels(), rotation=90)
    plt.show()

 

연도별 시각화 결과, dangjin_floating 수치만 감소세를 보입니다. 수치로 확인하기 위해 아래처럼 작업을 진행했습니다.

concat_df.groupby('year').agg({'dangjin_floating':['count','sum','mean']})

2018년도는 3월부터 기록되어 있으므로 Count 수가 다릅니다. 하지만 태양 에너지 평균은 가장 높습니다.

dangjin_floating 만 감소세를 보이는 원인에 대해서는 파악이 되지 않으나 dangjin_floating 에너지 수급 패턴을 짐작할수 있습니다.

 

 

 

3.2 월별

Y_colname = 'dangjin_floating','dangjin_warehouse','dangjin','ulsan'
for col in Y_colname:
    concat_df2 = concat_df[concat_df[col]!=0] 
    result = concat_df2.groupby('month').agg({col:['sum','mean','count']}).reset_index(drop=False)
    # print(result.iloc[:,3])
    plt.figure(figsize=(15,6))
    plt.subplot(1,2,1)
    plt.plot(result.iloc[:,0], result.iloc[:,2])
    plt.title('%s mean'%col)
    plt.subplot(1,2,2)
    plt.plot(result.iloc[:,0], result.iloc[:,1])
    plt.title('%s sum'%col)
    plt.show()

concat_df.boxplot(column ='ulsan', by ='month', grid =True, figsize=(12,5))
concat_df.boxplot(column ='dangjin_floating', by ='month', grid =True, figsize=(12,5))

여름에 가장 많은 태양광 에너지를 얻을거라는 예상과 달리 3~4월 기점으로 에너지 수급이 7월까지 급격한 감소 추세를 보입니다. 7월 이후로 반등하는 모습을 보이다가 10월 이후, 겨울이 본격적으로 시작되면서 다시 감소 추세입니다. 가장 수급이 좋은 시기는 초봄인 3월~4월과 더위가 점차 사그라드는 9~10월로 보입니다. 즉 적당한 환경일때 효율적인 수급이 가능 한걸로 추측됩니다.

이후 조사 결과 태양광 에너지 수급에 있어서 중요한 핵심은 태양광의 모듈 온도 이라고 합니다. 이 모듈 온도가 높아지면 효율이 떨어진다고 합니다. 그래서 실제로 이 온도를 낮추기 위한 시설이나 장치를 설치하는 경우가 있다고 합니다. 

 

3.3 시간대별

for col in Y_colname:
  concat_df2 = concat_df[concat_df[col]!=0]     
  result=concat_df2.groupby(['hour']).agg({col:['count','sum','mean']})
  result.iloc[:,2].plot(label = '%s_energy'%col, legend=True, figsize=(20,10))

시간대는 약 13시30분에 가장 높은 수급을 보입니다.

정리를 하면, 3~4월의 13시30분에 가장 높은 에너지를 확보 할수 있습니다.

데이터 보기전에는 무조건 온도가 높으면 좋을 줄 알았는데 조금 의외의 결과였습니다.

 

3.4 날씨별

 

*Cloud : 하늘상태(1-맑음, 2-구름보통, 3-구름많음, 4-흐림)

concat_df['Cloud'] = concat_df['Cloud'].astype('int') #일기예보의 구름 양 예측에 따른 에너지 양
Y_colname = ['dangjin_floating','dangjin_warehouse','dangjin','ulsan']
cloud = concat_df.groupby(['Cloud'])[Y_colname].sum()
cloud.plot(kind = 'bar')

대체로 구름이 없을수록 태양열 에너지 수급이 원활함을 알수 있음 특이한 점은 구름이 많은 상태인 3에서 소폭 상승을 보였다는 점이다. 자세한 파악을 위해서 상관성을 시각화 해보았습니다.

import seaborn as sns
plt.figure(figsize=(16,10))
sns.heatmap(concat_df.corr(), annot=True)
plt.show()

Corr 결과만 봤을때는 음의 상관관계이므로 1->4로 갈수록 수급이 떨어져야 정상입니다.  추측으로는 다중공선성의 영향이 있을 거라 생각됩니다. 

 

3.5 일자별

#Date 설정
concat_df['date'] = concat_df['Forecast_time'].dt.date
concat_df['date'] = pd.to_datetime(concat_df['date'])

#2019/01일 부터 시각화
date_df = concat_df[concat_df['date'] >= '2019-01-01']
date_df =date_df.groupby('date')[Y_colname].sum().reset_index()
plt.figure(figsize=(20,9))
plt.plot('date', 'dangjin_floating', 'g-',label='dangjin_floating',data=date_df)
plt.gcf().autofmt_xdate()
plt.legend(loc='center left', bbox_to_anchor=(1,0.5), fontsize=20)
plt.title('date_df')
plt.show()

월별 시각화와 일맥상통으로 봄에서 겨울 사이에는 감소를 보이다가 다시 상승하는 패턴을 보입니다.

상승이나 하강의 추세는 아직까진 없어 보이는 대신 계절성 패턴이 명확하게 보입니다. 

 

4. Data Preprocessing & Feature Engineering

# 선형 보건법
def to_date(x):
    return pd.DateOffset(hours=x)

def fcst_to_interpolate(dangjin_fcst,date_number,graph=True):

    #날짜타입으로 변경
    dangjin_fcst['Forecast_time'] = pd.to_datetime(dangjin_fcst['Forecast time'])

    #적용할 date_number
    fcst = dangjin_fcst[dangjin_fcst['Forecast_time'].dt.hour==date_number]
    fcst = fcst[(fcst['forecast']>=24-date_number) & (fcst['forecast']<=47-date_number)] 

    #forecast 시간 합치기
    fcst['Forecast_time'] = fcst['Forecast_time'] + fcst['forecast'].map(to_date)

    #사용될 컬럼 추출
    fcst = fcst[['Forecast_time', 'Temperature', 'Humidity', 'WindSpeed', 'WindDirection', 'Cloud']]
    
    #선형보건법이 적용된 데이터 프레임 생성 
    fcst_= pd.DataFrame()
    fcst_['Forecast_time'] = pd.date_range(start='2018-03-02 00:00:00', end='2021-03-01 23:00:00', freq='H')
    fcst_merge = pd.merge(fcst_, fcst, on='Forecast_time', how='outer')
    inter_fcst = fcst_merge.interpolate()

    if graph:
        plt.figure(figsize=(20,5))
        days = 5
        plt.plot(inter_fcst.loc[:24*days, 'Forecast_time'], inter_fcst.loc[:24*days, 'Temperature'], '.-')
        plt.plot(fcst_merge.loc[:24*days, 'Forecast_time'], fcst_merge.loc[:24*days, 'Temperature'], 'o')
    
    return inter_fcst

def obs_to_interpolate(dangjin_obs,graph=True):

    #날짜타입으로 변경
    dangjin_obs['Forecast_time'] = pd.to_datetime(dangjin_obs['time'])

    #사용될 컬럼 추출
    obs = dangjin_obs[['Forecast_time', 'Temperature', 'Humidity', 'WindSpeed', 'WindDirection', 'Cloud']]
    
    #선형보건법이 적용된 데이터 프레임 생성 
    obs_= pd.DataFrame()
    obs_['Forecast_time'] = pd.date_range(start='2018-03-01 00:00:00', end='2021-01-31 23:00:00', freq='H')
    obs_merge = pd.merge(obs_, obs, on='Forecast_time', how='outer')
    inter_obs = obs_merge.interpolate()

    if graph:
        plt.figure(figsize=(20,5))
        days = 5
        plt.plot(inter_obs.loc[:24*days, 'Forecast_time'], inter_obs.loc[:24*days, 'Temperature'], '.-')
        plt.plot(obs_merge.loc[:24*days, 'Forecast_time'], obs_merge.loc[:24*days, 'Temperature'], 'o')
    
    return inter_obs

def feature_engineering(concat_df, target):
    if 'Forecast_time' in concat_df.columns:
        concat_df['Forecast_time']=pd.to_datetime(concat_df['Forecast_time'])
        concat_df.index =concat_df['Forecast_time']

    concat_df=concat_df.asfreq('H')

    #0으로 되어 있는 값을 작년 수치 & 변수 대체
    concat_df.loc['2020-06-19 11:00:00':"2020-06-25 23:00:00",'dangjin_floating'] = concat_df.loc['2019-06-19 11:00:00':'2019-06-25 23:00:00','dangjin_floating'].values
    concat_df.loc['2020-06-19 11:00:00':"2020-06-25 23:00:00",'Temperature':]  = concat_df.loc['2020-06-19 11:00:00':"2020-06-25 23:00:00",'Temperature':].values 

    #nan값 채우기
    concat_df.loc['2020-06-26 01:00:00':"2020-06-27 00:00:00",'dangjin_floating'] = concat_df.loc['2020-06-28 01:00:00':'2020-06-29 00:00:00','dangjin_floating'].values
    concat_df.loc['2018-03-17 01:00:00':"2018-03-19 00:00:00",'dangjin_warehouse'] = concat_df.loc['2018-03-15 01:00:00':'2018-03-17 00:00:00','dangjin_warehouse'].values
    
    #날짜 형식 추가
    concat_df['hour']=concat_df['Forecast_time'].dt.hour
    concat_df['year']=concat_df['Forecast_time'].dt.year
    concat_df['month']=concat_df['Forecast_time'].dt.month
    concat_df['day']=concat_df['Forecast_time'].dt.day

    result =sm.tsa.seasonal_decompose(concat_df[target], model='additive')
    Y_seasonal = pd.DataFrame(result.seasonal)
    Y_seasonal.fillna(method='bfill', inplace=True)
    Y_seasonal.fillna(method='ffill', inplace=True)
    Y_seasonal.columns= ['%s_seasonal'%target]

    concat_df = pd.concat([concat_df,Y_seasonal], axis=1)
    return concat_df

def feature_engineering_lag(concat_fe,x_colname,number,multi_number=1):
  for col in x_colname:
    for mul_number in range(1,multi_number+1):
      shift_number = concat_fe[[col]].shift(number*mul_number)
      shift_number.fillna(method='bfill', inplace=True)
      shift_number.fillna(method='ffill', inplace=True)
      shift_number.columns =['{}_lag{}'.format(col,number*mul_number)]
      concat_fe = pd.concat([concat_fe,shift_number], axis=1)
  return concat_fe
  
def feature_engineering_diff(concat_fe, x_colname, diffs):
  diff_multi_number =2
  for col in x_colname:
    for number in range(1,diffs+1):
      diff_number = concat_fe[[col]].diff(number)
      diff_number.fillna(method='bfill', inplace=True)
      diff_number.fillna(method='ffill', inplace=True)
      diff_number.columns =['{}_diff{}'.format(col,number)]
      concat_fe =pd.concat([concat_fe, diff_number], axis=1)
  return concat_fe

def feature_engineering_duplicated(x_train,x_val,target):
  target = ['%s_seasonal'%target]  # '%s_trend'%target '%s_diff'%target,'%s_lag_1'%target, '%s_lag_2'%target,'Y_%s_week'%target, 'Y_%s_day'%target
  for col in target:
    x_val.loc['2021-01-01 00:00:00':'2021-01-31 23:00:00',col] = x_train.loc['2020-01-01 00:00:00':'2020-01-31 23:00:00',col].values
  return x_val
  
#Datasplit
def train_dataset(concat_df,criteria, X_colname, target):
    if 'time' in concat_df.columns:
      del concat_df['time']
    if 'Forecast_time' in concat_df.columns:
      del concat_df['Forecast_time']

    train_df = concat_df.loc[concat_df.index<criteria,:]
    val_df = concat_df.loc[concat_df.index>=criteria,:]
    
    x_train = train_df[X_colname]
    y_train = train_df[target]
    
    x_val = val_df[X_colname]
    y_val = val_df[target]
    print('x_train:',x_train.shape, 'y_train:',y_train.shape)
    print('x_val',x_val.shape,'y_val',y_val.shape)
    return x_train,x_val, y_train,y_val

#Scaling
def Scaling(x_train,x_val,scaler):
  from sklearn import preprocessing
  scaler = scaler
  scaler_fit = scaler.fit(x_train)
  x_train_feRS = pd.DataFrame(scaler_fit.transform(x_train), index =x_train.index, columns=x_train.columns)
  x_val_feRS = pd.DataFrame(scaler_fit.transform(x_val),index =x_val.index, columns=x_val.columns)
  return x_train_feRS,x_val_feRS

#다중공선성
def feature_engineering_VIF(x_train):
  vif= pd.DataFrame()
  vif['VIF_Factor'] = [variance_inflation_factor(x_train.values,i) for i in range(x_train.shape[1])]
  vif['Feature'] = x_train.columns
  # x_colname_vif = vif.sort_values(by='VIF_Factor', ascending=True)['Feature'][:num_variables].values
  return  vif

#evalution
def evaluation(y_train, pred, graph=True):
    loss_length = len(y_train.values.flatten()) - len(pred)
    if loss_length !=0:
        print('길이가 맞지 않습니다.')
    if graph == True:
        pd.concat([y_train, pd.DataFrame(pred, index=y_train.index, columns=['prediction'])], axis=1).plot(kind='line', figsize=(20,6),
                                                                                                        xlim=(y_train.index.min(), y_train.index.max()),
                                                                                                        linewidth=3, fontsize=20)
        plt.title('Time Series of Target', fontsize=20)
        plt.xlabel('index', fontsize=15)
        plt.ylabel('Target value', fontsize=15)
    Residual = pd.DataFrame(y_train.values.flatten() - pred, index= y_train.index, columns=['Error'])
    return Residual

def evaluation_trte(y_train, y_train_pred, y_val, y_val_pred, graph=True ):
    Residual_train = evaluation(y_train, y_train_pred,graph=graph)
    Residual_val = evaluation(y_val, y_val_pred, graph=graph)
    return Residual_train,Residual_val

def nmae_10(y_pred, dataset,capacity=1000):
    y_true = dataset.get_label()
    absolute_error = abs(y_true - y_pred)
    absolute_error /= capacity
    target_idx = np.where(y_true>=capacity*0.1)
    nmae = 100 * absolute_error[target_idx].mean()
    return 'score', nmae, False

def sola_nmae(answer, pred,capacity=1000):
    absolute_error = np.abs(answer - pred)
    absolute_error /= capacity
    target_idx = np.where(answer>=capacity*0.1)
    nmae = 100 * absolute_error[target_idx].mean()
    return nmae
    
#feature engineering
concat_fe=feature_engineering(concat_df,"dangjin_floating")

#X's diff 생성 
x_colname=['Temperature','WindSpeed','Humidity'] # 'year', 'month', 'day'  
concat_fe = feature_engineering_diff(concat_fe,x_colname,6) 

#X's lag 생성 
# x_colname2=['WindDirection']
# concat_fe = feature_engineering_lag(concat_fe,x_colname2,4)

#datasplit
Y_colname = ['dangjin_floating','dangjin_warehouse','dangjin','ulsan']
X_colname =[col for col in concat_fe.columns if col not in Y_colname+['time','Forecast_time',"year"]]
x_train_feR,x_val_feR, y_train_feR,y_val_feR =train_dataset(concat_fe,'2021-01-01 00:00:00',X_colname,'dangjin_floating')

# #feature_engineering_duplicated
x_val_feR = feature_engineering_duplicated(x_train_feR,x_val_feR,'dangjin_floating')

#설명변수 스케일링
x_train_feRS, x_val_feRS = Scaling(x_train_feR,x_val_feR,preprocessing.StandardScaler())

#다중공선성
vif = feature_engineering_VIF(x_train_feRS)
vif.sort_values(by=['VIF_Factor'], inplace=True)
vif.reset_index(drop=True, inplace=True)
VIF_colname =[]
for idx,x in enumerate(vif['VIF_Factor'].values): 
  if x <= 15 : 
    VIF_colname.append(vif['Feature'][idx])

#REV
ols = linear_model.LinearRegression()
refecv = RFECV(estimator=ols, step=1, scoring='neg_mean_squared_error')
refecv.fit(x_train_feRS, y_train_feR)
refecv.transform(x_train_feRS)
refecv_colname =[colname for idx,colname in enumerate(x_train_feRS.columns) if refecv.ranking_[idx]==1]

 

5. modeling

*target의 정규화를 각 모델에 적용한 결과, 비 정규화가 더 높은 성능을 보였습니다. 정규화 결과는 간결한 진행을 위해서 제외 시켰습니다. 

 

5.1 auto_arima

!pip install pmdarima
from pmdarima.arima import auto_arima

arima_model = auto_arima(y_train_feR, start_p=2, d=0, start_q=2,
                         max_p=3, max_d=1, max_q=3,
                          start_P=1, D=1, start_Q=1,
                         max_P=7, max_D=3, max_Q=7, m=24, seasonal=True,
                         error_action='ignore', trace=True,
                         supress_warnings=True, stepwise=True,
                         random_state=20) #, n_fits=50
arima_model.fit(y_train_feR)

auto_arima로 파리미터를 찾으려고 했으나 computing 문제로 초반부터 중단이 되었습니다. SARIMAX의 acf,pacf 결과로 order 추정을 해보았습니다. 

 

5.2 SARIMAX

가장 성능이 좋았던 order

위 설정값은 2020년도 값만 적용했을때의 결과값입니다. 이 order 값을 전체 데이터에 적용해 보았지만 computing 문제로 실행되지 않았습니다. 따라서 order 값을 적용했을때의 모습을 보여 드리기 위하여 아래 임의의 order값을 적용했습니다.

#SARIMAX

trend_order = (0,0,0)
seasonal_order = (2,1,2,24)
model = sm.tsa.SARIMAX(y_train_feR, trend='c', order =trend_order, seasonal_order= seasonal_order, exog = x_train_feRS) 
result = model.fit()
print('SARIMAX {}*{} and AIC: {}'.format(trend_order,seasonal_order,result.aic))
display(result.summary())

#예측
SARIMAX_train_pred = result.predict(start=0, end=len(y_train_feR)-1, exog = x_train_feRS)
SARIMAX_val_pred = result.forecast(len(y_val_feR), exog = x_val_feRS) #1월달 예측

display('train_CV Score : ', sola_nmae(y_train_feR.to_numpy(), SARIMAX_train_pred.to_numpy()))
display('val_CV Score : ', sola_nmae(y_val_feR.to_numpy(), SARIMAX_val_pred.to_numpy()))

#시각화
plt.figure(figsize=(20,10))
plt.subplot(2,1,1)
plt.plot(SARIMAX_train_pred, label = 'train_pred')
plt.plot(y_train_feR, label ='true')
plt.legend()
plt.title('train plot')

plt.subplot(2,1,2)
plt.plot(SARIMAX_val_pred, label = 'val_pred')
plt.plot(y_val_feR, label ='true')
plt.legend()
plt.title('val plot')
plt.show()

#잔차진단
result.plot_diagnostics(figsize=(10,8))
plt.tight_layout()
plt.show()

res= result.resid
sm.tsa.graphics.plot_acf(res, lags=100)
sm.tsa.graphics.plot_pacf(res, lags=100)

분석결과 

자기상관성 존재하며 정규분포는 아니며 등분산은 아니라는 결과를 받았습니다. 정상성을 만들지 못했습니다. 총 62번의 실험을 해보았지만 정상성을 만족하는 order값은 차지 못했습니다. 또한 데이콘에서 제시한 베이스 코드의 점수인 8.3에 못미치는 점수가 나왔으므로 SARIMAX로 모델링 하는건 시간적으로 무리가 있다고 생각했습니다. 

 

 

5.3 지수평활

CV_Score=[]
numbers=range(1,100)
# log = True
for number in numbers:
  fit_exp = ExponentialSmoothing(y_train_feR, seasonal_periods=24*number, trend=None, seasonal='additive').fit(use_boxcox=False)
  forecast_exp = fit_exp.forecast(24*31)

  #예측점수 계산
  CV_Score.append(sola_nmae(y_val_feR.to_numpy(),forecast_exp.to_numpy()))
  idxs.append(number)
pd.options.display.max_rows=100
pd.DataFrame(CV_Score, index= numbers, columns=['Score'])

아래는 가장 점수 성능이 좋은 number=12 일 때의 시각화입니다

seasonal_periods의 number 값을 100까지 실행한 결과 9.5 점수를 얻은 number=12일때 가장 좋은 성능을 보였습니다. 

 

*extract features

개선된 모델을 위하여 중간에 tsfresh 라이브러리를 사용하여 변수를 늘려보았습니다.

!pip install tsfresh

import tsfresh
# extracted_features=tsfresh.extract_features(concat_x, column_id='time')

#데이터 불러오기
extracted_features = pd.read_csv('/content/drive/MyDrive/extracted_features.csv', sep=',')

#rename
extracted_features.rename(columns = {'Unnamed: 0' : 'Forecast_time'}, inplace = True)
extracted_features.set_index('Forecast_time',inplace=True)

extracted_features_df= extracted_features.copy()
extracted_features_df.dropna(axis=1, inplace=True) #결측값 제거
extracted_features_df.isnull().sum().sum()

#열의 평균이 0 or 1이면 삭제
del_list=[]
columns = extracted_features_df.columns
for columns in columns:
  mean_calc = extracted_features_df[columns].mean()
  if mean_calc == 0.0 or mean_calc==1.0:
    del_list.append(columns)
extracted_features_df.drop(columns=del_list, axis=1,inplace=True

#datasplit
from sklearn.model_selection import train_test_split
target = concat_fe['dangjin_floating']
x_train_feR,x_val_feR, y_train_feR,y_val_feR = train_test_split(extracted_features_df, 
                                                    target, 
                                                    test_size=0.1, 
                                                    shuffle=False, 
                                                    random_state=1004)

# 다중공선성
vif = feature_engineering_VIF(x_train_feR)
vif.sort_values(by=['VIF_Factor'], inplace=True)
vif.reset_index(drop=True, inplace=True)
VIF_colname =[]
for idx,x in enumerate(vif['VIF_Factor'].values): 
  if x <= 10: 
    VIF_colname.append(vif['Feature'][idx])
extracted_features_df[VIF_colname]

총 생성된 변수 1300여개 중 전처리 후 다중공선성을 제거하기 위해 vif를 실행한 결과 입니다. 

VIF_colname

위 9개 변수를 포함하여 가장 성능이 좋았던 random forest에 적용 해본 결과, 성능 다운이 발생했습니다. 변수가 많아져서 과적합이 발생한 걸로 추측해 볼수 있습니다. 아래에 진행 될 모델은 위 9개 변수를 제외하여 진행한 내용입니다.

5.4 randomForeest and LGB

def model(target,fcst,energy_df, n_components=3, y_log =False):

  #선형보건법
  #20시간으로 한 선형보건법 적용
  target_fcst= fcst_to_interpolate(fcst,20,False)
  target_fcst = target_fcst.loc[1:25607].reset_index(drop=True)

  #energy + dangjin_fcst
  energy=energy_df[24:-1].reset_index(drop=True)
  concat_df = pd.concat([energy,target_fcst], axis=1)
  concat_df.index = concat_df['Forecast_time']

  #feature engineering
  concat_fe=feature_engineering(concat_df,target)

  #X's diff 생성 
  x_colname=['Temperature','WindSpeed','Humidity'] # 'year', 'month', 'day'  
  concat_fe = feature_engineering_diff(concat_fe,x_colname,6) 

  #X's lag 생성 
  x_colname2=['day']
  concat_fe = feature_engineering_lag(concat_fe,x_colname2,24,2)

  #datasplit
  Y_colname = ['dangjin_floating','dangjin_warehouse','dangjin','ulsan']
  X_colname =[col for col in concat_fe.columns if col not in Y_colname+['time','Forecast_time',"year"]]
  x_train_feR,x_val_feR, y_train_feR,y_val_feR =train_dataset(concat_fe,'2021-01-01 00:00:00',X_colname,target)

  # #feature_engineering_duplicated
  x_val_feR = feature_engineering_duplicated(x_train_feR,x_val_feR,target)

  #설명변수 스케일링
  x_train_feRS, x_val_feRS = Scaling(x_train_feR,x_val_feR,preprocessing.StandardScaler())
  # 1. Normal, 2.Robust = 3.Standard  4. Minmax

  # target log -> 예측범위를 좁히는데 용이
  if y_log:
    y_train_feR = np.log1p(y_train_feR).copy()
    y_val_feR = np.log1p(y_val_feR).copy()

  #다중공선성
  vif = feature_engineering_VIF(x_train_feRS)
  vif.sort_values(by=['VIF_Factor'], inplace=True)
  vif.reset_index(drop=True, inplace=True)
  VIF_colname =[]
  for idx,x in enumerate(vif['VIF_Factor'].values): 
    if x <= 15 : 
      VIF_colname.append(vif['Feature'][idx])

  #PCA 변환
  pca_col =[col for col in x_train_feRS.columns if col not in VIF_colname]
  pca = PCA(n_components=n_components)

  #pca fit
  pca.fit(x_train_feRS[pca_col])
  train_pca_colname = pca.transform(x_train_feRS[pca_col])
  val_pca_colname= pca.transform(x_val_feRS[pca_col])
  train_pca_df = pd.DataFrame(train_pca_colname, columns=['pca_components%s'%x for x in range(n_components)]) #['pca_components1','pca_components2','pca_components3']
  val_pca_df = pd.DataFrame(val_pca_colname, columns=['pca_components%s'%x for x in range(n_components)])
  x_train_feRS = x_train_feRS[VIF_colname]
  x_val_feRS = x_val_feRS[VIF_colname]
  
  for x in range(n_components):
    x_train_feRS.loc[:,'pca_components{}'.format(x)]=train_pca_df.iloc[:,x].values
    x_val_feRS.loc[:,'pca_components{}'.format(x)]=val_pca_df.iloc[:,x].values

  #REV
  ols = linear_model.LinearRegression()
  refecv = RFECV(estimator=ols, step=1, scoring='neg_mean_squared_error')
  refecv.fit(x_train_feRS, y_train_feR)
  refecv.transform(x_train_feRS)
  refecv_colname =[colname for idx,colname in enumerate(x_train_feRS.columns) if refecv.ranking_[idx]==1]

  # 1.RandomForest
  # fit_ranfor = RandomForestRegressor(n_estimators=600, random_state=123,min_samples_leaf=6, min_samples_split=14).fit(x_train_feRS, y_train_feR)
  fit_ranfor = RandomForestRegressor(n_estimators=600, random_state=123).fit(x_train_feRS, y_train_feR)
  random_train_pred = fit_ranfor.predict(x_train_feRS)
  random_val_pred = fit_ranfor.predict(x_val_feRS)

  # 2.LGB
  import lightgbm as lgb
  params = {
      'learning_rate': 0.01,
      'objective': 'regression',
      'metric':'mae',
      'seed':42,
  }

  train_datasets = lgb.Dataset(x_train_feRS.to_numpy(), y_train_feR.to_numpy())
  val_dataset = lgb.Dataset(x_val_feRS.to_numpy(), y_val_feR.to_numpy())
  fit_lgb = lgb.train(params, train_datasets, 10000, val_dataset, feval=nmae_10, verbose_eval=500, early_stopping_rounds=200)

  #lgb 예측
  lgb_train_pred = fit_lgb.predict(x_train_feRS)
  lgb_val_pred = fit_lgb.predict(x_val_feRS)

  #y_log 값 환원 
  if y_log:
    y_train_feR = np.expm1(y_train_feR).copy()
    y_val_feR = np.expm1(y_val_feR).copy()
    random_train_pred = np.expm1(random_train_pred).copy()
    random_val_pred = np.expm1(random_val_pred).copy()
    lgb_train_pred = np.expm1(lgb_train_pred).copy()
    lgb_val_pred = np.expm1(lgb_val_pred).copy()

  # 잔차 진단
  rf_Residual_train,rf_Residual_val = evaluation_trte(y_train_feR,random_train_pred, y_val_feR, random_val_pred, graph=True)
  display('rf_CV Score : ', sola_nmae(y_val_feR.to_numpy(), random_val_pred))
  rf_score = sola_nmae(y_val_feR.to_numpy(), random_val_pred)

  lgb_Residual_train,lgb_Residual_val = evaluation_trte(y_train_feR,lgb_train_pred, y_val_feR, lgb_val_pred, graph=True)
  display('lgb_CV Score : ', sola_nmae(y_val_feR.to_numpy(), lgb_val_pred))
  lgb_score = sola_nmae(y_val_feR.to_numpy(), lgb_val_pred)
  
  return rf_score,lgb_score,rf_Residual_train,lgb_Residual_train,fit_ranfor,fit_lgb,concat_fe,x_train_feRS,y_train_feR,x_val_feRS,y_val_feR,VIF_colname
  
rf_score,lgb_score,rf_Residual_train,lgb_Residual_train,fit_ranfor,fit_lgb,concat_fe,x_train_feRS,y_train_feR,x_val_feRS,y_val_feR,VIF_colname = model('dangjin_floating',dangjin_fcst,energy)

 

rf_train
rf_val
lgb_train

# 랜덤 포레스트 중요도 
importances_values = fit_ranfor.feature_importances_
importances_df = pd.Series(importances_values, index = x_train_feRS.columns).sort_values(ascending=False)
plt.figure(figsize=(20,9))
plt.title('Importancsvariables')
sns.barplot(x = importances_df, y = importances_df.index)
plt.show()

sns.distplot(rf_Residual_train['Error'].iloc[1:], norm_hist='True', fit=stats.norm)
figure, axes = plt.subplots(1, 4, figsize=(30,5))
pd.plotting.lag_plot(rf_Residual_train['Error'].iloc[1:], lag=1, ax=axes[0])
pd.plotting.lag_plot(rf_Residual_train['Error'].iloc[1:], lag=5, ax=axes[1])
pd.plotting.lag_plot(rf_Residual_train['Error'].iloc[1:], lag=10, ax=axes[2])
pd.plotting.lag_plot(rf_Residual_train['Error'].iloc[1:], lag=50, ax=axes[3])

figure, axes = plt.subplots(2,1,figsize=(12,5))
figure = sm.graphics.tsa.plot_acf(rf_Residual_train['Error'].iloc[1:], lags=100, use_vlines=True, ax=axes[0])
figure = sm.graphics.tsa.plot_pacf(rf_Residual_train['Error'].iloc[1:], lags=100, use_vlines=True, ax=axes[1])

rf(랜덤포레스트)의 정규분포/자기상관성 상태를 시각화 해보았습니다. 

ACF를 보면 초반에 튀는 값이 약 7개가 존재하며 중간에 24주기를 중심으로 살짝 튀는 값들도 존재합니다.

제가 이번 프로젝트를 하면서 보았던 자기상관성 그래프들 중 가장 안정적인 모습을 보입니다.

하지만 튀는 값은 존재하므로 제가 잡지 못한 패턴은 존재합니다. 

 

6. Test

def test_dataset(target,concat_fe,start='2021-02-01 00:00:00', end = '2021-02-28 23:00:00', duplicated_start='2020-02-01 00:00:00', duplicated_end = '2020-02-28 23:00:00',n_components=3):
  from sklearn import preprocessing
  from sklearn.decomposition import PCA

  # data loading
  dangjin_fcst = pd.read_csv('/content/drive/MyDrive/dangjin_fcst_data.csv', sep=',')
  ulsan_fcst = pd.read_csv('/content/drive/MyDrive/ulsan_fcst_data.csv', sep=',')

  #당진
  ##선형보건법
  dangjin_fcst= fcst_to_interpolate(dangjin_fcst,20,False)
  dangjin_start_index =dangjin_fcst[dangjin_fcst['Forecast_time']==start].index[0]
  dangjin_end_index =dangjin_fcst[dangjin_fcst['Forecast_time']==end].index[0]
  dangjin_fcst_test = dangjin_fcst.iloc[dangjin_start_index:dangjin_end_index+1,:]

  #울산
  ##선형보건법
  ulsan_fcst= fcst_to_interpolate(ulsan_fcst,20,False)
  ulsan_start_index =ulsan_fcst[ulsan_fcst['Forecast_time']==start].index[0]
  ulsan_end_index =dangjin_fcst[ulsan_fcst['Forecast_time']==end].index[0]
  ulsan_fcst_test = dangjin_fcst.iloc[ulsan_start_index:ulsan_end_index,:]

  ##feature_engineering
  def test_feature_engineering(fcst_test,target):
      if 'Forecast_time' in fcst_test.columns:
          fcst_test['Forecast_time']=pd.to_datetime(fcst_test['Forecast_time'])
          fcst_test.set_index(fcst_test['Forecast_time'], drop=True, inplace=True)

      fcst_test=fcst_test.asfreq('H')

      #날짜 형식 추가
      fcst_test['hour']=fcst_test['Forecast_time'].dt.hour
      fcst_test['month']=fcst_test['Forecast_time'].dt.month
      fcst_test['day']=fcst_test['Forecast_time'].dt.day
      del fcst_test['Forecast_time']

      ##X's diff 생성 
      x_colname=['Temperature','WindSpeed','Humidity'] # 'year', 'month', 'day'  
      test_fe = feature_engineering_diff(fcst_test,x_colname,6) 

      #X's lag 생성 
      x_colname2=['day']
      test_fe = feature_engineering_lag(test_fe,x_colname2,24,2)

      #seasonal_duplicated
      target2 = ['%s_seasonal'%target]
      test_fe.loc[:,target2] = concat_fe.loc[duplicated_start:duplicated_end,target2].values

      #스케일링
      scaler = preprocessing.StandardScaler()
      test_feRS = pd.DataFrame(scaler.fit_transform(test_fe), index = test_fe.index, columns=test_fe.columns)
      
      #PCA 변환
      
      
      pca_col =[col for col in test_feRS.columns if col not in VIF_colname]
      pca = PCA(n_components=n_components)

      #pca fit
      pca.fit(test_feRS[pca_col])
      test_pca_colname = pca.transform(test_feRS[pca_col])
      test_pca_df = pd.DataFrame(test_pca_colname, columns=['pca_components%s'%x for x in range(n_components)])
      test_feRS = test_feRS[VIF_colname]
      for x in range(n_components):
        test_feRS.loc[:,'pca_components{}'.format(x)]=test_pca_df.iloc[:,x].values
      return test_feRS

  #전처리 작업
  if target =='ulsan':
    test_feRS = test_feature_engineering(ulsan_fcst_test,target)
  else:
    test_feRS = test_feature_engineering(dangjin_fcst_test,target)

  return test_feRS
test_feRS = test_dataset('dangjin_floating',concat_fe)
test_feRS.head()

# 2월 test 예측
rf_pred = fit_ranfor.predict(test_feRS)
pd.DataFrame(rf_pred,index = test_feRS.index, columns =['pred']).plot()

 

* dangjin_floating

dangjin_floating 1월 예측 점수(왼쪽) & dangjin_floating 2월 예측(오른쪽)

 

* dangjin_warehouse 

dangjin1월 예측 점수(왼쪽) & dangjin 2월 예측(오른쪽)

 

* dangjin_warehouse 

dangjin_warehouse 1월 예측 점수(왼쪽) & dangjin_warehouse 2월 예측(오른쪽)

 

* ulsan

ulsan 1월 예측 점수(왼쪽) & ulsan 2월 예측(오른쪽)

울산 1월 예측 점수는 다른 target과는 달리 큰 폭으로 오차가 줄어들었습니다. lgb가 randomforest보다 더 좋은 성능을 보이고 있습니다. 하지만 일관성을 위해서 randomforest로 2월달을 예측하였습니다.

 

 

*프로젝트를 끝내며

데이콘 첫 참가라서 꽤나 어리둥절 했던 것같습니다 프로젝트를 거의 다 만들고

본격적인 평가를 할려고 보니까 6월10일부터 누적 평가라고 하더라구요 이를 확인했을때는 이미 

6월 말쯤이라서.. 뒤늦게 제출해도 지나간 날에는 0점으로 매긴다고 합니다. 그래서 프라이빗 평가 대신 대회 끝난 후 퍼블릭 평가를 해볼 생각입니다. 

 

상관성을 보면 추세부분에도 미세하게 연관성이 있어 보입니다. 시계열 모델로 하면 ARMA로 추세부분을 어떻게 해볼순있겠지만 가장 좋은 성능을 보인 랜덤에서는 추세를 어떻게 해야 할까 고민을 좀 해봤습니다.

그래서 생각해 본게 시계열 분해에서 나온 추세를 그냥 넣지 말고 차분이나 lag 등 전처리를 하면 조금이라도 잡을수 있지 않을까 생각해 보면서 마무리를 지어 봅니다.

 

이 대회 첫참가 하고 데이터를 딱 받아 봤을때 뭘 먼저 해야하는지 감도 잡히지 않아서 일주일동안 답답해 했던 날들이 생각납니다. 이번 대회 덕분에 자신감도 생기고 뿌듯했던 경험이었습니다

728x90

'projects' 카테고리의 다른 글

따릉이 자전거 이용 예측_dacon  (0) 2021.09.30
Face Mask Detection  (0) 2021.08.25
뉴욕 택시수요예측  (0) 2021.02.16
국민청원글 토픽모델링  (0) 2021.01.18
CNN_BTS 멤버 image classification  (0) 2021.01.10

+ Recent posts