Si te gusta Skforecast , ayúdanos dándonos una estrella en GitHub! ⭐️
More about forecasting
En escenarios que implican la predicción de cientos o miles de series temporales, surge una decisión crucial: ¿se deben desarrollar modelos individuales para cada serie o se debe utilizar un único modelo para manejarlas todas a la vez?
En la modelación de una sola serie (modelo de forecasting local), se crea un modelo de predicción independiente para cada serie temporal. Aunque este método proporciona una comprensión exhaustiva de cada serie, su escalabilidad puede verse dificultada por la necesidad de crear y mantener cientos o miles de modelos.
La modelización multiserie (modelo de forecasting global) consiste en crear un único modelo predictivo que tenga en cuenta todas las series temporales simultáneamente. Intenta captar los patrones básicos que rigen las series, mitigando así el ruido potencial que pueda introducir cada serie. Este enfoque es eficiente desde el punto de vista computacional, fácil de mantener y puede producir generalizaciones más sólidas, aunque potencialmente a costa de sacrificar algunos conocimientos individuales.
Este documento muestra cómo predecir más de 1,000 series temporales con un único modelo que incluye características exógenas, algunas de las cuales tienen valores diferentes en cada serie.
💡 Tip
Este es el primero de una serie de documentos sobre modelos de forecasting globales:# Data management
# ==============================================================================
import numpy as np
import pandas as pd
# Plots
# ==============================================================================
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
plt.style.use('seaborn-v0_8-darkgrid')
# Forecasting
# ==============================================================================
import skforecast
import lightgbm
from lightgbm import LGBMRegressor
from sklearn.preprocessing import OrdinalEncoder
from sklearn.compose import make_column_transformer
from sklearn.feature_selection import RFECV
from skforecast.recursive import ForecasterRecursiveMultiSeries
from skforecast.model_selection import TimeSeriesFold, OneStepAheadFold
from skforecast.model_selection import backtesting_forecaster_multiseries
from skforecast.model_selection import bayesian_search_forecaster_multiseries
from skforecast.feature_selection import select_features_multiseries
from skforecast.preprocessing import RollingFeatures
from skforecast.preprocessing import series_long_to_dict
from skforecast.preprocessing import exog_long_to_dict
from feature_engine.datetime import DatetimeFeatures
from feature_engine.creation import CyclicalFeatures
from skforecast.datasets import fetch_dataset
# Configuration
# ==============================================================================
import warnings
warnings.filterwarnings('once')
print('Versión skforecast:', skforecast.__version__)
print('Versión lightgbm:', lightgbm.__version__)
Los datos utilizados en este documento se han obtenido del proyecto The Building Data Genome Project 2 https://github.com/buds-lab/building-data-genome-project-2. El conjunto de datos contiene información sobre el consumo energético de más de 1500 edificios. El rango temporal de los datos de las series temporales abarca los dos años completos (2016 y 2017) y la frecuencia es de mediciones horarias de electricidad, agua de calefacción y refrigeración, vapor y contadores de riego. Además, el conjunto de datos incluye información sobre las características de los edificios y las condiciones meteorológicas. Los datos se han agregado a una resolución diaria y solo se ha considerado la electricidad entre las distintas fuentes de energía.
# Descarga de datos
# ==============================================================================
data = fetch_dataset(name='bdg2_daily')
print("Data shape:", data.shape)
data.head(3)
# Rango de fechas disponibles
# ==============================================================================
print(
f"Rango de fechas disponibles : {data.index.min()} --- {data.index.max()} "
f"(n_días={(data.index.max() - data.index.min()).days})"
)
# Rango de fechas disponibles por serie
# ==============================================================================
available_dates_per_series = (
data
.dropna(subset="meter_reading")
.reset_index()
.groupby("building_id")
.agg(
min_index=("timestamp", "min"),
max_index=("timestamp", "max"),
n_values=("timestamp", "nunique")
)
)
display(available_dates_per_series)
print(f"Longitudes de las series : {available_dates_per_series.n_values.unique()}")
Todas las series temporales tienen la misma longitud, comenzando el 1 de enero de 2016 y terminando el 31 de diciembre de 2017. Las variables exógenas tienen pocos valores faltantes. Skforecast no requiere que las series temporales tengan la misma longitud, y se permiten valores faltantes siempre que el regresor subyacente pueda manejarlos, que es el caso de LightGBM, XGBoost y HistGradientBoostingRegressor.
# Valores nulos por variable
# ==============================================================================
data.isna().mean().mul(100).round(2)
Las variables exógenas son variables externas a la serie temporal y pueden utilizarse como predictores para mejorar la predicción. En este caso, las variables exógenas utilizadas son: las características de los edificios, variables de calendario y condiciones meteorológicas.
⚠ Warning
Las variables exógenas deben conocerse en el momento de la predicción. Por ejemplo, si la temperatura se utiliza como variable exógena, el valor de la temperatura para el día siguiente debe conocerse en el momento de la predicción. Si no se tiene acceso al valor de la temperatura, la predicción no será posible.Uno de los atributos clave asociados a cada edificio es su uso designado. Este atributo puede desempeñar un papel crucial en el patrón de consumo de energía.
# Numero de edificios, tipos y subtipos
# ==============================================================================
print(f"Número de edificios: {data['building_id'].nunique()}")
print(f"Número de edificios types: {data['primaryspaceusage'].nunique()}")
print(f"Número de edificios subtypes: {data['sub_primaryspaceusage'].nunique()}")
Algunos tipos y subtipos de edificios aparecen con poca frecuencia en el conjunto de datos. Tipos con menos de 100 edificios y subtipos con menos de 50 edificios se agrupan en la categoría "Other".
# Agregación de categorías infrecuentes
# ==============================================================================
infrequent_types = (
data
.drop_duplicates(subset=['building_id'])['primaryspaceusage']
.value_counts()
.loc[lambda x: x < 100]
.index
.tolist()
)
infrequent_subtypes = (
data
.drop_duplicates(subset=['building_id'])['sub_primaryspaceusage']
.value_counts()
.loc[lambda x: x < 50]
.index
.tolist()
)
data['primaryspaceusage'] = np.where(
data['primaryspaceusage'].isin(infrequent_types),
'Other',
data['primaryspaceusage']
)
data['sub_primaryspaceusage'] = np.where(
data['sub_primaryspaceusage'].isin(infrequent_subtypes),
'Other',
data['sub_primaryspaceusage']
)
display(data.drop_duplicates(subset=['building_id'])['primaryspaceusage'].value_counts())
display(data.drop_duplicates(subset=['building_id', 'sub_primaryspaceusage'])['sub_primaryspaceusage'].value_counts())
# Variables categóricas
# ==============================================================================
features_to_extract = [
'month',
'week',
'day_of_week',
]
calendar_transformer = DatetimeFeatures(
variables = 'index',
features_to_extract = features_to_extract,
drop_original = False,
)
data = calendar_transformer.fit_transform(data)
# Cyclical encoding
# ==============================================================================
features_to_encode = [
"month",
"week",
"day_of_week",
]
max_values = {
"month": 12,
"week": 52,
"day_of_week": 6,
}
cyclical_encoder = CyclicalFeatures(
variables = features_to_encode,
max_values = max_values,
drop_original = False
)
data = cyclical_encoder.fit_transform(data)
data.head(3)
✎ Note
For more information about calendar features and cyclical encoding visit Calendar features and Cyclical features in time series forecasting.Las variables meteorológicas se han registrado a nivel de localidad, lo que significa que los datos meteorológicos varían según la ubicación del edificio, incluso en el mismo instante de tiempo. En otras palabras, aunque las variables exógenas son consistentes en todas las series, sus valores difieren por ubicación.
# Valores meteorológicos para cada ubicacion para ina fecha dada
# ==============================================================================
data.loc["2016-01-01"].groupby("site_id", observed=True).agg(
{
"airTemperature": "first",
"cloudCoverage": "first",
"dewTemperature": "first",
"precipDepth1HR": "first",
"precipDepth6HR": "first",
"seaLvlPressure": "first",
"windDirection": "first",
"windSpeed": "first",
}
)
Skforecast permite incluir variables exógenas distintas y/o valores distintos para cada serie dentro del conjunto de datos (detalles proporcionados en la siguiente sección).
LightGBM permite incluir variables categóricas en el modelo sin necesidad de preprocesamiento. Para permitir la detección automática de variables categóricas en un Forecaster
, primero las variables categóricas deben codificarse como enteros (codificación ordinal) y luego almacenarse como tipo category
. Esto es necesario porque skforecast utiliza una matriz numérica internamente para acelerar el cálculo, y LightGBM requiere que las características categóricas estén codificadas como category
para ser detectadas automáticamente. También es necesario establecer el parámetro categorical_features
en 'auto'
durante la inicialización del modelo de forecasting utilizando fit_kwargs = {'categorical_feature': 'auto'}
.
⚠ Warning
Las cuatro principales implementaciones de gradient boosting - LightGBM, scikit-learn's HistogramGradientBoosting, XGBoost y CatBoost - son capaces de manejar directamente las variables categóricas dentro del modelo. Sin embargo, es importante tener en cuenta que cada implementación tiene sus propias configuraciones, beneficios y posibles problemas. Para comprender completamente cómo utilizar estas implementaciones, se recomienda consultar la guía del usuario de skforecast para una comprensión detallada.# Transformer: ordinal encoding
# ==============================================================================
# Un ColumnTransformer se utiliza para transformar las variables categóricas
# (no numéricas) utilizando la codificación ordinal. Las varaibles numéricas
# se dejan sin modificar. Los valores perdidos se codifican como -1. Si una
# nueva categoría se encuentra en el conjunto de prueba, se codifica como -1.
categorical_features = ['primaryspaceusage', 'sub_primaryspaceusage', 'timezone']
transformer_exog = make_column_transformer(
(
OrdinalEncoder(
dtype=float,
handle_unknown="use_encoded_value",
unknown_value=np.nan,
encoded_missing_value=np.nan
),
categorical_features
),
remainder="passthrough",
verbose_feature_names_out=False,
).set_output(transform="pandas")
transformer_exog
Cuando se crea un Forecaster
con LGBMRegressor
, es necesario especificar cómo manejar las columnas categóricas utilizando el argumento fit_kwargs
. Esto se debe a que el argumento categorical_feature
solo se especifica en el método fit
de LGBMRegressor
, y no durante su inicialización.
ForecasterRecursiveMultiSeries
permite modelar series temporales de diferentes longitudes y utilizando distintas variables exógenas. Cuando las series tienen longitudes diferentes, los datos deben transformarse en un diccionario. Las claves del diccionario son los nombres de las series y los valores son las propias series. Para ello, se utiliza la función series_long_to_dict
, que toma un DataFrame en «formato largo» y devuelve un diccionario de Series de Pandas. Del mismo modo, cuando las variables exógenas son diferentes (valores o variables) para cada serie, los datos deben transformarse en un diccionario. Las claves del diccionario son los nombres de las series y los valores son las propias variables exógenas. Se utiliza la función exog_long_to_dict
, que toma el DataFrame en «formato largo» y devuelve un diccionario de variables exógenas (series de Pandas o DataFrames de Pandas).
Cuando todas las series tienen la misma longitud y las mismas variables exógenas, no es necesario utilizar diccionarios. Las series se pueden pasar como un único DataFrame con cada serie en una columna, y las variables exógenas se pueden pasar como un DataFrame con la misma longitud que la serie.
✎ Note
Para más información sobre cómo modelar series de diferentes longitudes y utilizar diferentes variables exógenas, visite Global Forecasting Models: Time series with different lengths and different exogenous variables.# Varaibles exógenas para el modelo
# ==============================================================================
exog_features = [
"primaryspaceusage",
"sub_primaryspaceusage",
"timezone",
"sqm",
"airTemperature",
"cloudCoverage",
"dewTemperature",
"precipDepth1HR",
"precipDepth6HR",
"seaLvlPressure",
"windDirection",
"windSpeed",
"day_of_week_sin",
"day_of_week_cos",
"week_sin",
"week_cos",
"month_sin",
"month_cos",
]
# Transformación de las series y variables exógenas a formato dict
# ==============================================================================
series_dict = series_long_to_dict(
data = data.reset_index(),
series_id = 'building_id',
index = 'timestamp',
values = 'meter_reading',
freq = 'D'
)
exog_dict = exog_long_to_dict(
data = data[exog_features + ['building_id']].reset_index(),
series_id = 'building_id',
index = 'timestamp',
freq = 'D'
)
Para entrenar los modelos, buscar los hiperparámetros óptimos y evaluar su rendimiento predictivo, los datos se dividen en tres conjuntos separados: entrenamiento, validación y test.
# Partiticón de los datos en entrenamiento, validación y test
# ==============================================================================
data = data.sort_index()
end_train = '2017-08-31 23:59:00'
end_validation = '2017-10-31 23:59:00'
series_dict_train = {k: v.loc[: end_train,] for k, v in series_dict.items()}
series_dict_valid = {k: v.loc[end_train: end_validation,] for k, v in series_dict.items()}
series_dict_test = {k: v.loc[end_validation:,] for k, v in series_dict.items()}
exog_dict_train = {k: v.loc[: end_train,] for k, v in exog_dict.items()}
exog_dict_valid = {k: v.loc[end_train: end_validation,] for k, v in exog_dict.items()}
exog_dict_test = {k: v.loc[end_validation:,] for k, v in exog_dict.items()}
print(
f"Rage of dates available : {data.index.min()} --- {data.index.max()} "
f"(n_days={(data.index.max() - data.index.min()).days})"
)
print(
f" Dates for training : {data.loc[: end_train, :].index.min()} --- {data.loc[: end_train, :].index.max()} "
f"(n_days={(data.loc[: end_train, :].index.max() - data.loc[: end_train, :].index.min()).days})"
)
print(
f" Dates for validation : {data.loc[end_train:end_validation, :].index.min()} --- {data.loc[end_train:end_validation, :].index.max()} "
f"(n_days={(data.loc[end_train:end_validation, :].index.max() - data.loc[end_train:end_validation, :].index.min()).days})"
)
print(
f" Dates for test : {data.loc[end_validation:, :].index.min()} --- {data.loc[end_validation:, :].index.max()} "
f"(n_days={(data.loc[end_validation:, :].index.max() - data.loc[end_validation:, :].index.min()).days})"
)
La búsqueda de hiperparámetros y lags implica probar sistemáticamente diferentes valores de hiperparámetros (y/o lags) para encontrar la configuración óptima que ofrezca el mejor rendimiento. skforecast proporciona dos métodos diferentes para evaluar cada configuración candidata:
Backtesting: en este método, el modelo predice varios pasos a futuro en cada iteración, utilizando el mismo horizonte de predicción y la misma estrategia de reentrenamiento que se utilizarían si se desplegara el modelo. De este modo, se simula un escenario de predicción real en el que el modelo se reentrena y actualiza a lo largo del tiempo.
One-Step Ahead: Evalúa el modelo utilizando solo predicciones de un paso a futuro. Este método es más rápido porque requiere menos iteraciones, pero solo evalua el rendimiento del modelo en el siguiente paso temporal (t+1).
Cada método utiliza una estrategia de evaluación diferente, por lo que pueden producir resultados distintos. Sin embargo, a largo plazo, se espera que ambos métodos converjan a selecciones similares de hiperparámetros óptimos. El método de One-Step Ahead es mucho más rápido que el backtesting porque requiere menos iteraciones, pero solo prueba el rendimiento del modelo en el siguiente instante de tiempo. Se recomienda realizar un backtest del modelo final para obtener una estimación más precisa del rendimiento cuando se predicen varios pasos a futuro.
# Crear forecaster
# ==============================================================================
window_features = RollingFeatures(stats=['mean', 'min', 'max'], window_sizes=7)
forecaster = ForecasterRecursiveMultiSeries(
regressor = LGBMRegressor(random_state=8520, verbose=-1),
lags = 14,
window_features = window_features,
transformer_series = None,
transformer_exog = transformer_exog,
fit_kwargs = {'categorical_feature': categorical_features},
encoding = "ordinal"
)
# Bayesian search con OneStepAheadFold
# ==============================================================================
def search_space(trial):
search_space = {
'lags' : trial.suggest_categorical('lags', [31, 62]),
'n_estimators' : trial.suggest_int('n_estimators', 200, 800, step=100),
'max_depth' : trial.suggest_int('max_depth', 3, 8, step=1),
'min_data_in_leaf': trial.suggest_int('min_data_in_leaf', 25, 500),
'learning_rate' : trial.suggest_float('learning_rate', 0.01, 0.5),
'feature_fraction': trial.suggest_float('feature_fraction', 0.5, 0.8, step=0.1),
'max_bin' : trial.suggest_int('max_bin', 50, 100, step=25),
'reg_alpha' : trial.suggest_float('reg_alpha', 0, 1, step=0.1),
'reg_lambda' : trial.suggest_float('reg_lambda', 0, 1, step=0.1)
}
return search_space
cv = OneStepAheadFold(initial_train_size=608) # Tamaño del conjunto de entrenamiento
results_search, best_trial = bayesian_search_forecaster_multiseries(
forecaster = forecaster,
series = {k: v.loc[:end_validation,] for k, v in series_dict.items()},
exog = {k: v.loc[:end_validation, exog_features] for k, v in exog_dict.items()},
cv = cv,
search_space = search_space,
n_trials = 10,
metric = "mean_absolute_error",
return_best = True,
verbose = False,
n_jobs = "auto",
show_progress = True,
suppress_warnings = True,
)
best_params = results_search.at[0, 'params']
best_lags = results_search.at[0, 'lags']
results_search.head(3)
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
initial_train_size = 608 + 60, # Entreanmiento + validación
steps = 7,
refit = False
)
metrics, predictions = backtesting_forecaster_multiseries(
forecaster = forecaster,
series = series_dict,
exog = exog_dict,
cv = cv,
metric = 'mean_absolute_error',
verbose = False,
show_progress = True,
suppress_warnings = True
)
display(predictions.head())
display(metrics)
# Agregación de métricas para todos los edificios
# ==============================================================================
average_metric_all_buildings = metrics.query("levels == 'average'")["mean_absolute_error"].item()
errors_all_buildings = (
predictions
- data.pivot(
columns="building_id",
values="meter_reading",
).loc[predictions.index, predictions.columns]
)
sum_abs_errors_all_buildings = errors_all_buildings.abs().sum().sum()
sum_bias_all_buildings = errors_all_buildings.sum().sum()
print(f"Average mean absolute error for all buildings: {average_metric_all_buildings:.0f}")
print(f"Sum of absolute errors for all buildings (x 10,000): {sum_abs_errors_all_buildings/10000:.0f}")
print(f"Bias (x 10,000): {sum_bias_all_buildings/10000:.0f}")
# Gráfico de predicciones vs reales para dos edificios aleatorios
# ==============================================================================
rng = np.random.default_rng(14793)
n_buildings = 2
selected_buildings = rng.choice(data['building_id'].unique(), size=n_buildings, replace=False)
fig, axs = plt.subplots(n_buildings, 1, figsize=(7, 4.5), sharex=True)
axs = axs.flatten()
for i, building in enumerate(selected_buildings):
data.query("building_id == @building").loc[predictions.index, 'meter_reading'].plot(ax=axs[i], label='test')
predictions[building].plot(ax=axs[i], label='predictions')
axs[i].set_title(f"Building {building}", fontsize=10)
axs[i].set_xlabel("")
axs[i].legend()
fig.tight_layout()
plt.show();
La selección de predictores es el proceso de seleccionar un subconjunto de predictores relevantes (variables) para su uso en la construcción del modelo. Las técnicas de selección de predictores se utilizan por varias razones: para simplificar los modelos y hacerlos más fáciles de interpretar, para reducir el tiempo de entrenamiento, para evitar los problemas de dimensionalidad, para mejorar la generalización reduciendo el sobreajuste (formalmente, la reducción de la varianza), entre otros.
Skforecast es compatible con los métodos de selección implementados en scikit-learn. Existen varios métodos de selección de características, pero los más comunes son:
Recursive feature elimination (RFE)
Sequential Feature Selection (SFS)
Feature selection based on threshold (SelectFromModel)
💡 Tip
La selección de predictores es una herramienta poderosa para mejorar el rendimiento de los modelos de machine learning. Sin embargo, es computacionalmente costosa y puede llevar tiempo. Dado que el objetivo es encontrar el mejor subconjunto de variables, no el mejor modelo, no es necesario utilizar todo el conjunto de datos o un modelo muy complejo. En su lugar, se recomienda utilizar un pequeño subconjunto de datos y un modelo simple. Una vez que se haya identificado el mejor subconjunto de variables, el modelo puede entrenarse utilizando todo el conjunto de datos y una configuración más compleja.# Selección de predictores
# ==============================================================================
regressor = LGBMRegressor(n_estimators=100, max_depth=5, random_state=15926, verbose=-1)
selector = RFECV(estimator=regressor, step=1, cv=3, n_jobs=1)
selected_lags, selected_window_features, selected_exog = select_features_multiseries(
forecaster = forecaster,
selector = selector,
series = {k: v.loc[:end_validation,] for k, v in series_dict.items()},
exog = {k: v.loc[:end_validation, exog_features] for k, v in exog_dict.items()},
select_only = None,
force_inclusion = None,
subsample = 0.2,
random_state = 123,
verbose = True,
)
# Backtesting del forecaster con predictores seleccionados
# ==============================================================================
forecaster = ForecasterRecursiveMultiSeries(
regressor = LGBMRegressor(**best_params, random_state=8520, verbose=-1),
lags = selected_lags,
window_features = window_features,
transformer_series = None,
transformer_exog = transformer_exog,
fit_kwargs = {'categorical_feature': categorical_features},
encoding = "ordinal"
)
cv = TimeSeriesFold(
initial_train_size = 608 + 60, # Entreanmiento + validación
steps = 7,
refit = False
)
metrics, predictions = backtesting_forecaster_multiseries(
forecaster = forecaster,
series = series_dict,
exog = {k: v[exog_features] for k, v in exog_dict.items()},
cv = cv,
metric = 'mean_absolute_error',
verbose = False,
show_progress = True,
suppress_warnings = True
)
display(predictions.head())
display(metrics)
Se ha conseguido reducir el número de predictores sin que el rendimiento del modelo no se ve comprometido. Esto permite simplificar el modelo y acelera el entrenamiento.
La idea que hay detrás de modelar varias series al mismo tiempo es poder capturar los patrones principales que rigen dichas series, reduciendo así el impacto del ruido que pueda haber en cada una de ellas. Esto significa que las series que se comportan de manera similar pueden beneficiarse de ser modelizadas juntas. Una forma de identificar posibles grupos de series es realizar un estudio de +cluatering antes de modelizarlas. Si como resultado del clustering* se identifican grupos claros, es apropiado modelar cada uno de ellos por separado.
El clustering es una técnica de análisis no supervisado que agrupa un conjunto de observaciones en clústeres que contienen observaciones consideradas homogéneas, mientras que las observaciones en diferentes clústeres se consideran heterogéneas. Los algoritmos que agrupan series temporales se pueden dividir en dos grupos: aquellos que utilizan una transformación para crear variables antes de agrupar (clustering de series temporales basado en características) y aquellos que trabajan directamente en las series temporales (medidas de distancia elástica).
Clustering basado en características de series temporales: se extraen variables que describen las características estructurales de cada serie temporal y luego se introducen en algoritmos de clustering. Estas variables se obtienen aplicando operaciones estadísticas que capturan mejor las características subyacentes: tendencia, estacionalidad, periodicidad, correlación serial, asimetría, curtosis, caos, no linealidad y auto-similitud.
Medidas de distancia elástica: este enfoque trabaja directamente en las series temporales, ajustando o «reajustando» las series en comparación con otras. La medida más conocida de esta familia es el Dynamic Time Warping (DTW).
Para un ejemplo detallado de cómo el clustering de series temporales puede mejorar los modelos de forecasting, consulte Modelos de forecasting globales: Análisis comparativo de modelos de una y múltiples series.
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!
Forecasting escalable: modelado de mil series temporales con un único modelo global por Joaquín Amat Rodrigo y Javier Escobar Ortiz, disponible bajo una licencia Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0 DEED) en https://www.cienciadedatos.net/documentos/py59-modelos-forecasting-escalables.html
¿Cómo citar skforecast?
Si utilizas skforecast en tu investigación o publicación, te lo agradeceríamos mucho que lo cites. ¡Muchas gracias!
Zenodo:
Amat Rodrigo, Joaquin, & Escobar Ortiz, Javier. (2024). skforecast (v0.14.0). Zenodo. https://doi.org/10.5281/zenodo.8382788
APA:
Amat Rodrigo, J., & Escobar Ortiz, J. (2024). skforecast (Version 0.14.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.14.0}, month = {11}, year = {2024}, license = {BSD-3-Clause}, url = {https://skforecast.org/}, doi = {10.5281/zenodo.8382788} }
¿Te ha gustado el artículo? Tu ayuda es importante
Mantener un sitio web tiene unos costes elevados, tu contribución me ayudará a seguir generando contenido divulgativo gratuito. ¡Muchísimas gracias! 😊
Este documento creado por Joaquín Amat Rodrigo y Javier Escobar Ortiz tiene licencia Attribution-NonCommercial-ShareAlike 4.0 International.
Se permite:
Compartir: copiar y redistribuir el material en cualquier medio o formato.
Adaptar: remezclar, transformar y crear a partir del material.
Bajo los siguientes términos:
Atribución: Debes otorgar el crédito adecuado, proporcionar un enlace a la licencia e indicar si se realizaron cambios. Puedes hacerlo de cualquier manera razonable, pero no de una forma que sugiera que el licenciante te respalda o respalda tu uso.
NoComercial: No puedes utilizar el material para fines comerciales.
CompartirIgual: Si remezclas, transformas o creas a partir del material, debes distribuir tus contribuciones bajo la misma licencia que el original.