Python时间序列预测
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 预测历史均值

如本章开头所述,我们将使用强生公司1960~1980年以美元(USD)计算的季度每股收益。我们的目标是使用1960~1979年底的数据来预测1980年的四个季度。我们将讨论的第一个基线使用历史均值,即过去数值的算术平均数。它的实现很简单:计算训练集的均值,这将是我们对1980年四个季度的预测。不过,首先,我们需要做一些准备工作,我们将在所有基线实现中使用这些准备工作。

2.2.1 基线实现准备

第一步是加载数据集。为此,我们将使用pandas库,并使用read_csv方法将数据集加载到DataFrame中。你可以将该文件下载到本地计算机上,并将该文件的路径传递给read_csv方法,或者只需输入托管在GitHub上的CSV文件的URL。在这种情况下,我们将使用文件:

本章的完整代码可以在GitHub上找到:https://github.com/marcopeix/TimeSeriesForecasting InPython/tree/master/CH02。

DataFrame是pandas中最常用的数据结构。它是一种二维标签数据结构,其中的列可以保存不同类型的数据,如字符串、整数、浮点数或日期。

第二步是将数据拆分为用于训练的训练集和用于测试的测试集。鉴于我们的预测期是1年,我们的训练集将从1960年开始,一直到1979年底。我们将为我们的测试集保存1980年收集的数据。你可以将DataFrame视为具有列名和行索引的表格或电子表格。

使用DataFrame中的数据集,我们可以通过运行以下命令来显示前5个条目:

这将为我们提供如图2.3所示的输出。

图2.3将帮助你更好地理解DataFrame所保存的数据类型。当计算EPS时,我们有日期列,它指定每个季度的结束。数据列以美元(USD)为单位保存EPS的值。

我们可以选择显示数据集的最后5个条目,并获得图2.4中的输出:

图2.3 强生公司数据集的季度每股收益的前5个条目。注意我们的DataFrame有两列:date和data。它还具有从0开始的行索引

图2.4 数据集的最后5个条目。在这里,我们可以看到1980年的四个季度,我们将尝试使用不同的基线模型进行预测。我们将把预测与1980年的观测数据进行比较,以评估每个基线的性能

在图2.4中,我们看到了1980年的四个季度,这是我们将试图使用基线模型预测的。我们将通过比较预测与1980年四个季度的数据栏中的值来评估基线的性能。预测与观测值越接近越好。

在开发我们的基线模型之前,最后一步是将数据集拆分为训练集和测试集。如前所述,训练集将由1960~1979年底的数据组成,而测试集将由1980年的四个季度组成。训练集将是我们用于开发模型的唯一信息。一旦建立了模型,我们将预测接下来的四个时间步长,这将对应于我们测试集中1980年的四个季度。这样,我们就可以将预测与观测数据进行比较,并评估基线的性能。

为了进行拆分,我们将指定训练集将包含保存在df中除最后4个条目之外的所有数据。测试集将仅由最后4个条目组成。这是由下面代码块来做的:

2.2.2 实现历史均值基线

现在我们准备实施基线。我们将首先使用整个训练集的算术平均数。为了计算均值,我们将使用numpy库,因为它是一个非常快速的Python科学计算包,可以很好地处理DataFrame:

在前面的代码块中,我们首先导入numPy库,然后计算整个训练集的EPS的均值,并将其打印在屏幕上。这给出了4.31美元的数值。这意味着1960~1979年底,强生公司的季度每股收益平均为4.31美元。

现在,我们将简单地预测1980年每个季度的这个值。为此,我们只需创建一个新列pred_mean,它将训练集的历史平均值作为预测:

接下来,我们需要定义并计算误差度量,以便评估预测在测试集上的性能。在这种情况下,我们将使用平均绝对百分比误差(Mean Absolute Percentage Error,MAPE)。它是一种预测方法的预测准确性的衡量标准,易于解释,并且独立于数据规模。这意味着,无论我们使用的是两位数的数值还是六位数的数值,MAPE都将始终以百分比表示。因此,MAPE返回预测值与观测值或实际值平均偏差的百分比,无论预测值高于观测值还是低于观测值。MAPE的定义见式2.1。

在式2.1中,Ai为第i时间点的实际值,Fi为第i时间点的预测值,n只是预测的数量。在我们的例子中,因为我们预测的是1980年的四个季度,所以n=4。在求和中,从实际值中减去预测值,然后将结果除以实际值,这就给出了百分比误差。然后我们取百分比误差的绝对值。对n个时间点中的每个时间点重复该操作,并将结果加在一起。最后,我们将总和除以n(即时间点的数量),这有效地给出了平均绝对百分比误差。

