Overview¶
Two experimental setups are compared:
- Setup A (exp1) – Full series including COVID period (reference only)
- Setup B (exp2) – COVID period excluded, drift imputation (primary setup)
Pipeline per setup:
check_stats– stationarity, seasonality and white noise validationcompare_models– automated benchmarkingcreate_model– train selected modelstune_model– hyperparameter optimisation (native TS models only)blend_models– ensemble of top modelsstack_models– stacked ensemble with meta-learnerplot_model– decomposition, ACF, diagnostics, in-sample, forecast with CIfinalize_model+predict_model– forecasts stored in dictionary for comparisonget_config('dataset_transformed')– inspect drift imputation applied to COVID gap
Environment Configuration¶
| Component | Version |
|---|---|
| Python | 3.9.6 (Clang 21.0.0, macOS-26.4.1-arm64) |
| PyCaret | 3.3.2 |
| sktime | 0.26.0 |
| scikit-learn | 1.4.2 |
| statsmodels | 0.14.6 |
| pandas | 2.1.4 |
| numpy | 1.26.4 |
| jupyter_core | 5.8.1 |
| ipykernel | 6.30.1 |
Note: Results may vary on different environments. PyCaret 3.3.2 with sktime 0.26.0 is the validated configuration for this notebook.
1. Libraries and Data Loading¶
import subprocess
import sys
# Install pyjstat if not available
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'pyjstat', '-q'])
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
from collections import OrderedDict
import matplotlib.pyplot as plt
import requests
from pyjstat import pyjstat
WARNING: You are using pip version 21.2.4; however, version 26.0.1 is available. You should consider upgrading via the '/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip' command.
url = "https://ws.cso.ie/public/api.restful/PxStat.Data.Cube_API.ReadDataset/ASM01/JSON-stat/2.0/en"
r = requests.get(url)
r.raise_for_status()
json_stat_obj = r.json(object_pairs_hook=OrderedDict)
df_list = pyjstat.from_json_stat(json_stat_obj)
df = pd.DataFrame(df_list[0])
# delete non necessary columns and filter for air arrivals only
df = df.drop(columns=['STATISTIC'])
df = df[df['Mode'] == 'Air'].drop(columns=['Mode'])
df['Date'] = pd.to_datetime(df['Month'])
df = df.set_index('Date').sort_index().drop(columns=['Month'])
# fix to Dublin airport arrivals only (83% of total air arrivals)
df['Dublin_airport'] = df['value'] * 0.83
df = df.drop(columns=['value'])
df = df[df['Direction'] == 'Arrivals'].drop(columns=['Direction'])
print(f"Dataset: {df.shape[0]} observations | {df.index[0].date()} to {df.index[-1].date()}")
df.tail(14)
Dataset: 195 observations | 2010-01-01 to 2026-03-01
| Dublin_airport | |
|---|---|
| Date | |
| 2025-02-01 | 1059.578 |
| 2025-03-01 | 1286.998 |
| 2025-04-01 | 1519.066 |
| 2025-05-01 | 1657.095 |
| 2025-06-01 | 1720.507 |
| 2025-07-01 | 1877.294 |
| 2025-08-01 | 1924.106 |
| 2025-09-01 | 1661.162 |
| 2025-10-01 | 1593.932 |
| 2025-11-01 | 1279.279 |
| 2025-12-01 | 1276.540 |
| 2026-01-01 | 1197.109 |
| 2026-02-01 | 1152.538 |
| 2026-03-01 | 1402.866 |
2. Setup A – Full Series Including COVID¶
Reference only. Included to demonstrate the impact of the pandemic structural break on model performance.
from pycaret.time_series import *
df_A = df.copy()
df_A.index = df_A.index.to_period('M')
print(f"Setup A: {df_A.shape[0]} observations")
df_A
Setup A: 195 observations
| Dublin_airport | |
|---|---|
| Date | |
| 2010-01 | 604.572 |
| 2010-02 | 628.476 |
| 2010-03 | 752.478 |
| 2010-04 | 610.714 |
| 2010-05 | 803.855 |
| ... | ... |
| 2025-11 | 1279.279 |
| 2025-12 | 1276.540 |
| 2026-01 | 1197.109 |
| 2026-02 | 1152.538 |
| 2026-03 | 1402.866 |
195 rows × 1 columns
exp1 = TSForecastingExperiment()
exp1.setup(
data=df_A,
target='Dublin_airport',
session_id=100,
fh=12,
fold=3,
fold_strategy='expanding',
seasonal_period=12,
coverage=0.90,
verbose=True
)
| Description | Value | |
|---|---|---|
| 0 | session_id | 100 |
| 1 | Target | Dublin_airport |
| 2 | Approach | Univariate |
| 3 | Exogenous Variables | Not Present |
| 4 | Original data shape | (195, 1) |
| 5 | Transformed data shape | (195, 1) |
| 6 | Transformed train set shape | (183, 1) |
| 7 | Transformed test set shape | (12, 1) |
| 8 | Rows with missing values | 0.0% |
| 9 | Fold Generator | ExpandingWindowSplitter |
| 10 | Fold Number | 3 |
| 11 | Enforce Prediction Interval | False |
| 12 | Splits used for hyperparameters | all |
| 13 | User Defined Seasonal Period(s) | 12 |
| 14 | Ignore Seasonality Test | False |
| 15 | Seasonality Detection Algo | user_defined |
| 16 | Max Period to Consider | 60 |
| 17 | Seasonal Period(s) Tested | [12] |
| 18 | Significant Seasonal Period(s) | [12] |
| 19 | Significant Seasonal Period(s) without Harmonics | [12] |
| 20 | Remove Harmonics | False |
| 21 | Harmonics Order Method | harmonic_max |
| 22 | Num Seasonalities to Use | 1 |
| 23 | All Seasonalities to Use | [12] |
| 24 | Primary Seasonality | 12 |
| 25 | Seasonality Present | True |
| 26 | Seasonality Type | mul |
| 27 | Target Strictly Positive | True |
| 28 | Target White Noise | No |
| 29 | Recommended d | 1 |
| 30 | Recommended Seasonal D | 0 |
| 31 | Preprocess | False |
| 32 | CPU Jobs | -1 |
| 33 | Use GPU | False |
| 34 | Log Experiment | False |
| 35 | Experiment Name | ts-default-name |
| 36 | USI | 937f |
<pycaret.time_series.forecasting.oop.TSForecastingExperiment at 0x12acc8700>
2.1 Statistical Validation (Setup A)¶
check_stats() runs formal tests for stationarity (ADF, KPSS), seasonality, and white noise before any model is trained.
exp1.check_stats()
| Test | Test Name | Data | Property | Setting | Value | |
|---|---|---|---|---|---|---|
| 0 | Summary | Statistics | Transformed | Length | 195.0 | |
| 1 | Summary | Statistics | Transformed | # Missing Values | 0.0 | |
| 2 | Summary | Statistics | Transformed | Mean | 1005.049554 | |
| 3 | Summary | Statistics | Transformed | Median | 1026.71 | |
| 4 | Summary | Statistics | Transformed | Standard Deviation | 416.164152 | |
| 5 | Summary | Statistics | Transformed | Variance | 173192.601164 | |
| 6 | Summary | Statistics | Transformed | Kurtosis | 0.025503 | |
| 7 | Summary | Statistics | Transformed | Skewness | -0.394332 | |
| 8 | Summary | Statistics | Transformed | # Distinct Values | 194.0 | |
| 9 | White Noise | Ljung-Box | Transformed | Test Statictic | {'alpha': 0.05, 'K': 24} | 865.945938 |
| 10 | White Noise | Ljung-Box | Transformed | Test Statictic | {'alpha': 0.05, 'K': 48} | 1208.888895 |
| 11 | White Noise | Ljung-Box | Transformed | p-value | {'alpha': 0.05, 'K': 24} | 0.0 |
| 12 | White Noise | Ljung-Box | Transformed | p-value | {'alpha': 0.05, 'K': 48} | 0.0 |
| 13 | White Noise | Ljung-Box | Transformed | White Noise | {'alpha': 0.05, 'K': 24} | 0.0 |
| 14 | White Noise | Ljung-Box | Transformed | White Noise | {'alpha': 0.05, 'K': 48} | 0.0 |
| 15 | Stationarity | ADF | Transformed | Stationarity | {'alpha': 0.05} | True |
| 16 | Stationarity | ADF | Transformed | p-value | {'alpha': 0.05} | 0.028922 |
| 17 | Stationarity | ADF | Transformed | Test Statistic | {'alpha': 0.05} | -3.069149 |
| 18 | Stationarity | ADF | Transformed | Critical Value 1% | {'alpha': 0.05} | -3.467211 |
| 19 | Stationarity | ADF | Transformed | Critical Value 5% | {'alpha': 0.05} | -2.877735 |
| 20 | Stationarity | ADF | Transformed | Critical Value 10% | {'alpha': 0.05} | -2.575403 |
| 21 | Stationarity | KPSS | Transformed | Trend Stationarity | {'alpha': 0.05} | True |
| 22 | Stationarity | KPSS | Transformed | p-value | {'alpha': 0.05} | 0.081382 |
| 23 | Stationarity | KPSS | Transformed | Test Statistic | {'alpha': 0.05} | 0.129054 |
| 24 | Stationarity | KPSS | Transformed | Critical Value 10% | {'alpha': 0.05} | 0.119 |
| 25 | Stationarity | KPSS | Transformed | Critical Value 5% | {'alpha': 0.05} | 0.146 |
| 26 | Stationarity | KPSS | Transformed | Critical Value 2.5% | {'alpha': 0.05} | 0.176 |
| 27 | Stationarity | KPSS | Transformed | Critical Value 1% | {'alpha': 0.05} | 0.216 |
| 28 | Normality | Shapiro | Transformed | Normality | {'alpha': 0.05} | False |
| 29 | Normality | Shapiro | Transformed | p-value | {'alpha': 0.05} | 0.001567 |
exp1.plot_model(plot='cv')
exp1.plot_model(plot='train_test_split')
best_A = exp1.compare_models(sort='SMAPE', n_select=3)
results_A = exp1.pull() # called immediately after compare_models
| Model | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | TT (Sec) | |
|---|---|---|---|---|---|---|---|---|---|
| ets | ETS | 0.5264 | 0.2817 | 107.6606 | 114.5154 | 0.0862 | 0.0910 | 0.4690 | 0.0267 |
| arima | ARIMA | 0.5388 | 0.3325 | 109.8586 | 134.3540 | 0.0865 | 0.0928 | 0.0874 | 0.6033 |
| lasso_cds_dt | Lasso w/ Cond. Deseasonalize & Detrending | 0.5675 | 0.3372 | 128.3293 | 144.4958 | 0.0944 | 0.1008 | 0.4976 | 0.1033 |
| lr_cds_dt | Linear w/ Cond. Deseasonalize & Detrending | 0.5680 | 0.3374 | 128.4184 | 144.5756 | 0.0945 | 0.1009 | 0.4971 | 0.1300 |
| en_cds_dt | Elastic Net w/ Cond. Deseasonalize & Detrending | 0.5677 | 0.3373 | 128.3709 | 144.5352 | 0.0945 | 0.1009 | 0.4973 | 0.1067 |
| ridge_cds_dt | Ridge w/ Cond. Deseasonalize & Detrending | 0.5680 | 0.3374 | 128.4184 | 144.5756 | 0.0945 | 0.1009 | 0.4971 | 0.1067 |
| llar_cds_dt | Lasso Least Angular Regressor w/ Cond. Deseasonalize & Detrending | 0.5676 | 0.3373 | 128.3355 | 144.5035 | 0.0944 | 0.1009 | 0.4975 | 0.0467 |
| br_cds_dt | Bayesian Ridge w/ Cond. Deseasonalize & Detrending | 0.5706 | 0.3423 | 129.4126 | 146.7938 | 0.0945 | 0.1013 | 0.4815 | 0.0467 |
| exp_smooth | Exponential Smoothing | 0.5910 | 0.3687 | 120.1458 | 148.5408 | 0.0938 | 0.1040 | -0.1009 | 0.0233 |
| theta | Theta Forecaster | 0.6646 | 0.3769 | 140.7851 | 156.0420 | 0.1084 | 0.1128 | 0.3272 | 0.0100 |
| huber_cds_dt | Huber w/ Cond. Deseasonalize & Detrending | 0.7032 | 0.3942 | 147.4265 | 163.2344 | 0.1118 | 0.1217 | 0.1222 | 0.1200 |
| auto_arima | Auto ARIMA | 0.7463 | 0.4124 | 160.0231 | 172.1743 | 0.1217 | 0.1319 | 0.1977 | 3.5367 |
| rf_cds_dt | Random Forest w/ Cond. Deseasonalize & Detrending | 0.8412 | 0.4471 | 170.2034 | 180.9643 | 0.1351 | 0.1541 | -0.5760 | 0.1233 |
| ada_cds_dt | AdaBoost w/ Cond. Deseasonalize & Detrending | 0.9034 | 0.4952 | 189.6974 | 204.8522 | 0.1454 | 0.1604 | -0.3554 | 0.0600 |
| et_cds_dt | Extra Trees w/ Cond. Deseasonalize & Detrending | 0.8680 | 0.4681 | 175.3988 | 189.6681 | 0.1399 | 0.1616 | -0.7129 | 0.1167 |
| omp_cds_dt | Orthogonal Matching Pursuit w/ Cond. Deseasonalize & Detrending | 0.9295 | 0.5341 | 199.5970 | 224.2139 | 0.1475 | 0.1646 | -0.4640 | 0.1167 |
| gbr_cds_dt | Gradient Boosting w/ Cond. Deseasonalize & Detrending | 0.8932 | 0.5333 | 183.8293 | 218.6532 | 0.1485 | 0.1714 | -0.5899 | 0.0567 |
| lightgbm_cds_dt | Light Gradient Boosting w/ Cond. Deseasonalize & Detrending | 1.0435 | 0.5889 | 217.8155 | 242.5443 | 0.1669 | 0.1890 | -0.7832 | 0.1033 |
| dt_cds_dt | Decision Tree w/ Cond. Deseasonalize & Detrending | 1.0174 | 0.5333 | 203.5298 | 214.4032 | 0.1631 | 0.1965 | -1.5193 | 0.0467 |
| knn_cds_dt | K Neighbors w/ Cond. Deseasonalize & Detrending | 1.1000 | 0.5921 | 222.3731 | 239.8312 | 0.1773 | 0.2119 | -1.6969 | 0.1033 |
| naive | Naive Forecaster | 1.2969 | 0.7693 | 278.2114 | 320.5331 | 0.2005 | 0.2259 | -1.6132 | 1.1633 |
| stlf | STLF | 1.2359 | 0.6751 | 245.9252 | 270.0803 | 0.1947 | 0.2526 | -3.5322 | 0.6533 |
| polytrend | Polynomial Trend Forecaster | 1.9047 | 1.1193 | 415.1086 | 470.5673 | 0.2942 | 0.3574 | -4.0191 | 0.6100 |
| grand_means | Grand Means Forecaster | 1.9050 | 1.1213 | 420.9558 | 473.8693 | 0.2957 | 0.3584 | -3.7682 | 0.5800 |
| snaive | Seasonal Naive Forecaster | 1.5853 | 0.8716 | 315.3723 | 348.6414 | 0.2459 | 0.3663 | -6.7446 | 0.5867 |
| croston | Croston | 2.1742 | 1.2115 | 455.8357 | 500.0620 | 0.3388 | 0.4433 | -6.8855 | 0.0100 |
2.2 Create and Tune Models (Setup A)¶
models_A = ['exp_smooth', 'arima', 'ets', 'lasso_cds_dt', 'lr_cds_dt']
tune_A = ['exp_smooth', 'arima', 'ets', 'lasso_cds_dt', 'lr_cds_dt'] # only native TS models support tune_model
resultados_A = {}
resultados_t_A = {}
for model_name in models_A:
print(f'Creating: {model_name}')
resultados_A[model_name] = exp1.create_model(model_name)
for model_name in tune_A:
print(f'Tuning: {model_name}')
resultados_t_A[model_name] = exp1.tune_model(resultados_A[model_name])
Creating: exp_smooth
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.2619 | 0.7870 | 241.5628 | 307.5916 | 0.1930 | 0.2261 | -2.1239 |
| 1 | 2023-03 | 0.1382 | 0.0944 | 32.8447 | 41.8083 | 0.0242 | 0.0243 | 0.9654 |
| 2 | 2024-03 | 0.3728 | 0.2246 | 86.0299 | 96.2226 | 0.0642 | 0.0617 | 0.8558 |
| Mean | NaT | 0.5910 | 0.3687 | 120.1458 | 148.5408 | 0.0938 | 0.1040 | -0.1009 |
| SD | NaT | 0.4840 | 0.3005 | 88.5578 | 114.6388 | 0.0720 | 0.0876 | 1.4312 |
Creating: arima
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.1545 | 0.7159 | 221.0189 | 279.8213 | 0.1735 | 0.1951 | -1.5853 |
| 1 | 2023-03 | 0.2908 | 0.1765 | 69.1127 | 78.1979 | 0.0559 | 0.0537 | 0.8791 |
| 2 | 2024-03 | 0.1709 | 0.1052 | 39.4443 | 45.0427 | 0.0302 | 0.0296 | 0.9684 |
| Mean | NaT | 0.5388 | 0.3325 | 109.8586 | 134.3540 | 0.0865 | 0.0928 | 0.0874 |
| SD | NaT | 0.4382 | 0.2727 | 79.5299 | 103.7477 | 0.0624 | 0.0730 | 1.1833 |
Creating: ets
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.0770 | 0.5305 | 206.1783 | 207.3455 | 0.1718 | 0.1885 | -0.4195 |
| 1 | 2023-03 | 0.1366 | 0.0942 | 32.4681 | 41.7288 | 0.0239 | 0.0240 | 0.9656 |
| 2 | 2024-03 | 0.3654 | 0.2205 | 84.3355 | 94.4720 | 0.0629 | 0.0604 | 0.8610 |
| Mean | NaT | 0.5264 | 0.2817 | 107.6606 | 114.5154 | 0.0862 | 0.0910 | 0.4690 |
| SD | NaT | 0.4004 | 0.1833 | 72.8096 | 69.0822 | 0.0626 | 0.0705 | 0.6297 |
Creating: lasso_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.3554 | 0.2036 | 68.0448 | 79.5626 | 0.0569 | 0.0581 | 0.7910 |
| 1 | 2023-03 | 0.8838 | 0.5246 | 210.0203 | 232.4646 | 0.1494 | 0.1634 | -0.0686 |
| 2 | 2024-03 | 0.4633 | 0.2835 | 106.9229 | 121.4603 | 0.0770 | 0.0810 | 0.7703 |
| Mean | NaT | 0.5675 | 0.3372 | 128.3293 | 144.4958 | 0.0944 | 0.1008 | 0.4976 |
| SD | NaT | 0.2280 | 0.1365 | 59.9052 | 64.5122 | 0.0397 | 0.0452 | 0.4004 |
Creating: lr_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.3561 | 0.2039 | 68.1786 | 79.6925 | 0.0570 | 0.0582 | 0.7903 |
| 1 | 2023-03 | 0.8841 | 0.5247 | 210.0740 | 232.5020 | 0.1494 | 0.1635 | -0.0690 |
| 2 | 2024-03 | 0.4637 | 0.2837 | 107.0026 | 121.5323 | 0.0771 | 0.0811 | 0.7700 |
| Mean | NaT | 0.5680 | 0.3374 | 128.4184 | 144.5756 | 0.0945 | 0.1009 | 0.4971 |
| SD | NaT | 0.2278 | 0.1364 | 59.8752 | 64.4770 | 0.0397 | 0.0452 | 0.4004 |
Tuning: exp_smooth
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.0578 | 0.5213 | 202.5059 | 203.7724 | 0.1688 | 0.1848 | -0.3710 |
| 1 | 2023-03 | 0.1389 | 0.0920 | 32.9991 | 40.7578 | 0.0242 | 0.0243 | 0.9672 |
| 2 | 2024-03 | 0.3765 | 0.2264 | 86.8834 | 96.9969 | 0.0647 | 0.0621 | 0.8535 |
| Mean | NaT | 0.5244 | 0.2799 | 107.4628 | 113.8424 | 0.0859 | 0.0904 | 0.4832 |
| SD | NaT | 0.3895 | 0.1793 | 70.7143 | 67.6080 | 0.0609 | 0.0685 | 0.6058 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 0.4s finished
Tuning: arima
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.6543 | 0.3536 | 125.2491 | 138.1992 | 0.0989 | 0.1050 | 0.3694 |
| 1 | 2023-03 | 0.3446 | 0.2075 | 81.8936 | 91.9285 | 0.0638 | 0.0628 | 0.8329 |
| 2 | 2024-03 | 0.4903 | 0.3403 | 113.1434 | 145.7729 | 0.0947 | 0.0873 | 0.6691 |
| Mean | NaT | 0.4964 | 0.3004 | 106.7620 | 125.3002 | 0.0858 | 0.0850 | 0.6238 |
| SD | NaT | 0.1265 | 0.0660 | 18.2659 | 23.7991 | 0.0157 | 0.0173 | 0.1919 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 2.0s finished
Tuning: ets
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.0770 | 0.5305 | 206.1783 | 207.3455 | 0.1718 | 0.1885 | -0.4195 |
| 1 | 2023-03 | 0.1366 | 0.0942 | 32.4681 | 41.7288 | 0.0239 | 0.0240 | 0.9656 |
| 2 | 2024-03 | 0.3654 | 0.2205 | 84.3355 | 94.4720 | 0.0629 | 0.0604 | 0.8610 |
| Mean | NaT | 0.5264 | 0.2817 | 107.6606 | 114.5154 | 0.0862 | 0.0910 | 0.4690 |
| SD | NaT | 0.4004 | 0.1833 | 72.8096 | 69.0822 | 0.0626 | 0.0705 | 0.6297 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 0.5s finished
Tuning: lasso_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.4612 | 0.2792 | 88.2830 | 109.1143 | 0.0695 | 0.0697 | 0.6069 |
| 1 | 2023-03 | 0.8838 | 0.5246 | 210.0158 | 232.4614 | 0.1494 | 0.1634 | -0.0686 |
| 2 | 2024-03 | 0.3151 | 0.2279 | 72.7194 | 97.6240 | 0.0543 | 0.0573 | 0.8516 |
| Mean | NaT | 0.5534 | 0.3439 | 123.6727 | 146.3999 | 0.0911 | 0.0968 | 0.4633 |
| SD | NaT | 0.2412 | 0.1295 | 61.3835 | 61.0352 | 0.0417 | 0.0474 | 0.3892 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 0.8s finished
Tuning: lr_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.3561 | 0.2039 | 68.1786 | 79.6925 | 0.0570 | 0.0582 | 0.7903 |
| 1 | 2023-03 | 0.8841 | 0.5247 | 210.0740 | 232.5020 | 0.1494 | 0.1635 | -0.0690 |
| 2 | 2024-03 | 0.4637 | 0.2837 | 107.0026 | 121.5323 | 0.0771 | 0.0811 | 0.7700 |
| Mean | NaT | 0.5680 | 0.3374 | 128.4184 | 144.5756 | 0.0945 | 0.1009 | 0.4971 |
| SD | NaT | 0.2278 | 0.1364 | 59.8752 | 64.4770 | 0.0397 | 0.0452 | 0.4004 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 0.9s finished
2.3 Blend and Stack (Setup A)¶
# Blend – weighted average of the top 3 models from compare_models
blended_A = exp1.blend_models(best_A)
print("Blended model created – Setup A")
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.6292 | 0.3998 | 120.4427 | 156.2658 | 0.0932 | 0.1010 | 0.1937 |
| 1 | 2023-03 | 0.2302 | 0.1442 | 54.6971 | 63.8840 | 0.0381 | 0.0390 | 0.9193 |
| 2 | 2024-03 | 0.1205 | 0.0724 | 27.8157 | 31.0275 | 0.0197 | 0.0196 | 0.9850 |
| Mean | NaT | 0.3266 | 0.2055 | 67.6519 | 83.7258 | 0.0503 | 0.0532 | 0.6993 |
| SD | NaT | 0.2186 | 0.1405 | 38.9085 | 53.0184 | 0.0312 | 0.0347 | 0.3585 |
Blended model created – Setup A
2.4 Diagnostics and Plots (Setup A)¶
# Seasonal decomposition and ACF for the best individual model
exp1.plot_model(resultados_t_A['exp_smooth'], plot='decomp')
exp1.plot_model(resultados_t_A['exp_smooth'], plot='acf')
for model_name in tune_A:
print(f'Diagnostics: {model_name}')
exp1.plot_model(estimator=resultados_t_A[model_name], plot='diagnostics')
Diagnostics: exp_smooth
Diagnostics: arima
Diagnostics: ets
Diagnostics: lasso_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. Diagnostics: lr_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret.
for model_name in tune_A:
print(f'In-sample: {model_name}')
exp1.plot_model(resultados_t_A[model_name], plot='insample')
In-sample: exp_smooth
In-sample: arima
In-sample: ets
In-sample: lasso_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In-sample: lr_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret.
2.5 Finalize and Store Forecasts (Setup A)¶
forecasts_A = {}
resultados_final_A = {}
# stack_models not supported in this PyCaret version for time series
# blend_models is used as the ensemble method
all_models_A = {**resultados_A, 'blend': blended_A}
for model_name, model_obj in all_models_A.items():
source = resultados_t_A.get(model_name, model_obj)
final_model = exp1.finalize_model(source)
pred = exp1.predict_model(final_model, fh=12)
forecasts_A[model_name] = pred['y_pred']
resultados_final_A[model_name] = final_model
print(f'Forecast – {model_name}:')
display(pred)
exp1.plot_model(final_model, plot='forecast', data_kwargs={'fh': 12})
print()
Forecast – exp_smooth:
| y_pred | |
|---|---|
| 2026-04 | 1581.6608 |
| 2026-05 | 1720.6196 |
| 2026-06 | 1832.7146 |
| 2026-07 | 2041.7404 |
| 2026-08 | 2069.9425 |
| 2026-09 | 1794.0123 |
| 2026-10 | 1706.0981 |
| 2026-11 | 1370.2635 |
| 2026-12 | 1339.9335 |
| 2027-01 | 1254.7507 |
| 2027-02 | 1254.1143 |
| 2027-03 | 1465.3596 |
Forecast – arima:
| y_pred | |
|---|---|
| 2026-04 | 1549.5826 |
| 2026-05 | 1657.3518 |
| 2026-06 | 1736.2438 |
| 2026-07 | 1882.2799 |
| 2026-08 | 1925.8036 |
| 2026-09 | 1755.0969 |
| 2026-10 | 1700.8654 |
| 2026-11 | 1489.3823 |
| 2026-12 | 1467.4014 |
| 2027-01 | 1409.8464 |
| 2027-02 | 1411.5449 |
| 2027-03 | 1545.9635 |
Forecast – ets:
| y_pred | |
|---|---|
| 2026-04 | 1573.6837 |
| 2026-05 | 1711.4875 |
| 2026-06 | 1822.9991 |
| 2026-07 | 2032.1108 |
| 2026-08 | 2060.7454 |
| 2026-09 | 1784.9221 |
| 2026-10 | 1696.5987 |
| 2026-11 | 1360.3668 |
| 2026-12 | 1326.9046 |
| 2027-01 | 1239.1107 |
| 2027-02 | 1237.3567 |
| 2027-03 | 1443.5386 |
Forecast – lasso_cds_dt:
| y_pred | |
|---|---|
| 2026-04 | 1542.0120 |
| 2026-05 | 1628.6971 |
| 2026-06 | 1704.8745 |
| 2026-07 | 1932.0375 |
| 2026-08 | 2057.1938 |
| 2026-09 | 1793.3397 |
| 2026-10 | 1680.6175 |
| 2026-11 | 1320.3128 |
| 2026-12 | 1285.1271 |
| 2027-01 | 1148.7629 |
| 2027-02 | 1123.8197 |
| 2027-03 | 1268.6165 |
Forecast – lr_cds_dt:
| y_pred | |
|---|---|
| 2026-04 | 1557.6938 |
| 2026-05 | 1640.9832 |
| 2026-06 | 1708.6036 |
| 2026-07 | 1840.1089 |
| 2026-08 | 1832.0089 |
| 2026-09 | 1624.9880 |
| 2026-10 | 1530.8852 |
| 2026-11 | 1288.6661 |
| 2026-12 | 1243.2458 |
| 2027-01 | 1158.4668 |
| 2027-02 | 1160.6390 |
| 2027-03 | 1290.4236 |
Forecast – blend:
| y_pred | |
|---|---|
| 2026-04 | 1586.7699 |
| 2026-05 | 1704.5554 |
| 2026-06 | 1783.6016 |
| 2026-07 | 1947.6311 |
| 2026-08 | 1968.4477 |
| 2026-09 | 1718.2799 |
| 2026-10 | 1633.5293 |
| 2026-11 | 1334.4188 |
| 2026-12 | 1305.8150 |
| 2027-01 | 1220.5191 |
| 2027-02 | 1204.5537 |
| 2027-03 | 1398.7796 |
3. Setup B – COVID Excluded (Primary Setup)¶
The COVID period (Jan 2020 – Feb 2022) is removed and the index rebuilt with NaN in the gap. PyCaret imputes these using drift interpolation — a linear bridge between pre- and post-pandemic levels that preserves the seasonal structure.
df_B = df[(df.index < '2020-01-01') | (df.index > '2022-02-01')].copy()
df_B = df_B[['Dublin_airport']].dropna()
df_B.index = df_B.index.to_period('M')
idx_full = pd.period_range(min(df_B.index), max(df_B.index), freq='M')
df_B = df_B.reindex(idx_full, fill_value=np.nan)
print(f"Setup B: {df_B.shape[0]} observations")
print(f" Period : {df_B.index[0]} to {df_B.index[-1]}")
print(f" NaN count: {df_B['Dublin_airport'].isna().sum()} months (COVID gap)")
df_B
Setup B: 195 observations Period : 2010-01 to 2026-03 NaN count: 26 months (COVID gap)
| Dublin_airport | |
|---|---|
| 2010-01 | 604.572 |
| 2010-02 | 628.476 |
| 2010-03 | 752.478 |
| 2010-04 | 610.714 |
| 2010-05 | 803.855 |
| ... | ... |
| 2025-11 | 1279.279 |
| 2025-12 | 1276.540 |
| 2026-01 | 1197.109 |
| 2026-02 | 1152.538 |
| 2026-03 | 1402.866 |
195 rows × 1 columns
exp2 = TSForecastingExperiment()
exp2.setup(
data=df_B,
target='Dublin_airport',
session_id=42,
fh=12,
fold=3,
fold_strategy='expanding',
seasonal_period=12,
coverage=0.90,
numeric_imputation_target='drift',
verbose=True
)
| Description | Value | |
|---|---|---|
| 0 | session_id | 42 |
| 1 | Target | Dublin_airport |
| 2 | Approach | Univariate |
| 3 | Exogenous Variables | Not Present |
| 4 | Original data shape | (195, 1) |
| 5 | Transformed data shape | (195, 1) |
| 6 | Transformed train set shape | (183, 1) |
| 7 | Transformed test set shape | (12, 1) |
| 8 | Rows with missing values | 13.3% |
| 9 | Fold Generator | ExpandingWindowSplitter |
| 10 | Fold Number | 3 |
| 11 | Enforce Prediction Interval | False |
| 12 | Splits used for hyperparameters | all |
| 13 | User Defined Seasonal Period(s) | 12 |
| 14 | Ignore Seasonality Test | False |
| 15 | Seasonality Detection Algo | user_defined |
| 16 | Max Period to Consider | 60 |
| 17 | Seasonal Period(s) Tested | [12] |
| 18 | Significant Seasonal Period(s) | [12] |
| 19 | Significant Seasonal Period(s) without Harmonics | [12] |
| 20 | Remove Harmonics | False |
| 21 | Harmonics Order Method | harmonic_max |
| 22 | Num Seasonalities to Use | 1 |
| 23 | All Seasonalities to Use | [12] |
| 24 | Primary Seasonality | 12 |
| 25 | Seasonality Present | True |
| 26 | Seasonality Type | mul |
| 27 | Target Strictly Positive | True |
| 28 | Target White Noise | No |
| 29 | Recommended d | 1 |
| 30 | Recommended Seasonal D | 0 |
| 31 | Preprocess | True |
| 32 | Numerical Imputation (Target) | drift |
| 33 | Transformation (Target) | None |
| 34 | Scaling (Target) | None |
| 35 | Feature Engineering (Target) - Reduced Regression | False |
| 36 | CPU Jobs | -1 |
| 37 | Use GPU | False |
| 38 | Log Experiment | False |
| 39 | Experiment Name | ts-default-name |
| 40 | USI | bc48 |
<pycaret.time_series.forecasting.oop.TSForecastingExperiment at 0x14d1635e0>
3.1 Statistical Validation (Setup B)¶
exp2.check_stats()
| Test | Test Name | Data | Property | Setting | Value | |
|---|---|---|---|---|---|---|
| 0 | Summary | Statistics | Transformed | Length | 195.0 | |
| 1 | Summary | Statistics | Transformed | # Missing Values | 0.0 | |
| 2 | Summary | Statistics | Transformed | Mean | 1126.863943 | |
| 3 | Summary | Statistics | Transformed | Median | 1138.428 | |
| 4 | Summary | Statistics | Transformed | Standard Deviation | 300.028783 | |
| 5 | Summary | Statistics | Transformed | Variance | 90017.270496 | |
| 6 | Summary | Statistics | Transformed | Kurtosis | -0.394798 | |
| 7 | Summary | Statistics | Transformed | Skewness | 0.155182 | |
| 8 | Summary | Statistics | Transformed | # Distinct Values | 194.0 | |
| 9 | White Noise | Ljung-Box | Transformed | Test Statictic | {'alpha': 0.05, 'K': 24} | 1286.307799 |
| 10 | White Noise | Ljung-Box | Transformed | Test Statictic | {'alpha': 0.05, 'K': 48} | 1720.25752 |
| 11 | White Noise | Ljung-Box | Transformed | p-value | {'alpha': 0.05, 'K': 24} | 0.0 |
| 12 | White Noise | Ljung-Box | Transformed | p-value | {'alpha': 0.05, 'K': 48} | 0.0 |
| 13 | White Noise | Ljung-Box | Transformed | White Noise | {'alpha': 0.05, 'K': 24} | 0.0 |
| 14 | White Noise | Ljung-Box | Transformed | White Noise | {'alpha': 0.05, 'K': 48} | 0.0 |
| 15 | Stationarity | ADF | Transformed | Stationarity | {'alpha': 0.05} | False |
| 16 | Stationarity | ADF | Transformed | p-value | {'alpha': 0.05} | 0.945526 |
| 17 | Stationarity | ADF | Transformed | Test Statistic | {'alpha': 0.05} | -0.137938 |
| 18 | Stationarity | ADF | Transformed | Critical Value 1% | {'alpha': 0.05} | -3.467005 |
| 19 | Stationarity | ADF | Transformed | Critical Value 5% | {'alpha': 0.05} | -2.877644 |
| 20 | Stationarity | ADF | Transformed | Critical Value 10% | {'alpha': 0.05} | -2.575355 |
| 21 | Stationarity | KPSS | Transformed | Trend Stationarity | {'alpha': 0.05} | True |
| 22 | Stationarity | KPSS | Transformed | p-value | {'alpha': 0.05} | 0.1 |
| 23 | Stationarity | KPSS | Transformed | Test Statistic | {'alpha': 0.05} | 0.10397 |
| 24 | Stationarity | KPSS | Transformed | Critical Value 10% | {'alpha': 0.05} | 0.119 |
| 25 | Stationarity | KPSS | Transformed | Critical Value 5% | {'alpha': 0.05} | 0.146 |
| 26 | Stationarity | KPSS | Transformed | Critical Value 2.5% | {'alpha': 0.05} | 0.176 |
| 27 | Stationarity | KPSS | Transformed | Critical Value 1% | {'alpha': 0.05} | 0.216 |
| 28 | Normality | Shapiro | Transformed | Normality | {'alpha': 0.05} | False |
| 29 | Normality | Shapiro | Transformed | p-value | {'alpha': 0.05} | 0.049615 |
3.2 Inspect Drift Imputation Applied to COVID Gap¶
# Visualise how drift imputation filled the 26 missing months
df_transformed = exp2.get_config('dataset_transformed')
fig, ax = plt.subplots(figsize=(16, 5))
df_transformed['Dublin_airport'].plot(ax=ax, color='steelblue', lw=1.5, label='After imputation')
ax.axvspan(
df_transformed.index.asof(pd.Period('2020-01', 'M')),
df_transformed.index.asof(pd.Period('2022-02', 'M')),
alpha=0.15, color='red', label='COVID gap (drift filled)'
)
ax.set_title('Dataset After Drift Imputation – COVID Gap Highlighted')
ax.set_ylabel('Passengers (thousands)')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
exp2.plot_model(plot='cv')
exp2.plot_model(plot='train_test_split')
best_B = exp2.compare_models(sort='SMAPE', n_select=3)
results_B = exp2.pull() # called immediately after compare_models
| Model | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | TT (Sec) | |
|---|---|---|---|---|---|---|---|---|---|
| exp_smooth | Exponential Smoothing | 0.8150 | 0.7053 | 68.3717 | 81.6027 | 0.0543 | 0.0548 | 0.8293 | 0.0300 |
| et_cds_dt | Extra Trees w/ Cond. Deseasonalize & Detrending | 0.9256 | 0.7908 | 76.7661 | 90.8576 | 0.0601 | 0.0594 | 0.7638 | 0.1333 |
| omp_cds_dt | Orthogonal Matching Pursuit w/ Cond. Deseasonalize & Detrending | 0.9179 | 0.7500 | 77.9127 | 87.8537 | 0.0627 | 0.0614 | 0.8346 | 0.0567 |
| gbr_cds_dt | Gradient Boosting w/ Cond. Deseasonalize & Detrending | 1.0126 | 0.8466 | 83.8836 | 97.0198 | 0.0652 | 0.0645 | 0.7165 | 0.0767 |
| rf_cds_dt | Random Forest w/ Cond. Deseasonalize & Detrending | 1.0271 | 0.8798 | 85.3169 | 101.0986 | 0.0674 | 0.0663 | 0.7094 | 0.1433 |
| dt_cds_dt | Decision Tree w/ Cond. Deseasonalize & Detrending | 1.0579 | 0.9629 | 90.0574 | 112.4471 | 0.0641 | 0.0664 | 0.7111 | 0.0533 |
| knn_cds_dt | K Neighbors w/ Cond. Deseasonalize & Detrending | 1.0946 | 0.9350 | 91.6093 | 107.6575 | 0.0716 | 0.0695 | 0.6763 | 0.1233 |
| lightgbm_cds_dt | Light Gradient Boosting w/ Cond. Deseasonalize & Detrending | 1.1015 | 0.9255 | 90.8624 | 105.7087 | 0.0721 | 0.0701 | 0.6397 | 0.1133 |
| ada_cds_dt | AdaBoost w/ Cond. Deseasonalize & Detrending | 1.1980 | 1.0012 | 99.0010 | 114.6962 | 0.0797 | 0.0774 | 0.6009 | 0.0667 |
| arima | ARIMA | 1.3680 | 1.0628 | 109.8095 | 118.8804 | 0.0865 | 0.0897 | 0.2589 | 0.0300 |
| snaive | Seasonal Naive Forecaster | 1.4778 | 1.2156 | 121.5364 | 138.8985 | 0.0931 | 0.0951 | 0.3747 | 0.0367 |
| theta | Theta Forecaster | 1.3855 | 1.0569 | 111.6768 | 118.8350 | 0.0904 | 0.0976 | 0.3182 | 0.0333 |
| huber_cds_dt | Huber w/ Cond. Deseasonalize & Detrending | 1.4851 | 1.2577 | 120.8226 | 142.1904 | 0.0933 | 0.0987 | 0.1714 | 0.0667 |
| ets | ETS | 1.4810 | 1.1344 | 120.0975 | 128.1750 | 0.0966 | 0.1031 | 0.2951 | 0.0300 |
| br_cds_dt | Bayesian Ridge w/ Cond. Deseasonalize & Detrending | 1.5906 | 1.2852 | 129.0605 | 145.0602 | 0.1016 | 0.1050 | 0.0976 | 0.0600 |
| auto_arima | Auto ARIMA | 1.6152 | 1.2883 | 131.1861 | 145.4063 | 0.1043 | 0.1086 | 0.0849 | 2.3433 |
| en_cds_dt | Elastic Net w/ Cond. Deseasonalize & Detrending | 1.7032 | 1.3734 | 137.8067 | 154.6299 | 0.1087 | 0.1134 | -0.0901 | 0.0567 |
| lasso_cds_dt | Lasso w/ Cond. Deseasonalize & Detrending | 1.7032 | 1.3734 | 137.8044 | 154.6274 | 0.1087 | 0.1134 | -0.0901 | 0.0667 |
| llar_cds_dt | Lasso Least Angular Regressor w/ Cond. Deseasonalize & Detrending | 1.7032 | 1.3734 | 137.8036 | 154.6269 | 0.1087 | 0.1134 | -0.0901 | 0.0600 |
| lr_cds_dt | Linear w/ Cond. Deseasonalize & Detrending | 1.7039 | 1.3740 | 137.8607 | 154.6899 | 0.1088 | 0.1135 | -0.0913 | 0.0567 |
| ridge_cds_dt | Ridge w/ Cond. Deseasonalize & Detrending | 1.7039 | 1.3740 | 137.8603 | 154.6895 | 0.1088 | 0.1135 | -0.0913 | 0.0733 |
| stlf | STLF | 1.8791 | 1.5711 | 155.9059 | 180.9973 | 0.1162 | 0.1215 | 0.0298 | 0.0433 |
| polytrend | Polynomial Trend Forecaster | 2.5179 | 2.0991 | 213.2533 | 244.8161 | 0.1715 | 0.1603 | -0.3413 | 0.0167 |
| croston | Croston | 2.5137 | 2.1289 | 214.7543 | 250.5319 | 0.1532 | 0.1623 | -0.3177 | 0.0200 |
| grand_means | Grand Means Forecaster | 3.3003 | 2.9833 | 282.2588 | 351.1370 | 0.1895 | 0.2206 | -1.5736 | 0.0167 |
| naive | Naive Forecaster | 3.3298 | 2.7725 | 278.2114 | 320.5331 | 0.2005 | 0.2259 | -1.6132 | 0.0200 |
3.3 Create and Tune Models (Setup B)¶
models_B = ['exp_smooth', 'arima', 'et_cds_dt', 'omp_cds_dt', 'gbr_cds_dt']
tune_B = ['exp_smooth', 'arima', 'et_cds_dt', 'omp_cds_dt', 'gbr_cds_dt']
resultados_B = {}
resultados_t_B = {}
for model_name in models_B:
print(f'Creating: {model_name}')
resultados_B[model_name] = exp2.create_model(model_name)
for model_name in tune_B:
print(f'Tuning: {model_name}')
resultados_t_B[model_name] = exp2.tune_model(resultados_B[model_name])
Creating: exp_smooth
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.0243 | 0.9165 | 79.7430 | 99.7027 | 0.0648 | 0.0683 | 0.6718 |
| 1 | 2023-03 | 0.6694 | 0.5116 | 57.4432 | 60.8166 | 0.0426 | 0.0430 | 0.9269 |
| 2 | 2024-03 | 0.7512 | 0.6876 | 67.9290 | 84.2886 | 0.0556 | 0.0530 | 0.8894 |
| Mean | NaT | 0.8150 | 0.7053 | 68.3717 | 81.6027 | 0.0543 | 0.0548 | 0.8293 |
| SD | NaT | 0.1517 | 0.1658 | 9.1092 | 15.9884 | 0.0091 | 0.0104 | 0.1125 |
Creating: arima
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 3.0688 | 2.3258 | 238.9059 | 253.0125 | 0.1953 | 0.2043 | -1.1137 |
| 1 | 2023-03 | 0.6672 | 0.5628 | 57.2550 | 66.8977 | 0.0394 | 0.0405 | 0.9115 |
| 2 | 2024-03 | 0.3679 | 0.2997 | 33.2678 | 36.7311 | 0.0247 | 0.0244 | 0.9790 |
| Mean | NaT | 1.3680 | 1.0628 | 109.8095 | 118.8804 | 0.0865 | 0.0897 | 0.2589 |
| SD | NaT | 1.2088 | 0.8996 | 91.8087 | 95.6419 | 0.0772 | 0.0813 | 0.9710 |
Creating: et_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.2907 | 1.1048 | 100.4847 | 120.1832 | 0.0856 | 0.0826 | 0.5231 |
| 1 | 2023-03 | 0.9880 | 0.8068 | 84.7826 | 95.9076 | 0.0586 | 0.0603 | 0.8181 |
| 2 | 2024-03 | 0.4980 | 0.4608 | 45.0310 | 56.4819 | 0.0362 | 0.0352 | 0.9503 |
| Mean | NaT | 0.9256 | 0.7908 | 76.7661 | 90.8576 | 0.0601 | 0.0594 | 0.7638 |
| SD | NaT | 0.3266 | 0.2632 | 23.3378 | 26.2500 | 0.0202 | 0.0194 | 0.1786 |
Creating: omp_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.8654 | 0.6910 | 67.3718 | 75.1735 | 0.0593 | 0.0583 | 0.8134 |
| 1 | 2023-03 | 0.9486 | 0.7317 | 81.4045 | 86.9760 | 0.0624 | 0.0617 | 0.8504 |
| 2 | 2024-03 | 0.9396 | 0.8273 | 84.9619 | 101.4115 | 0.0665 | 0.0642 | 0.8399 |
| Mean | NaT | 0.9179 | 0.7500 | 77.9127 | 87.8537 | 0.0627 | 0.0614 | 0.8346 |
| SD | NaT | 0.0373 | 0.0571 | 7.5938 | 10.7296 | 0.0029 | 0.0024 | 0.0156 |
Creating: gbr_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.4489 | 1.2430 | 112.7988 | 135.2171 | 0.0958 | 0.0930 | 0.3963 |
| 1 | 2023-03 | 1.0486 | 0.8428 | 89.9845 | 100.1785 | 0.0626 | 0.0644 | 0.8015 |
| 2 | 2024-03 | 0.5404 | 0.4541 | 48.8675 | 55.6638 | 0.0370 | 0.0363 | 0.9518 |
| Mean | NaT | 1.0126 | 0.8466 | 83.8836 | 97.0198 | 0.0652 | 0.0645 | 0.7165 |
| SD | NaT | 0.3717 | 0.3221 | 26.4540 | 32.5542 | 0.0241 | 0.0231 | 0.2346 |
Tuning: exp_smooth
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.7210 | 0.6546 | 56.1295 | 71.2073 | 0.0458 | 0.0471 | 0.8326 |
| 1 | 2023-03 | 0.5332 | 0.4535 | 45.7605 | 53.9062 | 0.0345 | 0.0349 | 0.9425 |
| 2 | 2024-03 | 0.7777 | 0.6945 | 70.3248 | 85.1325 | 0.0560 | 0.0536 | 0.8872 |
| Mean | NaT | 0.6773 | 0.6009 | 57.4049 | 70.0820 | 0.0454 | 0.0452 | 0.8874 |
| SD | NaT | 0.1045 | 0.1055 | 10.0688 | 12.7729 | 0.0088 | 0.0078 | 0.0449 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 0.6s finished
Tuning: arima
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 2.2248 | 1.8587 | 173.2023 | 202.1963 | 0.1507 | 0.1451 | -0.3499 |
| 1 | 2023-03 | 1.3013 | 1.1206 | 111.6712 | 133.2062 | 0.0757 | 0.0798 | 0.6491 |
| 2 | 2024-03 | 0.3656 | 0.3009 | 33.0603 | 36.8801 | 0.0240 | 0.0239 | 0.9788 |
| Mean | NaT | 1.2972 | 1.0934 | 105.9779 | 124.0942 | 0.0834 | 0.0829 | 0.4260 |
| SD | NaT | 0.7590 | 0.6363 | 57.3542 | 67.7969 | 0.0520 | 0.0495 | 0.5649 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 0.8s finished
Tuning: et_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.4945 | 1.3302 | 116.3518 | 144.7052 | 0.1048 | 0.0969 | 0.3086 |
| 1 | 2023-03 | 0.7065 | 0.5791 | 60.6265 | 68.8395 | 0.0436 | 0.0446 | 0.9063 |
| 2 | 2024-03 | 0.4087 | 0.3400 | 36.9510 | 41.6715 | 0.0268 | 0.0268 | 0.9730 |
| Mean | NaT | 0.8699 | 0.7498 | 71.3098 | 85.0721 | 0.0584 | 0.0561 | 0.7293 |
| SD | NaT | 0.4581 | 0.4219 | 33.2838 | 43.6013 | 0.0335 | 0.0298 | 0.2987 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 1.9s finished
Tuning: omp_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 1.0662 | 0.8210 | 83.0038 | 89.3081 | 0.0712 | 0.0686 | 0.7366 |
| 1 | 2023-03 | 0.5204 | 0.4508 | 44.6565 | 53.5896 | 0.0364 | 0.0361 | 0.9432 |
| 2 | 2024-03 | 0.5792 | 0.4769 | 52.3710 | 58.4544 | 0.0410 | 0.0399 | 0.9468 |
| Mean | NaT | 0.7219 | 0.5829 | 60.0104 | 67.1174 | 0.0495 | 0.0482 | 0.8756 |
| SD | NaT | 0.2446 | 0.1687 | 16.5610 | 15.8164 | 0.0155 | 0.0145 | 0.0982 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 0.9s finished
Tuning: gbr_cds_dt
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.5874 | 0.5166 | 45.7298 | 56.1968 | 0.0397 | 0.0388 | 0.8957 |
| 1 | 2023-03 | 1.5374 | 1.1867 | 131.9332 | 141.0611 | 0.0950 | 0.1003 | 0.6065 |
| 2 | 2024-03 | 0.7360 | 0.6098 | 66.5468 | 74.7480 | 0.0505 | 0.0497 | 0.9130 |
| Mean | NaT | 0.9536 | 0.7710 | 81.4033 | 90.6686 | 0.0617 | 0.0630 | 0.8051 |
| SD | NaT | 0.4172 | 0.2964 | 36.7268 | 36.4288 | 0.0239 | 0.0268 | 0.1406 |
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 10 concurrent workers. [Parallel(n_jobs=-1)]: Done 30 out of 30 | elapsed: 1.5s finished
3.4 Blend and Stack (Setup B)¶
# Blend – weighted average of top 3 models
blended_B = exp2.blend_models(best_B)
print("Blended model created – Setup B")
| cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2022-03 | 0.8326 | 0.6504 | 64.8159 | 70.7484 | 0.0536 | 0.0536 | 0.8347 |
| 1 | 2023-03 | 0.8681 | 0.6515 | 74.4963 | 77.4452 | 0.0545 | 0.0550 | 0.8814 |
| 2 | 2024-03 | 0.6663 | 0.6255 | 60.2456 | 76.6786 | 0.0492 | 0.0473 | 0.9085 |
| Mean | NaT | 0.7890 | 0.6425 | 66.5193 | 74.9574 | 0.0524 | 0.0520 | 0.8749 |
| SD | NaT | 0.0880 | 0.0120 | 5.9412 | 2.9926 | 0.0023 | 0.0034 | 0.0304 |
Blended model created – Setup B
3.5 Decomposition, ACF and Diagnostics (Setup B)¶
# Seasonal decomposition and ACF – best tuned model
exp2.plot_model(resultados_t_B['exp_smooth'], plot='decomp')
exp2.plot_model(resultados_t_B['exp_smooth'], plot='acf')
for model_name in tune_B:
print(f'Diagnostics: {model_name}')
exp2.plot_model(estimator=resultados_t_B[model_name], plot='diagnostics')
Diagnostics: exp_smooth
Diagnostics: arima
Diagnostics: et_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. Diagnostics: omp_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. Diagnostics: gbr_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret.
for model_name in tune_B:
print(f'In-sample: {model_name}')
exp2.plot_model(resultados_t_B[model_name], plot='insample')
In-sample: exp_smooth
In-sample: arima
In-sample: et_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In-sample: omp_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret. In-sample: gbr_cds_dt In sample predictions has not been implemented for this estimator of type 'BaseCdsDtForecaster' in `sktime`. When this is implemented, it will be enabled by default in pycaret.
3.6 Finalize and Store Forecasts (Setup B)¶
forecasts_B = {}
resultados_final_B = {}
# Include blend and stack alongside individual models
all_models_B = {**resultados_B, 'blend': blended_B}
for model_name, model_obj in all_models_B.items():
source = resultados_t_B.get(model_name, model_obj)
final_model = exp2.finalize_model(source)
pred = exp2.predict_model(final_model, fh=12)
forecasts_B[model_name] = pred['y_pred']
resultados_final_B[model_name] = final_model
print(f'Forecast – {model_name}:')
display(pred)
# Forecast with 90% confidence interval
exp2.plot_model(final_model, plot='forecast', data_kwargs={'fh': 12})
print()
Forecast – exp_smooth:
| y_pred | |
|---|---|
| 2026-04 | 1560.9654 |
| 2026-05 | 1677.9206 |
| 2026-06 | 1772.7468 |
| 2026-07 | 1952.0170 |
| 2026-08 | 1965.9407 |
| 2026-09 | 1720.8029 |
| 2026-10 | 1637.8686 |
| 2026-11 | 1346.1215 |
| 2026-12 | 1316.0814 |
| 2027-01 | 1256.8653 |
| 2027-02 | 1248.8532 |
| 2027-03 | 1402.8660 |
Forecast – arima:
| y_pred | |
|---|---|
| 2026-04 | 1566.4898 |
| 2026-05 | 1704.6223 |
| 2026-06 | 1767.7433 |
| 2026-07 | 1923.9976 |
| 2026-08 | 1971.5737 |
| 2026-09 | 1708.8255 |
| 2026-10 | 1640.9376 |
| 2026-11 | 1326.3116 |
| 2026-12 | 1323.4354 |
| 2027-01 | 1244.0834 |
| 2027-02 | 1200.0322 |
| 2027-03 | 1450.0576 |
Forecast – et_cds_dt:
| y_pred | |
|---|---|
| 2026-04 | 1538.1621 |
| 2026-05 | 1648.7140 |
| 2026-06 | 1751.9956 |
| 2026-07 | 1913.4137 |
| 2026-08 | 1933.8756 |
| 2026-09 | 1693.3180 |
| 2026-10 | 1605.3448 |
| 2026-11 | 1326.0590 |
| 2026-12 | 1295.6060 |
| 2027-01 | 1199.4461 |
| 2027-02 | 1198.7948 |
| 2027-03 | 1437.5339 |
Forecast – omp_cds_dt:
| y_pred | |
|---|---|
| 2026-04 | 1580.8557 |
| 2026-05 | 1673.4297 |
| 2026-06 | 1758.9376 |
| 2026-07 | 1917.8768 |
| 2026-08 | 1928.3096 |
| 2026-09 | 1688.5507 |
| 2026-10 | 1600.3482 |
| 2026-11 | 1324.8890 |
| 2026-12 | 1295.2040 |
| 2027-01 | 1205.1776 |
| 2027-02 | 1202.0565 |
| 2027-03 | 1431.2530 |
Forecast – gbr_cds_dt:
| y_pred | |
|---|---|
| 2026-04 | 1596.0229 |
| 2026-05 | 1729.8815 |
| 2026-06 | 1850.1600 |
| 2026-07 | 2057.7315 |
| 2026-08 | 2085.9122 |
| 2026-09 | 1851.0805 |
| 2026-10 | 1768.1470 |
| 2026-11 | 1479.5906 |
| 2026-12 | 1464.3070 |
| 2027-01 | 1373.3573 |
| 2027-02 | 1382.6150 |
| 2027-03 | 1670.9396 |
Forecast – blend:
| y_pred | |
|---|---|
| 2026-04 | 1557.8026 |
| 2026-05 | 1664.1409 |
| 2026-06 | 1748.8814 |
| 2026-07 | 1906.3237 |
| 2026-08 | 1925.7698 |
| 2026-09 | 1711.8875 |
| 2026-10 | 1618.0851 |
| 2026-11 | 1377.9164 |
| 2026-12 | 1351.4142 |
| 2027-01 | 1303.8262 |
| 2027-02 | 1301.5762 |
| 2027-03 | 1437.5363 |
4. Forecast Comparison – All Models¶
Forecasts collected from predict_model during the finalize loop — no dependency on pull().
# Build comparison tables from stored dictionaries
df_forecast_A = pd.DataFrame(forecasts_A)
df_forecast_A.index = df_forecast_A.index.astype(str)
df_forecast_A.columns = [f'{c}_A' for c in df_forecast_A.columns]
df_forecast_B = pd.DataFrame(forecasts_B)
df_forecast_B.index = df_forecast_B.index.astype(str)
df_forecast_B.columns = [f'{c}_B' for c in df_forecast_B.columns]
df_comparison = pd.concat([df_forecast_A, df_forecast_B], axis=1)
df_comparison.index.name = 'Period'
print('12-Month Forecast – Setup A (with COVID) vs Setup B (without COVID):')
print(df_comparison.round(0).astype(int).to_string())
12-Month Forecast – Setup A (with COVID) vs Setup B (without COVID):
exp_smooth_A arima_A ets_A lasso_cds_dt_A lr_cds_dt_A blend_A exp_smooth_B arima_B et_cds_dt_B omp_cds_dt_B gbr_cds_dt_B blend_B
Period
2026-04 1582 1550 1574 1542 1558 1587 1561 1566 1538 1581 1596 1558
2026-05 1721 1657 1711 1629 1641 1705 1678 1705 1649 1673 1730 1664
2026-06 1833 1736 1823 1705 1709 1784 1773 1768 1752 1759 1850 1749
2026-07 2042 1882 2032 1932 1840 1948 1952 1924 1913 1918 2058 1906
2026-08 2070 1926 2061 2057 1832 1968 1966 1972 1934 1928 2086 1926
2026-09 1794 1755 1785 1793 1625 1718 1721 1709 1693 1689 1851 1712
2026-10 1706 1701 1697 1681 1531 1634 1638 1641 1605 1600 1768 1618
2026-11 1370 1489 1360 1320 1289 1334 1346 1326 1326 1325 1480 1378
2026-12 1340 1467 1327 1285 1243 1306 1316 1323 1296 1295 1464 1351
2027-01 1255 1410 1239 1149 1158 1221 1257 1244 1199 1205 1373 1304
2027-02 1254 1412 1237 1124 1161 1205 1249 1200 1199 1202 1383 1302
2027-03 1465 1546 1444 1269 1290 1399 1403 1450 1438 1431 1671 1438
# Line chart – all Setup B models including blend and stack
fig, ax = plt.subplots(figsize=(14, 6))
colors_map = {
'exp_smooth' : '#2ecc71',
'arima' : '#3498db',
'ets' : '#9b59b6',
'knn_cds_dt' : '#e67e22',
'llar_cds_dt': '#1abc9c',
'lasso_cds_dt':'#e74c3c',
'blend' : '#2c3e50',
'stack' : '#8e44ad'
}
for model_name, series in forecasts_B.items():
lw = 3 if model_name in ['blend', 'stack'] else 1.5
ls = '-' if model_name in ['blend', 'stack'] else '--'
color = colors_map.get(model_name, 'grey')
ax.plot(series.index.astype(str), series.values,
marker='o', label=f'{model_name} (no COVID)',
color=color, lw=lw, ls=ls, ms=5)
# Overlay exp_smooth from Setup A
if 'exp_smooth' in forecasts_A:
s = forecasts_A['exp_smooth']
ax.plot(s.index.astype(str), s.values,
'r--s', label='exp_smooth (with COVID)', lw=2, ms=5)
ax.set_xlabel('Month')
ax.set_ylabel('Passengers (thousands)')
ax.set_title('PyCaret – 12-Month Forecast: All Models + Ensemble')
ax.legend(loc='upper right', fontsize=8, ncol=2)
ax.grid(True, alpha=0.3)
plt.xticks(rotation=30, ha='right')
plt.tight_layout()
plt.show()
# Bar chart – peak month forecast by model
july_str = [i for i in list(forecasts_B.values())[0].index.astype(str) if '-07' in i]
if july_str:
july_period = july_str[0]
july_vals = {m: forecasts_B[m][forecasts_B[m].index.astype(str) == july_period].values[0]
for m in forecasts_B}
fig, ax = plt.subplots(figsize=(12, 5))
colors = [colors_map.get(m, 'grey') for m in july_vals]
bars = ax.bar(july_vals.keys(), july_vals.values(), color=colors, alpha=0.85)
for b in bars:
ax.text(b.get_x()+b.get_width()/2, b.get_height()+5,
f'{b.get_height():,.0f}k', ha='center', fontsize=9)
ax.set_ylabel('Passengers (thousands)')
ax.set_title(f'PyCaret Setup B – Peak Month ({july_period}) Forecast by Model')
ax.grid(True, alpha=0.3, axis='y')
plt.xticks(rotation=20, ha='right')
plt.tight_layout()
plt.show()
# Blend vs Stack vs best individual – direct comparison
fig, ax = plt.subplots(figsize=(12, 5))
highlight = ['exp_smooth', 'arima', 'blend', 'stack']
styles = ['g--', 'b--', 'k-', 'm-']
lws = [1.5, 1.5, 2.5, 2.5]
for model_name, style, lw in zip(highlight, styles, lws):
if model_name in forecasts_B:
s = forecasts_B[model_name]
ax.plot(s.index.astype(str), s.values,
style, label=model_name, lw=lw, marker='o', ms=5)
ax.set_xlabel('Month')
ax.set_ylabel('Passengers (thousands)')
ax.set_title('PyCaret Setup B – Individual vs Ensemble Forecasts')
ax.legend()
ax.grid(True, alpha=0.3)
plt.xticks(rotation=30, ha='right')
plt.tight_layout()
plt.show()
# exp_smooth: with COVID vs without COVID
if 'exp_smooth' in forecasts_A and 'exp_smooth' in forecasts_B:
s_A = forecasts_A['exp_smooth']
s_B = forecasts_B['exp_smooth']
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(s_A.index.astype(str), s_A.values,
'r--o', label='exp_smooth – With COVID', lw=2, ms=6)
ax.plot(s_B.index.astype(str), s_B.values,
'g-o', label='exp_smooth – No COVID', lw=2, ms=6)
ax.set_xlabel('Month')
ax.set_ylabel('Passengers (thousands)')
ax.set_title('exp_smooth: Impact of COVID Period on Forecast')
ax.legend()
ax.grid(True, alpha=0.3)
plt.xticks(range(len(s_A)), s_A.index.astype(str), rotation=30, ha='right')
plt.tight_layout()
plt.show()
4.4 Forecast vs Actual CSO Data¶
# Dados reais (Abr 2025 – Mar 2026) + Forecast ETS Setup A (Abr 2026 – Mar 2027)
# ETS foi o melhor modelo no Setup A (com COVID)
data_A = {
'2025-04': 1519.066, '2025-05': 1657.095, '2025-06': 1720.507,
'2025-07': 1877.294, '2025-08': 1924.106, '2025-09': 1661.162,
'2025-10': 1593.932, '2025-11': 1279.279, '2025-12': 1276.540,
'2026-01': 1197.109, '2026-02': 1152.538, '2026-03': 1402.866,
'2026-04': 1573.684, '2026-05': 1711.488, '2026-06': 1822.999,
'2026-07': 2032.111, '2026-08': 2060.745, '2026-09': 1784.922,
'2026-10': 1696.599, '2026-11': 1360.367, '2026-12': 1326.905,
'2027-01': 1239.111, '2027-02': 1237.357, '2027-03': 1443.539
}
df_ets_A = pd.DataFrame.from_dict(data_A, orient='index', columns=['Dublin_airport'])
df_ets_A.index.name = 'Date'
df_ets_A['MoM_%'] = df_ets_A['Dublin_airport'].pct_change() * 100
df_ets_A['YoY_%'] = df_ets_A['Dublin_airport'].pct_change(periods=12) * 100
df_ets_A = df_ets_A.round(2)
df_ets_A['Source'] = ['Actual'] * 12 + ['ETS Forecast (Setup A)'] * 12
print('ETS Setup A – Actual + Forecast:')
print(df_ets_A.to_string())
ETS Setup A – Actual + Forecast:
Dublin_airport MoM_% YoY_% Source
Date
2025-04 1519.07 NaN NaN Actual
2025-05 1657.10 9.09 NaN Actual
2025-06 1720.51 3.83 NaN Actual
2025-07 1877.29 9.11 NaN Actual
2025-08 1924.11 2.49 NaN Actual
2025-09 1661.16 -13.67 NaN Actual
2025-10 1593.93 -4.05 NaN Actual
2025-11 1279.28 -19.74 NaN Actual
2025-12 1276.54 -0.21 NaN Actual
2026-01 1197.11 -6.22 NaN Actual
2026-02 1152.54 -3.72 NaN Actual
2026-03 1402.87 21.72 NaN Actual
2026-04 1573.68 12.18 3.60 ETS Forecast (Setup A)
2026-05 1711.49 8.76 3.28 ETS Forecast (Setup A)
2026-06 1823.00 6.52 5.96 ETS Forecast (Setup A)
2026-07 2032.11 11.47 8.25 ETS Forecast (Setup A)
2026-08 2060.74 1.41 7.10 ETS Forecast (Setup A)
2026-09 1784.92 -13.38 7.45 ETS Forecast (Setup A)
2026-10 1696.60 -4.95 6.44 ETS Forecast (Setup A)
2026-11 1360.37 -19.82 6.34 ETS Forecast (Setup A)
2026-12 1326.90 -2.46 3.95 ETS Forecast (Setup A)
2027-01 1239.11 -6.62 3.51 ETS Forecast (Setup A)
2027-02 1237.36 -0.14 7.36 ETS Forecast (Setup A)
2027-03 1443.54 16.66 2.90 ETS Forecast (Setup A)
# Dados reais (Abr 2025 – Mar 2026) + Forecast exp_smooth Setup B (Abr 2026 – Mar 2027)
# Exponential Smoothing foi o melhor modelo no Setup B (sem COVID)
data_B = {
'2025-04': 1519.066, '2025-05': 1657.095, '2025-06': 1720.507,
'2025-07': 1877.294, '2025-08': 1924.106, '2025-09': 1661.162,
'2025-10': 1593.932, '2025-11': 1279.279, '2025-12': 1276.540,
'2026-01': 1197.109, '2026-02': 1152.538, '2026-03': 1402.866,
'2026-04': 1560.965, '2026-05': 1677.921, '2026-06': 1772.747,
'2026-07': 1952.017, '2026-08': 1965.941, '2026-09': 1720.803,
'2026-10': 1637.869, '2026-11': 1346.122, '2026-12': 1316.081,
'2027-01': 1256.865, '2027-02': 1248.853, '2027-03': 1402.866
}
df_exp_B = pd.DataFrame.from_dict(data_B, orient='index', columns=['Dublin_airport'])
df_exp_B.index.name = 'Date'
df_exp_B['MoM_%'] = df_exp_B['Dublin_airport'].pct_change() * 100
df_exp_B['YoY_%'] = df_exp_B['Dublin_airport'].pct_change(periods=12) * 100
df_exp_B = df_exp_B.round(2)
df_exp_B['Source'] = ['Actual'] * 12 + ['exp_smooth Forecast (Setup B)'] * 12
print('exp_smooth Setup B – Actual + Forecast:')
print(df_exp_B.to_string())
exp_smooth Setup B – Actual + Forecast:
Dublin_airport MoM_% YoY_% Source
Date
2025-04 1519.07 NaN NaN Actual
2025-05 1657.10 9.09 NaN Actual
2025-06 1720.51 3.83 NaN Actual
2025-07 1877.29 9.11 NaN Actual
2025-08 1924.11 2.49 NaN Actual
2025-09 1661.16 -13.67 NaN Actual
2025-10 1593.93 -4.05 NaN Actual
2025-11 1279.28 -19.74 NaN Actual
2025-12 1276.54 -0.21 NaN Actual
2026-01 1197.11 -6.22 NaN Actual
2026-02 1152.54 -3.72 NaN Actual
2026-03 1402.87 21.72 NaN Actual
2026-04 1560.96 11.27 2.76 exp_smooth Forecast (Setup B)
2026-05 1677.92 7.49 1.26 exp_smooth Forecast (Setup B)
2026-06 1772.75 5.65 3.04 exp_smooth Forecast (Setup B)
2026-07 1952.02 10.11 3.98 exp_smooth Forecast (Setup B)
2026-08 1965.94 0.71 2.17 exp_smooth Forecast (Setup B)
2026-09 1720.80 -12.47 3.59 exp_smooth Forecast (Setup B)
2026-10 1637.87 -4.82 2.76 exp_smooth Forecast (Setup B)
2026-11 1346.12 -17.81 5.23 exp_smooth Forecast (Setup B)
2026-12 1316.08 -2.23 3.10 exp_smooth Forecast (Setup B)
2027-01 1256.86 -4.50 4.99 exp_smooth Forecast (Setup B)
2027-02 1248.85 -0.64 8.36 exp_smooth Forecast (Setup B)
2027-03 1402.87 12.33 0.00 exp_smooth Forecast (Setup B)
5. References¶
Ali, M. (2020) PyCaret: An open source, low-code machine learning library in Python. Available at: https://pycaret.org (Accessed: March 2026).
Central Statistics Office (2025) Air and Sea Travel Statistics – ASM01. Available at: https://data.cso.ie/table/ASM01 (Accessed: January 2026).
Moez Ali (2020) PyCaret: An open source, low-code machine learning library in Python. Available at: https://pycaret.org (Accessed: March 2026).
PJalgotrader (2024) PyCaret Time Series Notebook – Deep Forecasting USU. Available at: https://github.com/PJalgotrader/Deep_forecasting-USU/blob/main/Platforms%20and%20tools/PyCaret/PyCaret-timeseries.ipynb (Accessed: March 2026).
PyCaret Documentation (2024) Other Setup Parameters – Preprocessing. Available at: https://pycaret.gitbook.io/docs/get-started/preprocessing/other-setup-parameters (Accessed: March 2026).
PyCaret Documentation (2024) Datasets API Reference. Available at: https://pycaret.readthedocs.io/en/latest/api/datasets.html#pycaret.datasets.get_data (Accessed: March 2026).
PyCaret Documentation (2024) Time Series Forecasting API. Available at: https://pycaret.readthedocs.io/en/stable/api/time_series.html (Accessed: March 2026).
Sktime developers (2024) sktime: A unified framework for machine learning with time series. Available at: https://www.sktime.net (Accessed: March 2026).
Hyndman, R.J. and Athanasopoulos, G. (2021) Forecasting: Principles and Practice, 3rd ed. OTexts. Available at: https://otexts.com/fpp3/ (Accessed: March 2026).
YouTube (2024) PyCaret Time Series Forecasting Tutorial. Available at: https://www.youtube.com/watch?v=V7K-pFxHop4 (Accessed: March 2026).
YouTube (2024) Time Series Forecasting with PyCaret. Available at: https://www.youtube.com/watch?v=uypdqSPdN60 (Accessed: March 2026).