内容目录
写在前面的话
稍微总结一下,时间序列中的特征,主要针对机器学习树模型,因为是时序数据,所以和寻常的机器学习特征略有不同,比如关注时间特征,滞后特征,滑窗特征等。
1.与时间特征有关的那些事
1.1时间序列特征
import datetime
import pandas as pd
df['ds']=df['ds'].astype(str)
df['ds'] = df['ds'].apply(lambda x: datetime.datetime.strptime(x, "%Y-%m-%d"))
df['year']=df['ds'].dt.year
df['quarter']=df['ds'].dt.quarter
df['month']=df['ds'].dt.month
df['dayofweek']=df['ds'].dt.dayofweek
df['week']=df['ds'].dt.week
诸如此类,更多相信内容请参照一下文档https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
还有格外需要手工生成的时间段切分:比如讲一天分为上午,下午,晚上这样的特征,可以使用apply,配合lambda匿名函数
df['half']=df['hour'].apply(lambda x: 1 if x<'12' else 0)
对于出现的异常值,年底出现促销,导致销售波动,对该天的数据做异常标记
df['outlier']=df['ds'].apply(lambda x: 1 if x=='2018-12-31' else 0)
还如,定义是否为春节月,是否为月末等,对这类具有明显销售波动的带有时间点打标签。这部分特征是十分基础且重要。
1.2类别mean encoding特征
- 针对类别变量可以有如下方法
- Mean Encoding
- 比如门店预测
- 带有城市标签,如果用OneHotEncoder转换为0-1特征就太多了
- 我们可以使用mean encoding方式,构建均值特征
city_mean=df.groupby(['city']).agg({'y': ['mean']})
city_mean.reset_index(inplace=True)
city_mean.columns = ['city','city_mean']
其他的信息,比如省份等亦是如此
df = pd.merge(df,city_mean,on='city',how='inner')
还有一种类似的count encoding,
其实应该归于统计特征
至于实现方式那就是
city_count=df.groupby(['city']).agg({'y': ['count']})
1.3统计特征
如mean, median, max, min, std
如果是月的话,建议选择三 如果是日,且有周的规律性,则应该选择7,也可以同时存在多个颗粒度的滑窗特征。比如,选择了滑窗4,同时使用滑窗12,那就是季度。当然你也可是多尝试,所以调参侠的乐趣/苦逼,也就在这里了。滑窗也是一种对数据的平滑。一定不要忘记,把数据的顺序换过来。
df.sort_values(['store_code','ds'],ascending=[True,True],inplace=True)
f_min = lambda x: x.rolling(window=4, min_periods=1).min()
f_max = lambda x: x.rolling(window=4, min_periods=1).max()
f_mean = lambda x: x.rolling(window=4, min_periods=1).mean()
f_std = lambda x: x.rolling(window=4, min_periods=1).std()
f_median=lambda x: x.rolling(window=4, min_periods=1).median()
function_list = [f_min, f_max, f_mean, f_std,f_median]
function_name = ['min', 'max', 'mean', 'std','median']
for i in range(len(function_list)):
df[('stat_%s' % function_name[i])] = df.sort_values('ds').groupby(['store_code'])['y'].apply(function_list[i])
说明一点,这里为何没有使用sum聚合函数,因为有了mean,所以就没有必要使用sum了,因为sum是可以依据mean求得(可以依据一个特征直接得到的另一个变量的就属于冗余特征),还有峰度,偏度等。
有一个在时间序列方面非常出名的特征库:tsfresh。提供了非常多的特征扩展,我在之前的一篇文章中有提到,感兴趣的读者,可以前往一看。
tsfresh已经可以生成这么多的特征了,为何我们不直接调包,还要花这么大的力气来手工一个一个实现呢?
针对这两点疑问,有如下解释,虽然目前计算资源较为充裕,但我们也不希望在模型准确率接近的情况下,一个自动生成了300维度的特征花费两个小时,一个自己扩展了30维度的特征花费3分钟,不断迭代中的等待时间是非常昂贵的,所以我个人倾向于手工实现,做到心中有数,否则和黑匣子有什么区别。
其二,我们在很多情况下还是要追求模型的可解释性,少而精的特征,对于指导运营人员,具有非常大的价值,需要明白知道特征的含义。
1.4滞后历史特征
for i in [1,2,4,8,12,24,52]:
df["lag_{}".format(i)] = df.groupby('store_code')['y'].shift(i)
这样的形式也是可以的,因为我这里是周的销售预测,所以我比较关注,上一周的,上两周的,四周以前的,也就是一个月前,以及8周,就是两月前。
这里是周销售预测,所以更关注上一周/上两周/四周前/8周的历史数据。
以上生成滞后特征,于是就可以方便的计算同/环比。
需要注意的是,只能是用滞后的数据,不能基于y值生成特征,否则就是数据穿越了
df['huanbi_1_2'] = df['last_1_sale'] / df['last_2_sale']
df['last_14_2_sale'] = (df['last_14_sale'] - df['last_2_sale']) / df['last_14_sale']
df['sale_uplift'] = (data['last_1_sale'] - data['last_2_sale']) / data['last_2_sale']
1.5序列特征
不太好定义这类特征名称,姑且叫着序列特征。
当面对众多序列的时候,也可以统计时间序列中单个序列的特性,比如,周期性,平稳性,复杂性(eg.排序熵)等等单个时间序列本身具有的信息。
1.6高阶特征
若有更多的信息,我们是可以利用多个特征进行融合,比如,我有门店开业时长,平均营业额,门店销售方差等等,可以利用这些信息聚类。
理由是:把类别标签,作为一个特征,相同的类别,理应具有相似的曲线,具有相似特性的数据,生成相同的数据特征。
当然有读者肯定有疑问了,聚类是无监督学习,事先无法知道聚类的个数,这里建议使用一点经验值,或者使用聚类的评估指标,如果轮廓系数,得到一个较为可靠的类别数。
def store_cluster(data):
data_.drop_duplicates(subset=['store_code'],inplace=True)
data_un=data_[['store_code','shop_mean','open_length','std_7']].set_index(['store_code'])
data_un.fillna(0, inplace=True)
columns_to_normalize= ['shop_mean','open_length','std_7']
data_un[columns_to_normalize] = data_un[columns_to_normalize].apply(lambda x: (x - x.mean()) / np.std(x))
kmeans= KMeans(n_clusters=4).fit(data_un)
data_un['cluster_id']=kmeans.predict(data_un)
data_un=data_un.reset_index()
return data_un[['store_code','cluster_id']]
刚提到了曲线和趋势,那么,我们也是可以依据多条序列的波动,依据波动,找到相似的波动曲线,作为同一类标签,这就是基于时间序列的一个聚类方式。
比如 DTW方法
如果说KMeans聚类是一种静态的使用欧式距离计算的聚类方法,DTW方法就是一种对多个序列具有延展或者压缩特性的度量距离的方法,考虑序列形状的相似,常用的算法如k-shape。
除此之外还有傅里叶变换模块和函数如下:
from pykalman import KalmanFilter
当然对于多个类别数据,也可以使用embedding的方式。或者*TF-IDF,总的来说我这里列举的高阶特征,是利用多个特征进行融合,或者使用多条数据,求得相似性,减少信息冗余。当然也限于篇幅的原因,这部分非常重要的内容没有展开来讲,不过可以依据以上提到的关键词和资料很容易找到相关材料。
1.7外部特征
很多数据科学竞赛本身不提供外部数据,但鼓励选手通过技术手段获取并利用外部数据,比如天气,比如节假日,对气温这样的特征,做分桶处理见pd.cut函数。
2.好用的小技巧
2.1python调整DataFrame的数据类型,减少内存占用
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
def reduce_mem_usage(df):
""" iterate through all the columns of a dataframe and modify the data type
to reduce memory usage.
"""
start_mem = df.memory_usage().sum()
print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
for col in df.columns:
col_type = df[col].dtype
if col_type != object:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
df[col] = df[col].astype(np.int64)
else:
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
else:
df[col] = df[col].astype('category')
end_mem = df.memory_usage().sum()
print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
return df
sample_feature = reduce_mem_usage(pd.read_csv('data_for_tree.csv'))
2.2Prophet: 时间序列预测库
prophet是facebook开源的python预测库,该库的api设计与sklearn很像,也是分为fit方法和predict方法。
prophet库可以帮助我们进行
- Saturating Forecasts
- Trend Changepoints
- Seasonality, Holidays Effects
- Multiplicative Seasonality
- Uncertainty Intervals
- Outliers
- Non-Daily Data
- Diagnostics
传入prophet的数据分为两列 ds 和 y, 其中
- ds是pandas的日期格式,样式类似与 YYYY-MM-DDfora dateorYYYY-MM-DD HH:MM:SS;
- y列必须是数值型,代表着我们希望预测的值。
本文使用的是wiki网站日访问量(数值经过log处理)的csv数据文件。
2.3.1安装
pip3 install fbprophet
2.3.2导入数据
import pandas as pd
from fbprophet import Prophet
df = pd.read_csv('user_balance_table.csv',parse_dates=(['report_date']))
df.head()
2.3.3检查下df的数据类型
df.dtypes
ds列必须是pandas的datetime数据类型,我们使用pandas自带的pd.to_datetime将日期转为datetime类型。
2.3.4拟合数据
学习数据中的规律
prophet = Prophet()
prophet.fit(df)
2.3.5预测
生成一个未来的日期的dataframe,然后用训练好的模型prophet来predict。
生成未来日期的dateframe用到 Prophet.makefuturedataframe(periods)
future = prophet.make_future_dataframe(periods=365)
future.tail()
有了未来的日期,我们就可以使用学习到的趋势来预测未来日期的走势。
预测的结果包括如下变量
'ds', 'trend', 'yhat_lower', 'yhat_upper', 'trend_lower', 'trend_upper',
'additive_terms', 'additive_terms_lower', 'additive_terms_upper',
'weekly', 'weekly_lower', 'weekly_upper', 'yearly', 'yearly_lower',
'yearly_upper', 'multiplicative_terms', 'multiplicative_terms_lower',
'multiplicative_terms_upper', 'yhat'
我们只用 'ds','yhat','yhat_lower','yhat_upper'
forecast = prophet.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].head()
Prophet.plot方法可以帮助我们可视化
fig1 = prophet.plot(forecast)
print(fig1)
成分分析
趋势是由不同的成分组成,比如总趋势、年、季节、月、周等等,我们要将这些成分从趋势中抽取出来看看不同成分的趋势情况
fig2 = prophet.plot_components(forecast)
print(fig2)
prophet库的API相对简单,并且由于使用了pandas标准的dataframe和matplotlib来显示数据,因此很适合python数据科学工作流程。
3.数据探索步骤
- 查看数据内容 df.info() df.head()
- 检查数据有没有缺失项,存在需要给定填充值,常用的填充值有-1,1
- 选择部分数据进行可视化,查找数据之间的关系