让我们用Python实现这个函数。我们将定义一个mape函数,该函数接受两个向量:测试集中观察到的实际值y_true和预测值y_pred。在这种情况下,因为numpy允许我们使用数组,所以我们不需要循环来对所有值求和。我们可以简单地从y_true数组中减去y_pred数组,然后除以y_true,以获得百分比误差,然后我们可以取绝对值。之后,我们求出这个结果的均值,即小心翼翼地对向量中的每个值求和,并除以预测的数量。最后,我们将结果乘以100,以便将输出表示为百分比而不是十进制数:

现在我们可以计算基线的MAPE。实际值在test数据列中,因此它将是传递给mape函数的第一个参数。预测值在test的pred_mean列中,因此它将是函数的第二个参数:

运行该函数得到的MAPE为70.00%。这意味着基线与强生公司1980年观察到的季度每股收益平均偏离70%。

让我们将预测可视化,以便更好地理解70%的MAPE,如清单2.1所示。

清单2.1 可视化我们的预测

在清单2.1中,我们使用matplotlib库(它是在Python中生成可视化的最流行的库)来生成一个图形,显示训练数据、预测范围、测试集的观测值以及1980年每个季度的预测。

第一,我们初始化一个figure和一个ax对象。一个图形可以包含许多ax对象,这允许我们在一个图像上可以创建两个、三个或更多图像。在这种情况下,我们创建单个图像的图形,因此我们只需要一个ax。

第二,我们在ax对象上绘制数据。我们使用点划线绘制训练数据,并将此曲线标记为“训练”。该标签稍后将用于生成图表的图例。然后我们使用连续线绘制测试数据,标记为“测试”。最后,我们使用虚线绘制预测结果,标记为“预测”。

第三,我们标记x轴和y轴,并绘制一个矩形区域来说明预测范围。由于我们的预测范围是1980年的四个季度,因此该区域从索引80开始,到索引83结束,跨越整个1980年。请记住,我们通过运行df.tail()获得了1980年最后一个季度的指数,结果如图2.5所示。

图2.5 数据集的最后5个条目

我们将此区域设置为灰色,并使用alpha参数指定不透明度。当alpha为1时,形状完全不透明;当alpha为0时,形状完全透明。在我们的例子中,我们将使用20%(或0.2)的不透明度。

然后,我们为x轴上的记号指定标签。默认情况下,标签将显示数据集的每个季度的数据,这将创建一个带有无法读取的标签的拥挤的x轴。相反,我们将每两年显示一次年份。为此,我们将生成一个数组,指定标签必须出现的索引。这就是np.arange(0, 81, 8)所做的:它生成一个从0开始,到80结束的数组,因为不包括结束索引(81);步长为8,因为2年中有8个季度。这将有效地生成以下数组:[0,8,16,…,72,80]。然后,我们指定一个包含每个索引处的标签的数组,因此它必须以1960开始,以1980结束,就像我们的数据集一样。

最后,我们使用fig.automft_xdate()来自动格式化x轴上的刻度线标签。它将稍微旋转它们,并确保它们清晰可辨。最后的修饰是使用plt.tight_layout()来删除图形周围任何多余的空白。

最终结果见图2.6。显然,该基线没有生成准确的预测,因为预测线离测试线非常远。现在我们知道,预测平均比1980年每个季度的实际每股收益低70%。1980年的每股收益一直高于10美元,而我们预测每个季度的每股收益仅为4.31美元。

尽管如此,我们可以从中可以学到什么呢?查看训练集,我们可以看到一个积极的趋势,因为EPS随着时间的推移而增加。如图2.7所示,来自数据集分解的趋势分量进一步支持了这一点。

正如你所看到的,我们不仅有一个趋势,而且这个趋势在1960~1980年并不是恒定的——它变得越来越陡峭。因此,1960年观察到的每股收益可能不能预测1980年的每股收益,因为我们有一个积极的趋势,每股收益值随着时间的推移而增加,并且以更快的速度增加。

你能改进我们的基线吗

在进入2.3节之前,你能想出一种方法(同时仍然使用均值)来改进我们的基线吗?你认为取一个更短更近的时间段的均值会有帮助吗(例如,1970~1979年)?

图2.6 预测历史均值作为基线。你可以看到,预测与测试集中的实际值相差甚远。该基线给出的MAPE为70%

图2.7 时间序列的趋势分量。你可以看到数据有一个积极的趋势,因为它随着时间的推移而增加