維基百科對于特征工程的定義是:利用相關(guān)領(lǐng)域知識,通過數(shù)據(jù)挖掘技術(shù)從原始數(shù)據(jù)中提取特征的過程。這些特征可以用來提高機器學(xué)習(xí)算法的性能。
不過,特征工程不一定非得很花哨。特征工程的一個簡單但普遍的處理對象是時間序列數(shù)據(jù)。特征工程在這個領(lǐng)域的重要性是因為(原始)時間序列數(shù)據(jù)通常只包含一個表示時間屬性的列,即日期時間(或時間戳)。
對于日期時間數(shù)據(jù),特征工程可以看作是從獨立的(不同的)特征數(shù)據(jù)中提取有用的信息。例如,從"2020–07–01 10:21:05"這日期時間數(shù)據(jù)中,我們可能需要從中提取以下特征:
1. 月份:7
1. 本月第幾日:1
1. 周幾:周三(通過2020-07-01判斷得到)
1. 時刻:10:21:05
從日期時間數(shù)據(jù)中提取這類特征正是本文的目標。之后,我們將結(jié)合我們的工程實際中的特征數(shù)據(jù),將其作為預(yù)測因子,并且建立一個gradient boosting 回歸預(yù)測模型。具體來說,我們將預(yù)測地鐵州際交通量。
本文目錄
本文主要包含以下內(nèi)容:
詳細闡述如何從時間日期數(shù)據(jù)中提取以下特征數(shù)據(jù):
1. 月份
1. 時間數(shù)據(jù)處于每月第幾日
1. 周幾
1. 時間
1. 時段分類(早上、下午等)
1. 周末標記(如果是周末則添加標記1,否則添加標記0)
如何將上述特種數(shù)據(jù)用于搭建Gradient Boosting 回歸模型,并且實現(xiàn)對于地鐵州際交通量的預(yù)測
數(shù)據(jù)情況
在本文中,我們使用地鐵州際交通量數(shù)據(jù)集,它可以在UCI機器學(xué)習(xí)庫(archive.ics.uci.edu/ml/datasets/Metro+Interstate+Traffic+Volume)中找到。該數(shù)據(jù)集是明尼蘇達州圣保羅州明尼阿波利斯市I-94的每小時交通量,其中包括2012-2018年的天氣和假日數(shù)據(jù)。這48204行數(shù)據(jù)包含以下屬性:
1. holiday:類型數(shù)據(jù),包含美國國家法定假日、區(qū)域假日、明尼蘇達州博覽會等
1. temp:數(shù)值型數(shù)據(jù),平均溫度(開爾文)
1. rain_1h:數(shù)值型數(shù)據(jù),每小時降雨(毫米)
1. snow_1h:數(shù)值型數(shù)據(jù),每小時降雪(毫米)
1. clouds_all:數(shù)值型數(shù)據(jù),云層情況(百分比)
1. weather_main:類型數(shù)據(jù),當(dāng)前天氣的分類描述(簡要)
1. weather_description:類型數(shù)據(jù),當(dāng)前天氣的分類描述(詳細)
1. data_time:時間序列數(shù)據(jù)
1. traffic_volume:數(shù)值型數(shù)據(jù),每小時I-94 ATR 301記錄的西行交通量(本文預(yù)測目標)
接下來,我們首先載入數(shù)據(jù):
# import libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# load the dataraw = pd.read_csv('Metro_Interstate_Traffic_Volume.csv')
# display first five rowsraw.head()# display details for each column
raw.info()

raw.head()

