在这组笔记本中,我们将介绍使用外生变量进行建模。我们的行动计划如下:
对数据集进行探索性数据分析,以提取关于生成时间序列的过程的有价值的见解。
构建一个基准模型(不包含外生变量的单变量模型)用于基准测试。
构建一个包含所有外生变量的单变量模型,以检查最佳性能。
评估带有外生变量的模型,并讨论任何潜在问题。
克服上述识别出的问题。
使用最佳模型进行未来预测。
使用自动化时间序列建模(AutoML)复制流程。
# 导入os模块
import os
# 设置环境变量PYCARET_CUSTOM_LOGGING_LEVEL为"CRITICAL",仅启用关键日志记录(可选)
os.environ["PYCARET_CUSTOM_LOGGING_LEVEL"] = "CRITICAL"
# 定义一个函数what_is_installed,用于检查pycaret库是否已安装
def what_is_installed():
# 导入pycaret库中的show_versions函数
from pycaret import show_versions
# 调用show_versions函数,显示pycaret库的版本信息
show_versions()
try:
# 尝试调用what_is_installed函数
what_is_installed()
except ModuleNotFoundError:
# 如果出现ModuleNotFoundError异常,说明pycaret库未安装
# 使用pip命令安装pycaret库
!pip install pycaret
# 再次调用what_is_installed函数,确认pycaret库已安装成功
what_is_installed()
System:
python: 3.9.16 (main, Jan 11 2023, 16:16:36) [MSC v.1916 64 bit (AMD64)]
executable: C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_16p1\python.exe
machine: Windows-10-10.0.19044-SP0
PyCaret required dependencies:
pip: 22.3.1
setuptools: 65.6.3
pycaret: 3.0.0rc9
IPython: 8.10.0
ipywidgets: 8.0.4
tqdm: 4.64.1
numpy: 1.23.5
pandas: 1.5.3
jinja2: 3.1.2
scipy: 1.10.0
joblib: 1.2.0
sklearn: 1.2.1
pyod: 1.0.8
imblearn: 0.10.1
category_encoders: 2.6.0
lightgbm: 3.3.5
numba: 0.56.4
requests: 2.28.2
matplotlib: 3.7.0
scikitplot: 0.3.7
yellowbrick: 1.5
plotly: 5.13.0
kaleido: 0.2.1
statsmodels: 0.13.5
sktime: 0.16.1
tbats: 1.1.2
pmdarima: 2.0.2
psutil: 5.9.4
PyCaret optional dependencies:
shap: 0.41.0
interpret: Not installed
umap: Not installed
pandas_profiling: Not installed
explainerdashboard: Not installed
autoviz: Not installed
fairlearn: Not installed
xgboost: Not installed
catboost: Not installed
kmodes: Not installed
mlxtend: Not installed
statsforecast: Not installed
tune_sklearn: Not installed
ray: Not installed
hyperopt: Not installed
optuna: Not installed
skopt: Not installed
mlflow: 2.1.1
gradio: Not installed
fastapi: Not installed
uvicorn: Not installed
m2cgen: Not installed
evidently: Not installed
fugue: 0.8.0
streamlit: Not installed
prophet: 1.1.2
# 导入所需的库
import numpy as np
import pandas as pd
from pycaret.datasets import get_data
from pycaret.time_series import TSForecastingExperiment
# 全局图形设置用于笔记本 ----
# 根据您使用的是jupyter notebook、jupyter lab还是Google Colab,您可能需要适当设置渲染器
# 注意:在这里设置为静态渲染器,以减小笔记本的保存大小。
global_fig_settings = {
# "renderer": "notebook",
"renderer": "png", # 设置渲染器为png格式
"width": 1000, # 设置图形宽度为1000
"height": 600, # 设置图形高度为600
}
# 调用get_data函数,获取名为"airquality"的数据,并将结果赋值给变量data
data = get_data("airquality")
# 将"Date"和"Time"两列的数据合并为一个新的列"index",并将其转换为日期时间格式
data["index"] = pd.to_datetime(data["Date"] + " " + data["Time"])
# 删除原始数据中的"Date"和"Time"两列
data.drop(columns=["Date", "Time"], inplace=True)
# 定义目标变量为"CO(GT)"
target = "CO(GT)"
数据集中的缺失值被标记为-200。参考。我们应该删除这些值(用NaN替换),并让pycaret
适当处理插补(防止训练过程中的数据泄漏)。
# 查找目标列中值为-200的行,并显示前几行
data[data[target] == -200].head()
# 将data中的-200替换为np.nan(缺失值),并直接在原地进行替换
data.replace(-200, np.nan, inplace=True)
# 选取data中目标变量(target)为-200的行
data[data[target] == -200]
现在,让我们继续进行探索性数据分析(EDA)和建模。
# 创建一个时间序列预测实验对象
eda = TSForecastingExperiment()
# 设置EDA的参数
eda.setup(
data=data, # 数据集
target=target, # 目标变量
index="index", # 索引列
fh=48, # 预测未来的时间步长
numeric_imputation_target="ffill", # 数值型目标变量的缺失值填充方法
numeric_imputation_exogenous="ffill", # 数值型外生变量的缺失值填充方法
fig_kwargs=global_fig_settings, # 图形参数设置
session_id=42, # 设置随机种子
)
<pycaret.time_series.forecasting.oop.TSForecastingExperiment at 0x203109abac0>
即使在继续之前,我们可以在这里观察到一些有用的信息。
# 绘制目标变量和外生变量的图形
# 由于数据量很大,绘制交互式图形可能会使笔记本变慢。
# 因此,我们将在此图形中使用静态渲染器
eda.plot_model(fig_kwargs={"renderer": "png", "width": 1000, "height": 1200})
# 绘制目标变量和外生变量的图形
# 由于数据量很大,绘制交互式图形可能会使笔记本变慢。
# 因此,我们将在此图形中使用静态渲染器
eda.plot_model(fig_kwargs={"renderer": "png", "width": 1000, "height": 1200})
虽然在缩小的图中可能很难看到细节,但是pycaret提供了使用交互式plotly图表放大的功能。放大后,可以清楚地看到一个24小时的循环,高峰出现在19:00和8:00左右。
另外,看起来NMHC(GT)
列中有缺失值。对于我们将来开发的任何多变量模型,我们将删除这一列。
接下来,设置建议对数据进行差分处理。让我们看看差分后的数据是什么样子,以及是否有必要进行差分处理。除了差分后的数据,我们还将绘制一些诊断图,如ACF、PACF和周期图。虽然许多读者可能已经了解ACF和PACF,但对于周期图可能需要更多的解释。周期图是一个根据频率绘制时间序列的谱密度的图。在这种情况下,频率范围从0到0.5(测量频率所需的最小点数为2,对应于最大频率0.5)。不同频率上的幅度可以用来推导时间序列的重要特征。我们将在下面看到这一点。
exclude = ["NMHC(GT)"]
# 绘制原始数据和一阶差分数据的图表,默认为一阶差分
# 注意:取消注释display_format以使用“plotly-widget”。
eda.plot_model(
plot="diff", # 绘制一阶差分图表
fig_kwargs={
"height": 900, # 图表高度为900
# 使用plotly-widget时不需要传递show_dash参数
"resampler_kwargs": {"default_n_shown_samples": 1500} # 重采样参数,显示1500个样本
},
data_kwargs={"acf": True, "pacf": True, "periodogram": True}, # 数据参数,包括自相关函数、偏自相关函数和周期图
# display_format="plotly-widget", # 使用plotly-widget显示格式,取消注释以启用
)
图表分析:
一阶差分
注意:lags = 1与order = 1相同,意味着进行一阶差分,而lags = [1, 24]意味着先进行一阶差分,然后再进行24的季节性差分。
# 使用plot_model函数进行绘图
eda.plot_model(
# 绘图类型为diff
plot="diff",
# 设置绘图的参数
fig_kwargs={
# 设置绘图的高度为900
"height": 900,
# 设置重采样的参数
"resampler_kwargs": {
# 设置默认显示的样本数为1500
"default_n_shown_samples": 1500,
# 设置显示的模式为inline,端口号为8056
"show_dash": {"mode": "inline", "port": 8056},
},
},
# 设置数据的参数
data_kwargs={
# 设置滞后列表为[1, [1, 24]]
"lags_list": [1, [1, 24]],
# 设置是否显示自相关函数图像为True
"acf": True,
# 设置是否显示偏自相关函数图像为True
"pacf": True,
# 设置是否显示周期图像为True
"periodogram": True,
},
# 设置显示格式为plotly-dash
# display_format='plotly-dash',
)
我们将把注意力集中在第三行,因为前两行是最后一个图的重复。第三行表示一阶差分,后面是一个周期为24的季节性差分。从自相关图(第3行,第2列)可以看出,所有的扩展自相关已经得到了处理。我们仍然可以看到在滞后=24时有一个峰值(现在是负的),但是仔细检查周期图后,我们意识到泄漏的谱密度非常低。因此,我们可以得出结论,采用一阶差分和周期为24的季节性差分可以相当合理地建模这个时间序列。然而
注意:读者可以选择进一步研究以确定AR和MA分量。例如,偏自相关图(第3行,第3列)显示在滞后=2时有一个负峰(显著),进一步表明至少有一个二阶AR分量。为了简单起见,我们在这个练习中不使用它,但是它肯定应该被探索以进行更完整的评估。
接下来,让我们探索外生变量对目标的影响以及它们之间的相关性。这可以通过交叉相关图来实现。
# 使用plot_model函数绘制ccf图
# 参数plot指定绘制的图类型为ccf
# 参数fig_kwargs用于设置图的大小,这里设置高度为1000,宽度为1500
eda.plot_model(plot="ccf", fig_kwargs={"height": 1000, "width": 1500})
第一个图(第1行,第1列)显示了目标变量的自相关性(与其ACF相同),而其余的图显示了目标变量与滞后值的外生变量的相关性。例如,第1行,第2列显示CO浓度与滞后0和滞后24的PT08.S1(CO)
强相关。其他变量(如NOx(GT)
,C6H6(GT)
,PT08.S2(NMHC)
)也呈现类似的模式,这些变量在建模CO浓度时可能会有用。
另一个观察结果是绝对湿度AH
似乎与CO浓度没有很强的相关性。这个变量在建模中可能不重要。
# 将字符串"AH"添加到exclude列表中,表示需要排除的元素是"AH"
exclude.append("AH")
exclude
['NMHC(GT)', 'AH']
# 将数据复制一份并命名为data_uni
data_uni = data.copy()
# 将data_uni的索引设置为"index"
data_uni.set_index("index", inplace=True)
# 将data_uni的目标列设置为target
data_uni = data_uni[target]
# 创建一个时间序列预测实验对象exp_uni
exp_uni = TSForecastingExperiment()
# 设置exp_uni的参数
exp_uni.setup(
data=data_uni, # 使用data_uni作为数据
fh=48, # 预测未来48个时间步长
numeric_imputation_target="ffill", # 使用前向填充法对目标列进行数值插补
numeric_imputation_exogenous="ffill", # 使用前向填充法对外部变量进行数值插补
fig_kwargs=global_fig_settings, # 设置图形参数为global_fig_settings
session_id=42 # 设置会话ID为42
)
<pycaret.time_series.forecasting.oop.TSForecastingExperiment at 0x1d7cd3dac20>
# 创建一个ARIMA模型
# 参数说明:
# - "arima":表示使用ARIMA模型
# - order=(0,1,0):表示ARIMA模型的阶数,这里是(0,1,0),即p=0, d=1, q=0
# - seasonal_order=(0,1,0,24):表示ARIMA模型的季节性阶数,这里是(0,1,0,24),即P=0, D=1, Q=0, S=24
model = exp_uni.create_model("arima", order=(0,1,0), seasonal_order=(0,1,0,24))
Processing: 0%| | 0/4 [00:00<?, ?it/s]
# 调用plot_model函数,传入model参数
exp_uni.plot_model(model)
# 该代码用于绘制模型的图形化表示
# exp_uni模块中的plot_model函数可以将给定的模型绘制成图形化的形式
# model参数是要绘制的模型对象
# 通过调用exp_uni模块中的plot_model函数,可以方便地查看模型的结构和层次关系
在放大预测结果后,我们可以看到模型能够捕捉到数据集中的一些趋势(峰值),但并非全部。基准模型的表现表明,交叉验证折叠中的平均MASE为1.52,这并不是很好的结果。任何大于1的值都表明该模型的表现甚至比一个简单的一步预测的天真模型还要差。这个模型需要进一步改进。让我们看看是否添加外部变量可以帮助改善模型的性能。
# 创建一个时间序列预测实验对象
exp_exo = TSForecastingExperiment()
# 设置实验参数
exp_exo.setup(
data=data, # 数据集
target=target, # 目标变量
index="index", # 时间索引列名
fh=48, # 预测的未来时间步数
numeric_imputation_target="ffill", # 目标变量的数值缺失值填充方法
numeric_imputation_exogenous="ffill", # 外部变量的数值缺失值填充方法
fig_kwargs=global_fig_settings, # 图形参数设置
session_id=42 # 实验的会话ID
)
<pycaret.time_series.forecasting.oop.TSForecastingExperiment at 0x1d7d7a938b0>
# 创建一个ARIMA模型
# 参数说明:
# - "arima":表示使用ARIMA模型
# - order=(0,1,0):表示ARIMA模型的阶数,这里是(0,1,0),即p=0, d=1, q=0
# - seasonal_order=(0,1,0,24):表示季节性ARIMA模型的阶数,这里是(0,1,0,24),即P=0, D=1, Q=0, S=24
model_exo = exp_exo.create_model("arima", order=(0,1,0), seasonal_order=(0,1,0,24))
Processing: 0%| | 0/4 [00:00<?, ?it/s]
# 调用plot_model函数,并传入model_exo作为参数
exp_exo.plot_model(model_exo)
不错,我们成功地显著改进了MASE,这比单变量模型要好得多,也比朴素模型有了很大的改进。我们应该对这个改进感到满意。让我们通过在整个数据集上训练模型来最终确定它,这样我们就可以进行真正的未来预测了。
# 将模型训练结果进行最终处理
final_model_exo = exp_exo.finalize_model(model_exo)
# 定义一个函数safe_predict,用于对模型进行预测的包装器,以便于演示目的。
def safe_predict(exp, model):
try:
exp.predict_model(model) # 调用exp对象的predict_model方法进行模型预测
except ValueError as exception: # 如果出现ValueError异常
print(exception) # 打印异常信息
exo_vars = exp.exogenous_variables # 获取exp对象的外生变量
print(f"{len(exo_vars)} exogenous variables (X) needed in order to make future predictions:\n{exo_vars}") # 打印需要外生变量的数量和具体的外生变量名称
# 调用safe_predict函数,传入exp_exo和final_model_exo作为参数
safe_predict(exp_exo, final_model_exo)
Model was trained with exogenous variables but you have not passed any for predictions. Please pass exogenous variables to make predictions.
10 exogenous variables (X) needed in order to make future predictions:
['PT08.S1(CO)', 'C6H6(GT)', 'PT08.S2(NMHC)', 'NOx(GT)', 'PT08.S3(NOx)', 'NO2(GT)', 'PT08.S4(NO2)', 'PT08.S5(O3)', 'T', 'RH']
正如我们所看到的,这种方法并非没有副作用。问题在于我们有10个外生变量。因此,为了获得CO浓度的任何未知未来值,我们将需要所有这些外生变量的未来值。通常,这是通过某种预测过程本身来获得的。但是每个预测都会有误差,当有很多外生变量时,这些误差可能会累积。让我们看看是否可以减少这些外生变量,只保留一些有用的变量,而不会影响预测性能。
从CCF分析中,我们发现许多外生变量与CO浓度具有非常相似的相关结构。例如,24小时前(滞后=24)的PT08.S1(CO)
、NOx(GT)
、C6H6(GT)
、PT08.S2(NMHC)
的值与CO浓度呈高正相关。我们不需要保留所有这些变量,只需选择在滞后24时具有最高正相关性的变量,即NOx(GT)
。
同样,24小时前的PT08.S3(NOx)
值与CO浓度呈最高负相关。我们也会保留这个变量。
最后,在每日周期中,过去12小时发生的事情也会影响当前值(例如,昨晚的值可能会影响到第二天,反之亦然)。在滞后=12时,与CO浓度相关性最高的变量是RH
。我们也会保留这个变量。
# 创建一个TSForecastingExperiment对象
exp_slim = TSForecastingExperiment()
# 选择需要保留的特征列
keep = [target, "index", 'NOx(GT)', "PT08.S3(NOx)", "RH"]
# 从原始数据中提取需要保留的特征列
data_slim = data[keep]
# 设置实验参数
exp_slim.setup(
data=data_slim, # 数据集
target=target, # 目标变量
index="index", # 索引列
fh=48, # 预测步长
numeric_imputation_target="ffill", # 目标变量的数值填充方法
numeric_imputation_exogenous="ffill", # 外生变量的数值填充方法
fig_kwargs=global_fig_settings, # 图形参数
session_id=42 # 随机种子
)
<pycaret.time_series.forecasting.oop.TSForecastingExperiment at 0x1d7d799d300>
# 使用exp_slim库的create_model函数创建一个ARIMA模型
# 参数说明:
# - "arima":表示使用ARIMA模型
# - order=(0,1,0):表示ARIMA模型的阶数,这里是(0,1,0),即p=0, d=1, q=0
# - seasonal_order=(0,1,0,24):表示ARIMA模型的季节性阶数,这里是(0,1,0,24),即P=0, D=1, Q=0, S=24
model_slim = exp_slim.create_model("arima", order=(0,1,0), seasonal_order=(0,1,0,24))
Processing: 0%| | 0/4 [00:00<?, ?it/s]
# 调用plot_model函数,将model_slim作为参数传入
exp_slim.plot_model(model_slim)
不错。MASE只有轻微增加,但我们成功地大幅减少了外生变量。这将有助于我们在进行“真正”的未知未来预测时,因为我们将需要这些外生变量的“未知”未来值来进行CO浓度的预测。
# 将经过剪枝的模型进行最终的模型定型
final_slim_model = exp_slim.finalize_model(model_slim)
# 调用exp_slim模块中的save_model函数,将final_slim_model保存到指定路径
_ = exp_slim.save_model(final_slim_model, "final_slim_model")
Transformation Pipeline and Model Successfully Saved
safe_predict(exp_slim, final_slim_model)
Model was trained with exogenous variables but you have not passed any for predictions. Please pass exogenous variables to make predictions.
3 exogenous variables (X) needed in order to make future predictions:
['NOx(GT)', 'PT08.S3(NOx)', 'RH']
所以我们仍然需要3个外生变量的未来值。我们将在下一部分使用预测技术来获取这些值。
# 定义一个列表,包含需要使用的外生变量
exog_vars = ['NOx(GT)', 'PT08.S3(NOx)', 'RH']
# 从数据中选择需要的列,并将其存储在一个新的数据框中
data = data[["index"] + exog_vars]
# 打印数据框的前几行,以便查看结果
data.head()
# 创建一个空列表,用于存储每个外生变量的实验
exog_exps = []
# 创建一个空列表,用于存储每个外生变量的模型
exog_models = []
# 遍历每个外生变量
for exog_var in exog_vars:
# 创建一个时间序列预测实验对象
exog_exp = TSForecastingExperiment()
# 设置实验参数
exog_exp.setup(
data=data[["index", exog_var]], target=exog_var, index="index", fh=48,
numeric_imputation_target="ffill", numeric_imputation_exogenous="ffill",
fig_kwargs=global_fig_settings, session_id=42
)
# 用户可以自定义如何建模未来的外生变量,例如添加更多的步骤和模型,以获取更好的模型,但建模时间会增加。
# 比较不同模型的性能,并选择最佳模型
best = exog_exp.compare_models(
sort="mase", include=["arima", "ets", "exp_smooth", "theta", "lightgbm_cds_dt",]
)
# 完成模型的训练和调优
final_exog_model = exog_exp.finalize_model(best)
# 将实验对象和最终模型添加到列表中
exog_exps.append(exog_exp)
exog_models.append(final_exog_model)
# 第二步:获取外生变量的未来预测值 ----
# 对于每个外生变量的实验和模型,进行预测
future_exog = [
exog_exp.predict_model(exog_model)
for exog_exp, exog_model in zip(exog_exps, exog_models)
]
# 将预测结果合并为一个DataFrame
future_exog = pd.concat(future_exog, axis=1)
# 设置列名为外生变量的名称
future_exog.columns = exog_vars
Processing: 0%| | 0/25 [00:00<?, ?it/s]
Processing: 0%| | 0/25 [00:00<?, ?it/s]
Processing: 0%| | 0/25 [00:00<?, ?it/s]
future_exog
exp_future = TSForecastingExperiment()
# 加载模型
final_slim_model = exp_future.load_model("final_slim_model")
Transformation Pipeline and Model Successfully Loaded
# 使用final_slim_model对future_exog进行预测
future_preds = exp_future.predict_model(final_slim_model, X=future_exog)
# 绘制预测结果的图表
future_preds.plot()
<Axes: >
# 设置一个变量FH,表示预测的时间步长为48
FH = 48
# 设置一个变量metric,表示评估模型的指标为mase
metric = "mase"
# 设置一个列表exclude,包含了不需要使用的模型名称
exclude = ["auto_arima", "bats", "tbats", "lar_cds_dt", "par_cds_dt"]
# 创建一个时间序列预测实验对象
exp_auto = TSForecastingExperiment()
# 设置实验参数
exp_auto.setup(
data=data, # 数据集
target=target, # 目标变量
fh=FH, # 预测步长
enforce_exogenous=False, # 是否使用外生变量,如果模型支持多变量预测,则使用多变量预测,否则使用单变量预测
numeric_imputation_target="ffill", # 目标变量的数值缺失值填充方法,使用前向填充
numeric_imputation_exogenous="ffill", # 外生变量的数值缺失值填充方法,使用前向填充
fig_kwargs=global_fig_settings, # 图形参数设置
session_id=42 # 随机种子
)
<pycaret.time_series.forecasting.oop.TSForecastingExperiment at 0x27849347c40>
# 调用models()函数,查看可用的模型
exp_auto_noexo.models()
# 导入需要的模块和函数
from pycaret.regression import *
# 使用exp_auto对象的compare_models方法来比较不同模型的性能
# sort参数指定了排序的指标,即根据哪个指标来排序模型的性能
# turbo参数设置为False,表示使用较慢的模型,如Prophet模型
# exclude参数用于排除一些特定的模型
best = exp_auto.compare_models(sort=metric, turbo=False, exclude=exclude)
# 使用plot_model函数绘制模型
# 参数best是要绘制的模型
exp_auto.plot_model(best)
# 将最佳模型保存为最终模型
final_auto_model = exp_auto.finalize_model(best)
# 安全预测函数
def safe_predict(exp, model):
"""用于演示目的的预测包装函数。"""
try:
# 尝试进行未来预测
future_preds = exp.predict_model(model)
except ValueError as exception:
# 如果出现异常,打印异常信息
print(exception)
# 获取外生变量
exo_vars = exp.exogenous_variables
print(f"需要{len(exo_vars)}个外生变量(X)才能进行未来预测:\n{exo_vars}")
# 创建空列表以存储外生变量的实验和模型
exog_exps = []
exog_models = []
# 对于每个外生变量,进行以下操作
for exog_var in exog_vars:
# 创建时间序列预测实验对象
exog_exp = TSForecastingExperiment()
# 设置实验参数
exog_exp.setup(
data=data[exog_var], fh=FH,
numeric_imputation_target="ffill", numeric_imputation_exogenous="ffill",
fig_kwargs=global_fig_settings, session_id=42
)
# 用户可以自定义如何对未来的外生变量建模,例如添加更多步骤和模型以获取更好的模型,但建模时间会增加。
# 比较不同模型的性能,并选择最佳模型
best = exog_exp.compare_models(
sort=metric, include=["arima", "ets", "exp_smooth", "theta", "lightgbm_cds_dt",]
)
final_exog_model = exog_exp.finalize_model(best)
# 将实验和模型添加到列表中
exog_exps.append(exog_exp)
exog_models.append(final_exog_model)
# 第二步:获取外生变量的未来预测 ----
# 对于每个外生变量的实验和模型,进行未来预测
future_exog = [
exog_exp.predict_model(exog_model)
for exog_exp, exog_model in zip(exog_exps, exog_models)
]
# 将未来预测的结果合并为一个DataFrame
future_exog = pd.concat(future_exog, axis=1)
future_exog.columns = exog_vars
# 使用未来的外生变量进行最终的未来预测
future_preds = exp.predict_model(model, X=future_exog)
# 返回未来预测结果
return future_preds
# 调用safe_predict函数,对模型进行预测,并将结果保存在future_preds变量中
future_preds = safe_predict(exp_auto, final_auto_model)
# 使用matplotlib库中的plot函数,绘制future_preds的图形
future_preds.plot()
Model was trained with exogenous variables but you have not passed any for predictions. Please pass exogenous variables to make predictions.
3 exogenous variables (X) needed in order to make future predictions:
['NOx(GT)', 'PT08.S3(NOx)', 'RH']
<Axes: >