在许多应用中,时间序列集合是按层级组织的。例如,存在地理级别、产品或类别定义了不同类型的聚合。在这种场景下,预测人员通常需要为所有分解序列和聚合序列提供预测。一个自然的要求是这些预测必须是“一致的”,也就是说,最低层序列的预测总和必须与聚合序列的预测精确相等。

在本笔记本中,我们将展示一个示例,说明如何使用 HierarchicalForecast 在地理级别之间生成一致的预测。我们将使用澳大利亚监狱人口数据集。

我们将首先加载数据集,并使用 StatsForecast 中的 ETS 模型生成基础预测,然后使用 HierarchicalForecast 中的几种协调算法对预测进行协调。最后,我们展示其性能与Forecasting: Principles and Practice一书报告的结果相当,该书使用了 R 包 fable

您可以使用 Google Colab 通过 CPU 或 GPU 运行这些实验。

!pip install hierarchicalforecast statsforecast

1. 加载并处理数据

数据集只包含最低层的时间序列,因此我们需要为所有层级创建时间序列。

import numpy as np
import pandas as pd
Y_df = pd.read_csv('https://OTexts.com/fpp3/extrafiles/prison_population.csv')
Y_df = Y_df.rename({'Count': 'y', 'Date': 'ds'}, axis=1)
Y_df.insert(0, 'Country', 'Australia')
Y_df = Y_df[['Country', 'State', 'Gender', 'Legal', 'Indigenous', 'ds', 'y']]
Y_df['ds'] = pd.to_datetime(Y_df['ds']) + pd.DateOffset(months=1)
Y_df.head()
国家性别法律状态原住民dsy
0澳大利亚ACT女性还押ATSI2005-04-010
1澳大利亚ACT女性还押非 ATSI2005-04-012
2澳大利亚ACT女性已判刑ATSI2005-04-010
3澳大利亚ACT女性已判刑非 ATSI2005-04-015
4澳大利亚ACT男性还押ATSI2005-04-017

数据集可以按照以下分组结构进行分组。

hiers = [
    ['Country'],
    ['Country', 'State'], 
    ['Country', 'Gender'], 
    ['Country', 'Legal'], 
    ['Country', 'State', 'Gender', 'Legal']
]

使用 HierarchicalForecast 中的 aggregate 函数,我们可以获得完整的时间序列集合。

from hierarchicalforecast.utils import aggregate
Y_df, S_df, tags = aggregate(Y_df, hiers)
Y_df['y'] = Y_df['y']/1e3
Y_df.head()
unique_iddsy
0澳大利亚2005-04-0124.296
1澳大利亚2005-07-0124.643
2澳大利亚2005-10-0124.511
3澳大利亚2006-01-0124.393
4澳大利亚2006-04-0124.524
S_df.iloc[:5, :5]
unique_idAustralia/ACT/Female/RemandedAustralia/ACT/Female/SentencedAustralia/ACT/Male/RemandedAustralia/ACT/Male/Sentenced
0澳大利亚1.01.01.01.0
1Australia/ACT1.01.01.01.0
2Australia/NSW0.00.00.00.0
3Australia/NT0.00.00.00.0
4Australia/QLD0.00.00.00.0
tags
{'Country': array(['Australia'], dtype=object),
 'Country/State': array(['Australia/ACT', 'Australia/NSW', 'Australia/NT', 'Australia/QLD',
        'Australia/SA', 'Australia/TAS', 'Australia/VIC', 'Australia/WA'],
       dtype=object),
 'Country/Gender': array(['Australia/Female', 'Australia/Male'], dtype=object),
 'Country/Legal': array(['Australia/Remanded', 'Australia/Sentenced'], dtype=object),
 'Country/State/Gender/Legal': array(['Australia/ACT/Female/Remanded', 'Australia/ACT/Female/Sentenced',
        'Australia/ACT/Male/Remanded', 'Australia/ACT/Male/Sentenced',
        'Australia/NSW/Female/Remanded', 'Australia/NSW/Female/Sentenced',
        'Australia/NSW/Male/Remanded', 'Australia/NSW/Male/Sentenced',
        'Australia/NT/Female/Remanded', 'Australia/NT/Female/Sentenced',
        'Australia/NT/Male/Remanded', 'Australia/NT/Male/Sentenced',
        'Australia/QLD/Female/Remanded', 'Australia/QLD/Female/Sentenced',
        'Australia/QLD/Male/Remanded', 'Australia/QLD/Male/Sentenced',
        'Australia/SA/Female/Remanded', 'Australia/SA/Female/Sentenced',
        'Australia/SA/Male/Remanded', 'Australia/SA/Male/Sentenced',
        'Australia/TAS/Female/Remanded', 'Australia/TAS/Female/Sentenced',
        'Australia/TAS/Male/Remanded', 'Australia/TAS/Male/Sentenced',
        'Australia/VIC/Female/Remanded', 'Australia/VIC/Female/Sentenced',
        'Australia/VIC/Male/Remanded', 'Australia/VIC/Male/Sentenced',
        'Australia/WA/Female/Remanded', 'Australia/WA/Female/Sentenced',
        'Australia/WA/Male/Remanded', 'Australia/WA/Male/Sentenced'],
       dtype=object)}