raw.info()
查看info信息,我們發(fā)現(xiàn)data_time這一類目是object類型,所以我們需要將其轉(zhuǎn)化為datetime類型:
# convert date_time column to datetime type
raw.date_time = pd.to_datetime(raw.date_time)
特征工程
從上面的info方法的輸出中,我們知道除了date_time列之外還有其他的分類特征。但是由于本文的主要主題是處理時間序列數(shù)據(jù),我們將重點關(guān)注針對date_time的特性工程。
Month
Pandas自身有許多易于使用的方法來處理datetime類型的數(shù)據(jù)。要提取時間/日期信息,我們只需調(diào)用pd.Series.dt。pd.Series.dt.month是提取month信息所需的函數(shù)。這將產(chǎn)生一系列int64格式的月份數(shù)字(例如1代表1月,10代表10月)。
# extract month feature
months = raw.date_time.dt.month
Day of month
和Month類似,我們只需要調(diào)用pd.Series.dt.day函數(shù)。以2012-10-27 09:00:00為例,調(diào)用該函數(shù)提取結(jié)果為27。
# extract day of month feature
day_of_months = raw.date_time.dt.day
Hour
類似地,pd.Series.dt.hour將生產(chǎn)對應(yīng)的小時信息數(shù)據(jù)(范圍為0-23的整數(shù))。
# extract hour feature
hours = raw.date_time.dt.hour
Day name
獲取Day name的方式和上面幾個數(shù)據(jù)有所不同。我們想要確定raw.date_time序列中關(guān)于星期幾的信息,需要以下兩個步驟。首先,通過pd.Series.dt.day_name()生成day name序列。然后,我們需要通過pd.get_dummies()進行獨熱編碼(one-hot encode)。
# first: extract the day name literal
to_one_hot = raw.date_time.dt.day_name()
# second: one hot encode to 7 columns
days = pd.get_dummies(to_one_hot)
#display data
days

獨熱編碼后的Day name信息
Daypart
在本部分中,我們將基于Hour數(shù)據(jù)創(chuàng)建一個分組。我們希望有六個小組代表每一天的各個部分。它們是黎明(02.00-05.59)、上午(06.00-09.59)、中午(10.00-13.59)、下午(14.00-17.59)、晚上(18.00-21.59)和午夜(22.00-次日01.59)。
為此,我們創(chuàng)建了一個標識函數(shù),稍后將使用該函數(shù)來作為數(shù)據(jù)系列的Apply方法。然后,我們對得到的dayparts執(zhí)行一個熱編碼。
# daypart function
def daypart(hour): if hour in [2,3,4,5]:
return "dawn"
elif hour in [6,7,8,9]:
return "morning"
elif hour in [10,11,12,13]:
return "noon"
elif hour in [14,15,16,17]:
return "afternoon"
elif hour in [18,19,20,21]:
return "evening"
else: return "midnight"
# utilize it along with apply methodraw_dayparts = hours.apply(daypart)# one hot encodingdayparts = pd.get_dummies(raw_dayparts)# re-arrange columns for convenience
dayparts = dayparts[['dawn','morning','noon','afternoon','evening','midnight']]
#display datadayparts

獨熱編碼后的Day parts信息
Weekend flag
我們從date_time時間序列數(shù)據(jù)中提取的最后一個特征是is_weekend。這一特征指示給定的日期時間是否在周末(星期六或星期日)。為了實現(xiàn)這一目標,我們將利用pd.Series.dt.day_name()方法以及l(fā)ambda函數(shù)。
# is_weekend flag
day_names = raw.date_time.dt.day_name()
is_weekend = day_names.apply(lambda x : 1 if x in ['Saturday','Sunday'] else 0)
Holiday flag 以及 weather
幸運的是,這些數(shù)據(jù)還包含公共假日信息。信息是細粒度的,因為它提到每個公共假日的名稱。盡管如此,本文假設(shè)對每個假期進行編碼并沒有顯著的好處。因此,讓我們創(chuàng)建一個二進制特性來指示對應(yīng)的日期是否是假日。
# is_holiday flag
is_holiday = raw.holiday.apply(lambda x : 0 if x == "None" else 1)
我們需要考慮的最后一個分類特征是天氣。我們只對該特征進行如下獨熱編碼。
# one-hot encode weather
weathers = pd.get_dummies(raw.weather_main)
#display data
weathers

