If you like Skforecast , help us giving a star on GitHub! ⭐️
More about forecasting
In Single-Series Modeling (Local Forecasting Model), a single time series is modeled as a linear or nonlinear combination of its lags and exogenous variables. While this method provides a comprehensive understanding of each series, its scalability can be challenged when dealing with a large number of series.
Multi-Series Modeling (Global Forecasting Model) involves building a single predictive model that considers multiple time series simultaneously. It attempts to capture the core patterns that govern the series, thereby mitigating the potential noise that each series might introduce. This approach is computationally efficient, easy to maintain, and can yield more robust generalizations across time series, albeit potentially at the cost of sacrificing some individual insights. Two strategies of global forecasting models can be distinguished:
Independent multi-series forecasting
In independent multi-series forecasting, a single model is trained on all time series, but each remains independent of the others, meaning that past values of one series are not used as predictors of other series. Modeling them together is useful because the series may follow the same intrinsic pattern with respect to their past and future values. For example, the sales of products A and B in the same store may be unrelated, but they follow the same dynamic, that of the store.
To predict the next n steps, the strategy of recursive multi-step forecasting is applied, with the only difference that the series identifier for which the predictions are to be estimated must be specified.
Dependent multi-series forecasting (Multivariate forecasting)
In dependent multi-series forecasting (multivariate time series), all series are modeled together in a single model, considering that each time series depends not only on its past values but also on the past values of the other series. The forecaster is expected not only to learn the information of each series separately but also to relate them. An example is the measurements made by all the sensors (flow, temperature, pressure...) installed on an industrial machine such as a compressor.
🖉 Note
ForecasterAutoregMultiSeries
and ForecasterAutoregMultiSeriesCustom
classes cover the use case of independent multi-series forecasting. API Reference
ForecasterAutoregMultiVariate
class covers the use case of dependent multi-series forecasting (Multivariate forecasting). API Reference
Global forecasting models forecasts do not always outperform single-series forecasts. Which one works best depends largely on the characteristics of the use case to which they are applied. However, the following heuristic is worth keeping in mind:
Advantages of multi-series
It is easier to maintain and monitor a single model than several.
Since all time series are combined during training, the model has a higher learning capacity even if the series are short.
By combining multiple time series, the model can learn more generalizable patterns.
Limitations of multi-series
If the series do not follow the same internal dynamics, the model may learn a pattern that does not represent any of them.
The series may mask each other, so the model may not predict all of them with the same performance.
It is more computationally demanding (time and resources) to train and backtest a big model than several small ones.
The objective of this study is to compare the forecasting results of a global multi-series model with those of an individual model for each series.
The data has been obtained from the Store Item Demand Forecasting Challenge. This dataset contains 913,000 sales transactions from 01/01/2013 to 31/12/2017 for 50 products (SKU) in 10 stores. The goal is to predict the next 7 days sales for 50 different items in a store using the 5 years of available history.
# Data manipulation
# ==============================================================================
import sys
import os
import warnings
import numpy as np
import pandas as pd
# Plots
# ==============================================================================
import matplotlib.pyplot as plt
from tqdm.notebook import tqdm
from statsmodels.graphics.tsaplots import plot_acf
# Modelling and Forecasting
# ==============================================================================
import sklearn
import skforecast
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import RFECV
from sklearn.ensemble import HistGradientBoostingRegressor
from skforecast.ForecasterAutoregMultiSeries import ForecasterAutoregMultiSeries
from skforecast.ForecasterAutoreg import ForecasterAutoreg
from skforecast.model_selection import backtesting_forecaster
from skforecast.model_selection import bayesian_search_forecaster
from skforecast.model_selection_multiseries import backtesting_forecaster_multiseries
from skforecast.model_selection_multiseries import bayesian_search_forecaster_multiseries
from skforecast.model_selection_multiseries import select_features_multiseries
from skforecast.plot import set_dark_theme
from skforecast.preprocessing import series_long_to_dict
from skforecast.preprocessing import exog_long_to_dict
# Warnings configuration
# ==============================================================================
warnings.filterwarnings('once')
color = '\033[1m\033[38;5;208m'
print(f"{color}Version skforecast: {skforecast.__version__}")
print(f"{color}Version scikit-learn: {sklearn.__version__}")
print(f"{color}Version pandas: {pd.__version__}")
print(f"{color}Version numpy: {np.__version__}")
# Data loading
# ======================================================================================
data = pd.read_csv('./train_stores_kaggle.csv')
display(data)
print(f"Shape: {data.shape}")
# Data preprocessing
# ======================================================================================
selected_store = 2
selected_items = data.item.unique()
data = data[(data['store'] == selected_store) & (data['item'].isin(selected_items))].copy()
data['date'] = pd.to_datetime(data['date'], format='%Y-%m-%d')
data = pd.pivot_table(
data = data,
values = 'sales',
index = 'date',
columns = 'item'
)
data.columns.name = None
data.columns = [f"item_{col}" for col in data.columns]
data = data.asfreq('1D')
data = data.sort_index()
data.head(4)
The dataset is divided into 3 partitions: one for training, one for validation, and one for testing.
# Split data into train-validation-test
# ======================================================================================
end_train = '2016-05-31 23:59:00'
end_val = '2017-05-31 23:59:00'
data_train = data.loc[:end_train, :].copy()
data_val = data.loc[end_train:end_val, :].copy()
data_test = data.loc[end_val:, :].copy()
print(f"Train dates : {data_train.index.min()} --- {data_train.index.max()} (n={len(data_train)})")
print(f"Validation dates : {data_val.index.min()} --- {data_val.index.max()} (n={len(data_val)})")
print(f"Test dates : {data_test.index.min()} --- {data_test.index.max()} (n={len(data_test)})")
Four of the series are plotted to understand their trends and patterns. The reader is strongly encouraged to plot several more to gain an in-depth understanding of the series.
# Plot time series
# ======================================================================================
set_dark_theme()
fig, axs = plt.subplots(4, 1, figsize=(7, 5), sharex=True)
data.iloc[:, :4].plot(
legend = True,
subplots = True,
title = 'Sales of store 2',
ax = axs,
)
for ax in axs:
ax.axvline(pd.to_datetime(end_train) , color='white', linestyle='--', linewidth=1.5)
ax.axvline(pd.to_datetime(end_val) , color='white', linestyle='--', linewidth=1.5)
fig.tight_layout()
plt.show()
# Autocorrelation plot
# ======================================================================================
fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(7, 5), sharex=True)
axes = axes.flat
for i, col in enumerate(data.columns[:4]):
plot_acf(data[col], ax=axes[i], lags=7*5)
axes[i].set_ylim(-1, 1.1)
axes[i].set_title(f'{col}', fontsize=10)
fig.tight_layout()
plt.show()
The autocorrelation plots show a clear association between sales on one day and sales on the same day a week earlier (lag 7). This type of correlation is an indication that autoregressive models can work well. There is also a common weekly seasonality between the series. The more similar the dynamics between the series, the more likely the model will learn useful patterns.
An individual model (Gradient Boosting Machine) is trained for each item in the store and its mean absolute error in predicting sales over the next 7 days is estimated using backtesting.
# Train and backtest a model for each item: ForecasterAutoreg
# ======================================================================================
items = []
mae_values = []
predictions = {}
for i, item in enumerate(tqdm(data.columns)):
# Define forecaster
forecaster = ForecasterAutoreg(
regressor = HistGradientBoostingRegressor(random_state=8523),
lags = 14,
transformer_y = StandardScaler()
)
# Backtesting forecaster
metric, preds = backtesting_forecaster(
forecaster = forecaster,
y = data[item],
initial_train_size = len(data_train) + len(data_val),
steps = 7,
metric = 'mean_absolute_error',
refit = False,
fixed_train_size = False,
verbose = False,
show_progress = False
)
items.append(item)
mae_values.append(metric.at[0, 'mean_absolute_error'])
predictions[item] = preds
# Results
uni_series_mae = pd.Series(
data = mae_values,
index = items,
name = 'uni_series_mae'
)
A global model is trained on all series simultaneously. In the predict
and backtesting processes, you must specify the levels
at which predictions are to be performed. The argument can also be set to None
to perform prediction at all available levels.
# Train and backtest a model for all items: ForecasterAutoregMultiSeries
# ======================================================================================
items = list(data.columns)
# Define forecaster
forecaster_ms = ForecasterAutoregMultiSeries(
regressor = HistGradientBoostingRegressor(random_state=8523),
lags = 14,
encoding = 'ordinal',
transformer_series = StandardScaler(),
)
# Backtesting forecaster for all items
multi_series_mae, predictions_ms = backtesting_forecaster_multiseries(
forecaster = forecaster_ms,
series = data,
levels = items,
steps = 7,
metric = 'mean_absolute_error',
add_aggregated_metric = False,
initial_train_size = len(data_train) + len(data_val),
refit = False,
fixed_train_size = False,
verbose = False,
show_progress = True
)
# Results
display(multi_series_mae.head(3))
print('')
display(predictions_ms.head(3))
The mean absolute error (MAE) is calculated for each model and compared for each series.
# Difference of backtesting metric for each item
# ======================================================================================
multi_series_mae = multi_series_mae.set_index('levels')
multi_series_mae.columns = ['multi_series_mae']
results = pd.concat((uni_series_mae, multi_series_mae), axis = 1)
results['improvement'] = results.eval('uni_series_mae - multi_series_mae')
results['improvement_(%)'] = 100 * results.eval('(uni_series_mae - multi_series_mae) / uni_series_mae')
results = results.round(2)
results.style.bar(subset=['improvement_(%)'], align='mid', color=['#d65f5f', '#5fba7d'])
# Average improvement for all items
# ======================================================================================
results[['improvement', 'improvement_(%)']].agg(['mean', 'min', 'max'])
# Number of series with positive and negative improvement
# ======================================================================================
pd.Series(np.where(results['improvement_(%)'] < 0, 'negative', 'positive')).value_counts()
The global model achieves an average improvement of 6.6% compared to using an individual model for each series. For all series, the prediction error evaluated by backtesting is lower when the global model is used. This use case demonstrates that a multi-series model can have advantages over multiple individual models when forecasting time series that follow similar dynamics. In addition to the potential improvements in forecasting, it is also important to consider the benefit of having only one model to maintain and the speed of training and prediction.
⚠ Warning
This comparison was made without optimizing the model hyperparameters. See the Case study 3: Hyperparameter tuning and lags selection section to verify that the conclusions hold when the models are tuned with the best combination of hyperparameters and lags.When faced with a multi-series forecasting problem, it is common for the series to have varying lengths due to differences in the starting times of data recording. To address this scenario, the ForecasterAutoregMultiSeries and ForecasterAutoregMultiSeriesCustom classes allow the simultaneous modeling of time series of different lengths and using different exogenous variables.
pandas.Series
, have a datetime
index and have the same frequency.Series values | Allowed |
---|---|
[NaN, NaN, NaN, NaN, 4, 5, 6, 7, 8, 9] |
✔️ |
[0, 1, 2, 3, 4, 5, 6, 7, 8, NaN] |
✔️ |
[0, 1, 2, 3, 4, NaN, 6, 7, 8, 9] |
✔️ |
[NaN, NaN, 2, 3, 4, NaN, 6, 7, 8, 9] |
✔️ |
pandas.DataFrame
or pandas.Series
.The data for this example is stored in "long format" in a single DataFrame
. The series_id
column identifies the series to which each observation belongs. The timestamp
column contains the date of the observation, and the value
column contains the value of the series at that date. Each time series is of a different length.
The exogenous variables are stored in a separate DataFrame
, also in "long format". The column series_id
identifies the series to which each observation belongs. The column timestamp
contains the date of the observation, and the remaining columns contain the values of the exogenous variables at that date.
# Load time series of multiple lengths and exogenous variables
# ==============================================================================
series = pd.read_csv(
'https://raw.githubusercontent.com/JoaquinAmatRodrigo/skforecast-datasets/main/data/demo_multi_series.csv'
)
exog = pd.read_csv(
'https://raw.githubusercontent.com/JoaquinAmatRodrigo/skforecast-datasets/main/data/demo_multi_series_exog.csv'
)
series['timestamp'] = pd.to_datetime(series['timestamp'])
exog['timestamp'] = pd.to_datetime(exog['timestamp'])
display(series.head())
print("")
display(exog.head())
When series have different lengths, the data must be transformed into a dictionary. The keys of the dictionary are the names of the series and the values are the series themselves. To do this, the series_long_to_dict
function is used, which takes the DataFrame
in "long format" and returns a dict
of series.
Similarly, when the exogenous variables are different (values or variables) for each series, the data must be transformed into a dictionary. The keys of the dictionary are the names of the series and the values are the exogenous variables themselves. The exog_long_to_dict
function is used, which takes the DataFrame
in "long format" and returns a dict
of exogenous variables.
# Transform series and exog to dictionaries
# ==============================================================================
series_dict = series_long_to_dict(
data = series,
series_id = 'series_id',
index = 'timestamp',
values = 'value',
freq = 'D'
)
exog_dict = exog_long_to_dict(
data = exog,
series_id = 'series_id',
index = 'timestamp',
freq = 'D'
)
Some exogenous variables are omitted for series 1 and 3 to illustrate that different exogenous variables can be used for each series.
# Drop some exogenous variables for series 'id_1000' and 'id_1003'
# ==============================================================================
exog_dict['id_1000'] = exog_dict['id_1000'].drop(columns=['air_temperature', 'wind_speed'])
exog_dict['id_1003'] = exog_dict['id_1003'].drop(columns=['cos_day_of_week'])
# Partition data in train and test
# ==============================================================================
end_train = '2016-07-31 23:59:00'
series_dict_train = {k: v.loc[: end_train,] for k, v in series_dict.items()}
exog_dict_train = {k: v.loc[: end_train,] for k, v in exog_dict.items()}
series_dict_test = {k: v.loc[end_train:,] for k, v in series_dict.items()}
exog_dict_test = {k: v.loc[end_train:,] for k, v in exog_dict.items()}
# Plot series
# ==============================================================================
set_dark_theme()
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
fig, axs = plt.subplots(5, 1, figsize=(8, 4), sharex=True)
for i, s in enumerate(series_dict.values()):
axs[i].plot(s, label=s.name, color=colors[i])
axs[i].legend(loc='upper right', fontsize=8)
axs[i].tick_params(axis='both', labelsize=8)
axs[i].axvline(pd.to_datetime(end_train) , color='white', linestyle='--', linewidth=1) # End train
# Description of each series
# ==============================================================================
for k in series_dict.keys():
print(f"{k}:")
try:
print(
f"\tTrain: len={len(series_dict_train[k])}, {series_dict_train[k].index[0]}"
f" --- {series_dict_train[k].index[-1]}"
)
except:
print(f"\tTrain: len=0")
try:
print(
f"\tTest : len={len(series_dict_test[k])}, {series_dict_test[k].index[0]}"
f" --- {series_dict_test[k].index[-1]}"
)
except:
print(f"\tTest : len=0")
# Exogenous variables for each series
# ==============================================================================
for k in series_dict.keys():
print(f"{k}:")
try:
print(f"\t{exog_dict[k].columns.to_list()}")
except:
print(f"\tNo exogenous variables")
# Fit forecaster
# ==============================================================================
regressor = HistGradientBoostingRegressor(random_state=123, max_depth=5)
forecaster = ForecasterAutoregMultiSeries(
regressor = regressor,
lags = 14,
encoding = "ordinal",
dropna_from_series = False
)
forecaster.fit(series=series_dict_train, exog=exog_dict_train, suppress_warnings=True)
forecaster
Only series whose last window of data ends at the same datetime index can be predicted together. If levels = None
, series that do not reach the maximum index are excluded from prediction. In this example, series 'id_1002'
is excluded because it does not reach the maximum index.
# Predict
# ==============================================================================
predictions = forecaster.predict(steps=5, exog=exog_dict_test, suppress_warnings=True)
predictions
When series have different lengths, the backtesting process only returns predictions for the date-times that are present in the series.
# Backtesting
# ==============================================================================
forecaster = ForecasterAutoregMultiSeries(
regressor = regressor,
lags = 14,
encoding = "ordinal",
dropna_from_series = False
)
metrics_levels, backtest_predictions = backtesting_forecaster_multiseries(
forecaster = forecaster,
series = series_dict,
exog = exog_dict,
steps = 24,
metric = "mean_absolute_error",
add_aggregated_metric = False,
initial_train_size = len(series_dict_train["id_1000"]),
fixed_train_size = True,
gap = 0,
allow_incomplete_fold = True,
refit = False,
n_jobs ="auto",
verbose = True,
show_progress = True,
suppress_warnings = True
)
display(metrics_levels)
print("")
display(backtest_predictions)
# Plot backtesting predictions
# ==============================================================================
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
fig, axs = plt.subplots(5, 1, figsize=(8, 4), sharex=True)
for i, s in enumerate(series_dict.keys()):
axs[i].plot(series_dict[s], label=series_dict[s].name, color=colors[i])
axs[i].axvline(pd.to_datetime(end_train) , color='white', linestyle='--', linewidth=1)
try:
axs[i].plot(backtest_predictions[s], label='prediction', color="white")
except:
pass
axs[i].legend(loc='upper left', fontsize=8)
axs[i].tick_params(axis='both', labelsize=8)
By allowing the modeling of time series of different lengths and with different exogenous variables, the ForecasterAutoregMultiSeries class provides a flexible and powerful tool for using all available information to train the forecasting models.
In case study 1, the comparison between forecasters was done without optimizing the hyperparameters of the regressors. To make a fair comparison, a grid search strategy is used in order to select the best configuration for each forecaster. See more information on hyperparameter tuning and lags selection.
⚠ Warning
The following section may require significant computational time (about 45 minutes). Feel free to select only a subset of items to speed up the execution.# Hyperparameter search and backtesting of each item's model
# ======================================================================================
with open(os.devnull, 'w') as devnull: # Hide prints
sys.stdout = devnull
items = []
mae_values = []
def search_space(trial):
search_space = {
'lags' : trial.suggest_categorical('lags', [7, 14]),
'max_iter' : trial.suggest_int('max_iter', 100, 500),
'max_depth' : trial.suggest_int('max_depth', 5, 10),
'learning_rate' : trial.suggest_float('learning_rate', 0.01, 0.1)
}
return search_space
for item in tqdm(data.columns):
forecaster = ForecasterAutoreg(
regressor = HistGradientBoostingRegressor(random_state=123),
lags = 14,
transformer_y = StandardScaler()
)
results_bayesian = bayesian_search_forecaster(
forecaster = forecaster,
y = data.loc[:end_val, item],
search_space = search_space,
n_trials = 20,
steps = 7,
metric = 'mean_absolute_error',
initial_train_size = len(data_train),
refit = False,
fixed_train_size = False,
return_best = True,
verbose = False,
show_progress = False
)
metric, preds = backtesting_forecaster(
forecaster = forecaster,
y = data[item],
initial_train_size = len(data_train) + len(data_val),
steps = 7,
metric = 'mean_absolute_error',
refit = False,
fixed_train_size = False,
verbose = False,
show_progress = False
)
items.append(item)
mae_values.append(metric.at[0, 'mean_absolute_error'])
uni_series_mae = pd.Series(
data = mae_values,
index = items,
name = 'uni_series_mae'
)
sys.stdout = sys.__stdout__
# Hyperparameter search for the multi-series model and backtesting for each item
# ======================================================================================
def search_space(trial):
search_space = {
'lags' : trial.suggest_categorical('lags', [7, 14]),
'max_iter' : trial.suggest_int('max_iter', 100, 500),
'max_depth' : trial.suggest_int('max_depth', 5, 10),
'learning_rate' : trial.suggest_float('learning_rate', 0.01, 0.1)
}
return search_space
forecaster_ms = ForecasterAutoregMultiSeries(
regressor = HistGradientBoostingRegressor(random_state=123),
lags = 14,
transformer_series = StandardScaler(),
encoding = 'ordinal'
)
results_bayesian_ms = bayesian_search_forecaster_multiseries(
forecaster = forecaster_ms,
series = data.loc[:end_val, :],
levels = None, # If None all levels are selected
search_space = search_space,
n_trials = 20,
steps = 7,
metric = 'mean_absolute_error',
initial_train_size = len(data_train),
refit = False,
fixed_train_size = False,
return_best = True,
verbose = False,
show_progress = False
)
multi_series_mae, predictions_ms = backtesting_forecaster_multiseries(
forecaster = forecaster_ms,
series = data,
levels = None, # If None all levels are selected
steps = 7,
metric = 'mean_absolute_error',
add_aggregated_metric = False,
initial_train_size = len(data_train) + len(data_val),
refit = False,
fixed_train_size = False,
verbose = False
)
# Difference in backtesting metric for each item
# ======================================================================================
multi_series_mae = multi_series_mae.set_index('levels')
multi_series_mae.columns = ['multi_series_mae']
results = pd.concat((uni_series_mae, multi_series_mae), axis = 1)
results['improvement'] = results.eval('uni_series_mae - multi_series_mae')
results['improvement_(%)'] = 100 * results.eval('(uni_series_mae - multi_series_mae) / uni_series_mae')
results = results.round(2)
# Average improvement for all items
# ======================================================================================
results[['improvement', 'improvement_(%)']].agg(['mean', 'min', 'max'])
# Number of series with positive and negative improvement
# ======================================================================================
pd.Series(np.where(results['improvement_(%)'] < 0, 'negative', 'positive')).value_counts()
After identifying the combination of lags and hyperparameters that achieve the best predictive performance for each forecaster, more single-series models have achieved higher predictive ability by better generalizing their own data (one item). Even so, the multi-series model provides better results for most of the items.
Feature selection is the process of selecting a subset of relevant features (variables, predictors) for use in model construction. Feature selection techniques are used for several reasons: to simplify models to make them easier to interpret, to reduce training time, to avoid the curse of dimensionality, to improve generalization by reducing overfitting (formally, variance reduction), and others.
Skforecast is compatible with the feature selection methods implemented in the scikit-learn library. There are several methods for feature selection, but the most common are:
Recursive feature elimination (RFE)
Sequential Feature Selection (SFS)
Feature selection based on threshold (SelectFromModel)
💡 Tip
Feature selection is a powerful tool for improving the performance of machine learning models. However, it is computationally expensive and can be time-consuming. Since the goal is to find the best subset of features, not the best model, it is not necessary to use the entire data set or a highly complex model. Instead, it is recommended to use a small subset of the data and a simple model. Once the best subset of features has been identified, the model can then be trained using the entire dataset and a more complex configuration.The weights are used to control the influence that each observation has on the training of the model. ForecasterAutoregMultiseries
accepts two types of weights:
series_weights
controls the relative importance of each series. If a series has twice as much weight as the others, the observations of that series influence the training twice as much. The higher the weight of a series relative to the others, the more the model will focus on trying to learn that series.
weight_func
controls the relative importance of each observation according to its index value. For example, a function that assigns a lower weight to certain dates.
If the two types of weights are indicated, they are multiplied to create the final weights as shown in the figure. The resulting sample_weight
cannot have negative values.
Learn more about weights in multi-series forecasting and weighted time series forecasting with skforecast.
In this example, item_1
has higher relative importance among series (it weighs 3 times more than the rest of the series), and observations between '2013-12-01' and '2014-01-31' are considered non-representative and a weight of 0 is applied to them.
# Weights in ForecasterAutoregMultiSeries
# ======================================================================================
# Weights for each series
series_weights = {'item_1': 3.0} # Series not presented in the dict will have weight 1
# Weights for each index
def custom_weights(index):
"""
Return 0 if index is between '2013-12-01' and '2014-01-31', 1 otherwise.
"""
weights = np.where(
(index >= '2013-12-01') & (index <= '2014-01-31'),
0,
1
)
return weights
forecaster = ForecasterAutoregMultiSeries(
regressor = HistGradientBoostingRegressor(random_state=123),
lags = 14,
transformer_series = StandardScaler(),
encoding = 'ordinal',
transformer_exog = None,
weight_func = custom_weights,
series_weights = series_weights
)
forecaster.fit(series=data)
forecaster.predict(steps=7).head(3)
🖉 Note
A dictionary can be passed to `weight_func` to apply different functions for each series. If a series is not presented in the dictionary, it will have weight 1.This use case shows that a multi-series model may have advantages over multiple individual models when forecasting time series that follow similar dynamics. Beyond the potential improvements in forecasting, it is also important to take into consideration the benefit of having only one model to maintain.
import session_info
session_info.show(html=False)
How to cite this document
If you use this document or any part of it, please acknowledge the source, thank you!
Global Forecasting Models: Modeling Multiple Time Series with Machine Learning by Joaquín Amat Rodrigo and Javier Escobar Ortiz, available under a CC BY-NC-SA 4.0 at https://www.cienciadedatos.net/documentos/py44-multi-series-forecasting-skforecast.html
How to cite skforecast
If you use skforecast for a scientific publication, we would appreciate it if you cite the published software.
Zenodo:
Amat Rodrigo, Joaquin, & Escobar Ortiz, Javier. (2024). skforecast (v0.13.0). Zenodo. https://doi.org/10.5281/zenodo.8382788
APA:
Amat Rodrigo, J., & Escobar Ortiz, J. (2024). skforecast (Version 0.13.0) [Computer software]. https://doi.org/10.5281/zenodo.8382788
BibTeX:
@software{skforecast, author = {Amat Rodrigo, Joaquin and Escobar Ortiz, Javier}, title = {skforecast}, version = {0.13.0}, month = {8}, year = {2024}, license = {BSD-3-Clause}, url = {https://skforecast.org/}, doi = {10.5281/zenodo.8382788} }
Did you like the article? Your support is important
Website maintenance has high cost, your contribution will help me to continue generating free educational content. Many thanks! 😊
This work by Joaquín Amat Rodrigo and Javier Escobar Ortiz is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International.