划分训练/测试集

我们使用最后两年(8个季度)作为测试集。

Y_test_df = Y_df.groupby('unique_id', as_index=False).tail(8)
Y_train_df = Y_df.drop(Y_test_df.index)

2. 计算基础预测

以下单元格使用 ETS 模型计算 Y_df 中每个时间序列的基础预测。请注意,Y_hat_df 包含预测结果,但它们不一致。

from statsforecast.models import AutoETS
from statsforecast.core import StatsForecast
fcst = StatsForecast(models=[AutoETS(season_length=4, model='ZMZ')], 
                     freq='QS', n_jobs=-1)
Y_hat_df = fcst.forecast(df=Y_train_df, h=8, fitted=True)
Y_fitted_df = fcst.forecast_fitted_values()
Y_test_df
unique_iddsy
40澳大利亚2015-04-0135.271
41澳大利亚2015-07-0135.921
42澳大利亚2015-10-0136.067
43澳大利亚2016-01-0136.983
44澳大利亚2016-04-0137.830
2155Australia/WA/Male/Sentenced2016-01-013.894
2156Australia/WA/Male/Sentenced2016-04-013.876
2157Australia/WA/Male/Sentenced2016-07-013.969
2158Australia/WA/Male/Sentenced2016-10-014.076
2159Australia/WA/Male/Sentenced2017-01-014.088
Y_train_df
unique_iddsy
0澳大利亚2005-04-0124.296
1澳大利亚2005-07-0124.643
2澳大利亚2005-10-0124.511
3澳大利亚2006-01-0124.393
4澳大利亚2006-04-0124.524
2147Australia/WA/Male/Sentenced2014-01-013.614
2148Australia/WA/Male/Sentenced2014-04-013.635
2149Australia/WA/Male/Sentenced2014-07-013.692
2150Australia/WA/Male/Sentenced2014-10-013.726
2151Australia/WA/Male/Sentenced2015-01-013.780

3. 协调预测

以下单元格使用 HierarchicalReconciliation 类使之前的预测保持一致。由于层级结构并非严格,我们无法使用诸如 TopDownMiddleOut 之类的方法。在本例中,我们使用 BottomUpMinTrace

from hierarchicalforecast.methods import BottomUp, MinTrace
from hierarchicalforecast.core import HierarchicalReconciliation
reconcilers = [
    BottomUp(),
    MinTrace(method='mint_shrink')
]
hrec = HierarchicalReconciliation(reconcilers=reconcilers)
Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, S=S_df, tags=tags)

数据框 Y_rec_df 包含协调后的预测。

Y_rec_df.head()
unique_iddsAutoETSAutoETS/BottomUpAutoETS/MinTrace_method-mint_shrink
0澳大利亚2015-04-0134.79949734.94647634.923548
1澳大利亚2015-07-0135.19263835.41034235.432421
2澳大利亚2015-10-0135.18821635.58084935.473386
3澳大利亚2016-01-0135.88862835.95187835.939526
4澳大利亚2016-04-0136.04543736.41682936.245158

4. 评估

HierarchicalForecast 包包含 HierarchicalEvaluation 类,用于评估不同的层级,并且能够计算与基准模型相比的缩放指标。

from hierarchicalforecast.evaluation import evaluate
from utilsforecast.losses import mase
from functools import partial
eval_tags = {}
eval_tags['Total'] = tags['Country']
eval_tags['State'] = tags['Country/State']
eval_tags['Legal status'] = tags['Country/Legal']
eval_tags['Gender'] = tags['Country/Gender']
eval_tags['Bottom'] = tags['Country/State/Gender/Legal']

df = Y_rec_df.merge(Y_test_df, on=['unique_id', 'ds'])
evaluation = evaluate(df = df,
                      tags = eval_tags,
                      train_df = Y_train_df,
                      metrics = [partial(mase, seasonality=4)])

numeric_cols = evaluation.select_dtypes(include="number").columns
evaluation[numeric_cols] = evaluation[numeric_cols].map('{:.2f}'.format).astype(np.float64)
evaluation.rename(columns={'AutoETS': 'Base'}, inplace=True)
evaluation
级别指标基础AutoETS/BottomUpAutoETS/MinTrace_method-mint_shrink
0总计mase1.361.071.17
1mase1.531.551.59
2法律状态mase2.402.482.38
3性别mase1.080.820.93
4最低层mase2.162.162.14
5总体mase1.991.981.98

与 Fable 比较

请注意,我们可以重现由 Forecasting: Principles and Practice 一书报告的结果。原始结果是使用 R 包 fable 计算的。

参考文献