表 1:带有月份假人的数据帧。
首先,我们从 DatetimeIndex 中提取有关月份的信息(编码为 1 到 12 范围内的整数)。然后,我们使用pd.get_dummies函数来创建虚拟变量。每列都包含有关观测值(行)是否来自给定月份的信息。
您可能已经注意到,我们已经降低了一个级别,现在只有11列。我们这样做是为了避免在使用线性模型时可能出现的臭名昭著的虚拟变量陷阱(完美的多重共线性)问题。
在我们的示例中,我们使用虚拟变量方法来获取观测值的月份。其实也可以使用相同的方法获取来自 DatetimeIndex 的一系列其他信息。例如,一年中的日/周/季度,给定一天是否为周末的标志,一个周期的第一天/最后一天等等。可以找到一个列表,其中包含所有可能的从pandas文档索引中提取的功能,可在 pandas.pydata.org找到。
额外提示:以下已经不属于简单练习的范围了,但在现实生活中,我们还可以使用有关特殊日子的信息(想想国定假日,圣诞节,黑色星期五等)来创建功能。holidays是一个不错的Python库,包含每个国家/地区特殊日子的信息,无论过去和未来。
如简介中所述,特征工程的目标是将复杂性从模型转移到特征集。这就是为什么我们将使用最简单的ML模型之一 -线性回归 – 展示一下拟合时间序列的程度,在我们仅使用创建的虚拟数据下。
model_1 = LinearRegression().fit(X_1.iloc[:TRAIN_END], y.iloc[:TRAIN_END])
results_df["模型_1"] = model_1.predict(X_1)
results_df[["actuals","model_1"]].plot(figsize=(16,4),title="用月虚拟变量拟合")
plt.axvline(date(2020, 1, 1), c="m", linestyle="--");
图 2:使用月份假人进行拟合。垂直线将训练集和测试集分开。
我们可以看到,拟合线已经很好地遵循了时间序列,尽管它有点锯齿状(类似阶梯) - 这是由虚拟特征的不连续性引起的。因此我们将尝试通过接下来的两种方法解决此问题。
但在继续之前,值得一提的是,当使用非线性模型(例如决策树(或其集合))时,别将诸如月份,或一年中的某天等特征显式编码设为随机数。这些模型能够学习序数输入特征与目标之间的非单调关系。
方法#2:具有正弦/余弦变换的循环编码正如我们前面所看到的,拟合的线类似于步骤。这是因为每项虚拟数据都是单独处理的,没有连续性。然而,例如时间等变量存在明显的周期连续性。这意味着什么呢?
想象一下,我们正在处理购买者的数据。当我们纳入观察到的购买者消费月份的信息时,如果连续两个月之间存在更强的联系,是有道理的。按照这个逻辑,12月和1月之间以及1月和2月之间的联系很强。相比之下,1月和7月之间的联系就并不那么紧密。这道理同样适用于其他与时间相关的信息。
那么,我们如何将这些知识融入特征工程中呢?三角函数啊。我们可以使用以下正弦/余弦变换将循环时间特征编码为两个特征。
def sin_transformer(period):
return FunctionTransformer(lambda x: np.sin(x / period * 2 * np.pi))
def cos_transformer(period):
return FunctionTransformer(lambda x: np.cos(x / period * 2 * np.pi))
在下面的代码片段中,我们复制初始 DataFrame,添加带有月份数字的列,然后 使用正弦/余弦变换对月份和day_of_year两项进行编码。然后,我们绘制两对曲线。
X_2 = X.copy()
X_2["月"] = X_2.index.month
X_2["月_sin"] = sin_transformer(12).fit_transform(X_2)[" 月"]
X_2["月_cos"] = cos_transformer(12).fit_transform(X_2)[" 月"]
X_2["日_sin"] = sin_transformer(365).fit_transform(X_2)["每年的日"]
X_2["日_cos"] = cos_transformer(365).fit_transform(X_2)[" 每年的日"]
fig, ax = plt.subplots(2, 1, sharex=True, figsize=(16,8))
X_2[["月_sin", "月_cos"]].plot(ax=ax[0])
X_2[["日_sin", "日_cos"]].plot(ax=ax[1])
plt.suptitle("用正余弦变换循环编码");
图 3:基于每月和每日频率的正弦/余弦变换。
如图 3 所示,我们可以从转换后的数据中得出两个知识。首先,我们可以很容易地看到,当使用月份进行编码时,曲线是阶跃的,但是当使用每日频率时,曲线要平滑得多;其次,我们也可以理解为什么我们必须使用两条曲线而不是一条曲线。由于曲线的重复性,如果在绘图中绘制一条单年水平直线,则会在两个地方穿过曲线。这还不足以让模型了解观测值的时间点。但是有了这两条曲线,就没有这样的问题,用户可以识别出每一个时间点。让大家看得更明白点,我们在散点图上绘制正弦/余弦函数的值。在图 4 中,我们可以看到一个圆的模式,没有重叠。
图 4:正弦和余弦变换的散点图。
让我们仅使用来自每日频率的新创建要素来拟合相同的线性回归模型。
X_2_daily = X_2[["day_sin", "day_cos"]]
model_2 = LinearRegression().fit(X_2_daily.iloc[:TRAIN_END],y.iloc[:TRAIN_END])
results_df["model_2"] = model_2.predict(X_2_daily)
results_df[["actuals", "model_2"]].plot(figsize=(16,4), title="使用正弦/余弦特征拟合")
plt.axvline(date(2020, 1, 1), c="m", linestyle="--");