獨熱編碼后的Weather信息
特征處理后的數(shù)據(jù)
現(xiàn)在,我們終于有了最終的可用于訓(xùn)練的數(shù)據(jù)!讓我們創(chuàng)建一個名為features的全新數(shù)據(jù)集,它包含所有的特征,包括數(shù)值型特征(我們從原始數(shù)據(jù)中按原樣放置)和類型特征(我們設(shè)計的特性)。
# features table
#first step: include features with single column naturefeatures = pd.DataFrame({ 'temp' : raw.temp,
'rain_1h' : raw.rain_1h,
'snow_1h' : raw.snow_1h,
'clouds_all' : raw.clouds_all,
'month' : months,
'day_of_month' : day_of_months,
'hour' : hours,
'is_holiday' : is_holiday,
'is_weekend' : is_weekend
})#second step: concat with one-hot encode typed features
features = pd.concat([features, days, dayparts, weathers], axis = 1)
# target columntarget = raw.traffic_volume
在我們將數(shù)據(jù)輸入模型之前,我們需要分割數(shù)據(jù)(訓(xùn)練集和測試集)。請注意,下面我們不隨機化我們的數(shù)據(jù),這是由于我們的數(shù)據(jù)具有時間序列特征。
#split data into training and test data
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.1, shuffle = False)
建立回歸預(yù)測模型
現(xiàn)在我們準備建立我們的模型來預(yù)測地鐵州際交通量。在這項工作中,我們將使用Gradient Boosting回歸模型。
該模型的理論和具體細節(jié)超出了本文的討論范圍。但是簡單來說,gradient-boosting模型屬于集成模型,它使用梯度下降算法來降低弱學(xué)習(xí)模型(決策樹)中的預(yù)測損失。
訓(xùn)練模型
讓我們在訓(xùn)練數(shù)據(jù)上實例化模型并訓(xùn)練模型!
from sklearn import datasets, ensemble
# define the model parametersparams = {'n_estimators': 500,
'max_depth': 4,
'min_samples_split': 5,
'learning_rate': 0.01,
'loss': 'ls'}
# instantiate and train the modelgb_reg = ensemble.GradientBoostingRegressor(**params)gb_reg.fit(X_train, y_train)
評價模型
我們選擇兩個指標來評價模型:MAPE 和 R2得分。在測試集上使用訓(xùn)練完成的模型進行預(yù)測,然后計算這兩個指標。
# define MAPE function
def mape(true, predicted):
inside_sum = np.abs(predicted - true) / true
return round(100 * np.sum(inside_sum ) / inside_sum.size,2)
# import r2 score
from sklearn.metrics import r2_score
# evaluate the metricsy_true = y_testy_pred = gb_reg.predict(X_test)#print(f"GB model MSE is {round(mean_squared_error(y_true, y_pred),2)}")
print(f"GB model MAPE is {mape(y_true, y_pred)} %")
print(f"GB model R2 is {round(r2_score(y_true, y_pred)* 100 , 2)} %")

測試集上的評價指標結(jié)果
我們可以看出我們的模型性能相當(dāng)不錯。我們的MAPE低于15%,而R2得分略高于95%。
結(jié)果可視化
為了直觀理解模型性能,結(jié)果可視化很有必要。
由于我們的測試數(shù)據(jù)(4820個數(shù)據(jù)點)的長度,我們只繪制了最后100個數(shù)據(jù)點上的實際值和模型預(yù)測值。此外,我們還包括另一個模型(在下面的繪圖代碼中稱為gb_reg_lite),它不包含日期時間特征作為其預(yù)測因子(它只包含非日期時間列作為特征,包括temp、weather等)。
fig, ax = plt.subplots(figsize = (12,6))
index_ordered = raw.date_time.astype('str').tolist()[-len(X_test):][-100:]
ax.set_xlabel('Date')
ax.set_ylabel('Traffic Volume')
# the actual valuesax.plot(index_ordered, y_test[-100:].to_numpy(), color='k', ls='-', label = 'actual')
# predictions of model with engineered featuresax.plot(index_ordered, gb_reg.predict(X_test)[-100:], color='b', ls='--', label = 'predicted; with date-time features')
# predictions of model without engineered featuresax.plot(index_ordered, gb_reg_lite.predict(X_test_lite)[-100:], color='r', ls='--', label = 'predicted; w/o date-time features')
every_nth = 5
for n, label in enumerate(ax.xaxis.get_ticklabels()):
if n % every_nth != 0:
label.set_visible(False)ax.tick_params(axis='x', labelrotation= 90)
plt.legend()plt.title('Actual vs predicted on the last 100 data points')
plt.draw()

后100個點的預(yù)測結(jié)果
該圖中藍色虛線與黑色實線十分接近。也就是說,我們提出的gradient-boosting模型可以很好地預(yù)測地鐵交通量。
同時,我們看到不使用日期時間特征的模型在性能上出現(xiàn)了差異(紅色虛線)。為什么會這樣?只是因為我們會依賴交通工具,交通流量在周末趨于減少,但在高峰時段出現(xiàn)高峰。因此,如果我們不對日期時間數(shù)據(jù)進行特征工程處理,我們將錯過這些重要的預(yù)測因子!
作者:Pararawendy Indarjo
deephub翻譯組 OliverLee