diff --git a/.github/workflows/build-docs.yaml b/.github/workflows/build-docs.yaml
new file mode 100644
index 000000000..8209bdabe
--- /dev/null
+++ b/.github/workflows/build-docs.yaml
@@ -0,0 +1,52 @@
+name: "build-docs"
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+ workflow_dispatch:
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ build-docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: 'recursive'
+ - uses: actions/setup-python@v4
+ with:
+ cache: "pip"
+ python-version: '3.10'
+ cache-dependency-path: settings.ini
+ - name: Build docs
+ run: |
+ set -ux
+ python -m pip install --upgrade pip
+ pip install -Uq nbdev
+ pip install -e ".[dev]"
+ mkdir nbs/_extensions
+ cp -r docs-scripts/mintlify/ nbs/_extensions/
+ python docs-scripts/update-quarto.py
+ echo "procs = nbdev_plotly.plotly:PlotlyProc" >> settings.ini
+ nbdev_docs
+ - name: Apply final formats
+ run: bash ./docs-scripts/docs-final-formatting.bash
+ - name: Copy over necessary assets
+ run: |
+ cp nbs/mint.json _docs/mint.json
+ cp docs-scripts/imgs/* _docs/
+ - name: Deploy to Mintlify Docs
+ if: github.event_name == 'push'
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_branch: docs
+ publish_dir: ./_docs
+ # The following lines assign commit authorship to the official GH-Actions bot for deploys to `docs` branch.
+ # You can swap them out with your own user credentials.
+ user_name: github-actions[bot]
+ user_email: 41898282+github-actions[bot]@users.noreply.github.com
diff --git a/.gitignore b/.gitignore
index 1a945f599..92ef65b65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,13 +9,13 @@ dist
.vscode
.idea
*.gif
+*.icloud
*.csv
*/data/*
*.parquet
tmp
_docs/
_proc/
-sidebar.yml
.DS_Store
.gitattributes
.gitconfig
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..b09d038f8
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+[submodule "docs-scripts"]
+ path = docs-scripts
+ url = git@github.com:Nixtla/docs.git
+ branch = scripts
diff --git a/README.md b/README.md
index 96a610ead..d6aefd25f 100644
--- a/README.md
+++ b/README.md
@@ -1,32 +1,46 @@
-# StatsForecast ⚡️
-
-
+# Nixtla [](https://twitter.com/intent/tweet?text=Statistical%20Forecasting%20Algorithms%20by%20Nixtla%20&url=https://github.com/Nixtla/statsforecast&via=nixtlainc&hashtags=StatisticalModels,TimeSeries,Forecasting) [](https://join.slack.com/t/nixtlacommunity/shared_invite/zt-1pmhan9j5-F54XR20edHk0UtYAPcW4KQ)
+
+[](#contributors-)
+
+
+
+

+
Statistical ⚡️ Forecast
+
Lightning fast forecasting with statistical and econometric models
+
+[](https://github.com/Nixtla/statsforecast/actions/workflows/ci.yaml)
+[](https://pypi.org/project/statsforecast/)
+[](https://pypi.org/project/statsforecast/)
+[](https://anaconda.org/conda-forge/statsforecast)
+[](https://github.com/Nixtla/statsforecast/blob/main/LICENSE)
+[](https://nixtla.github.io/statsforecast/)
+[](https://pepy.tech/project/statsforecast)
+
+**StatsForecast** offers a collection of widely used univariate time series forecasting models, including automatic `ARIMA`, `ETS`, `CES`, and `Theta` modeling optimized for high performance using `numba`. It also includes a large battery of benchmarking models.
+
## Installation
-You can install
-[`StatsForecast`](https://Nixtla.github.io/statsforecast/src/core/core.html#statsforecast)
-with:
+You can install `StatsForecast` with:
-``` python
+```python
pip install statsforecast
```
-or
+or
-``` python
+```python
conda install -c conda-forge statsforecast
-```
+```
-Vist our [Installation
-Guide](./docs/getting-started/0_Installation.ipynb) for further
-instructions.
+
+Vist our [Installation Guide](https://nixtla.github.io/statsforecast/docs/getting-started/installation.html) for further instructions.
## Quick Start
**Minimal Example**
-``` python
+```python
from statsforecast import StatsForecast
from statsforecast.models import AutoARIMA
@@ -39,201 +53,144 @@ sf.fit(df)
sf.predict(h=12, level=[95])
```
-**Get Started with this [quick
-guide](../nbs/docs/getting-started/1_Getting_Started_short.ipynb).**
+**Get Started with this [quick guide](https://nixtla.github.io/statsforecast/docs/getting-started/getting_started_short.html).**
-**Follow this [end-to-end
-walkthrough](../nbs/docs/getting-started/2_Getting_Started_complete.ipynb)
-for best practices.**
+**Follow this [end-to-end walkthrough](https://nixtla.github.io/statsforecast/docs/getting-started/getting_started_complete.html) for best practices.**
-## Why?
+## Why?
-Current Python alternatives for statistical models are slow, inaccurate
-and don’t scale well. So we created a library that can be used to
-forecast in production environments or as benchmarks.
-[`StatsForecast`](https://Nixtla.github.io/statsforecast/src/core/core.html#statsforecast)
-includes an extensive battery of models that can efficiently fit
-millions of time series.
+Current Python alternatives for statistical models are slow, inaccurate and don't scale well. So we created a library that can be used to forecast in production environments or as benchmarks. `StatsForecast` includes an extensive battery of models that can efficiently fit millions of time series.
## Features
-- Fastest and most accurate implementations of
- [`AutoARIMA`](https://Nixtla.github.io/statsforecast/src/core/models.html#autoarima),
- [`AutoETS`](https://Nixtla.github.io/statsforecast/src/core/models.html#autoets),
- [`AutoCES`](https://Nixtla.github.io/statsforecast/src/core/models.html#autoces),
- [`MSTL`](https://Nixtla.github.io/statsforecast/src/core/models.html#mstl)
- and
- [`Theta`](https://Nixtla.github.io/statsforecast/src/core/models.html#theta)
- in Python.
-- Out-of-the-box compatibility with Spark, Dask, and Ray.
-- Probabilistic Forecasting and Confidence Intervals.
-- Support for exogenous Variables and static covariates.
-- Anomaly Detection.
-- Familiar sklearn syntax: `.fit` and `.predict`.
+* Fastest and most accurate implementations of `AutoARIMA`, `AutoETS`, `AutoCES`, `MSTL` and `Theta` in Python.
+* Out-of-the-box compatibility with Spark, Dask, and Ray.
+* Probabilistic Forecasting and Confidence Intervals.
+* Support for exogenous Variables and static covariates.
+* Anomaly Detection.
+* Familiar sklearn syntax: `.fit` and `.predict`.
## Highlights
-- Inclusion of `exogenous variables` and `prediction intervals` for
- ARIMA.
-- 20x
- [faster](https://github.com/Nixtla/statsforecast/tree/main/experiments/arima)
- than `pmdarima`.
-- 1.5x faster than `R`.
-- 500x faster than `Prophet`.
-- 4x
- [faster](https://github.com/Nixtla/statsforecast/tree/main/experiments/ets)
- than `statsmodels`.
-- Compiled to high performance machine code through
- [`numba`](https://numba.pydata.org/).
-- 1,000,000 series in [30
- min](https://github.com/Nixtla/statsforecast/tree/main/experiments/ray)
- with [ray](https://github.com/ray-project/ray).
-- Replace FB-Prophet in two lines of code and gain speed and accuracy.
- Check the experiments
- [here](https://github.com/Nixtla/statsforecast/tree/main/experiments/arima_prophet_adapter).
-- Fit 10 benchmark models on **1,000,000** series in [under **5
- min**](https://github.com/Nixtla/statsforecast/tree/main/experiments/benchmarks_at_scale).
-
-Missing something? Please open an issue or write us in
-[](https://join.slack.com/t/nixtlaworkspace/shared_invite/zt-135dssye9-fWTzMpv2WBthq8NK0Yvu6A)
+* Inclusion of `exogenous variables` and `prediction intervals` for ARIMA.
+* 20x [faster](./experiments/arima/) than `pmdarima`.
+* 1.5x faster than `R`.
+* 500x faster than `Prophet`.
+* 4x [faster](./experiments/ets/) than `statsmodels`.
+* Compiled to high performance machine code through [`numba`](https://numba.pydata.org/).
+* 1,000,000 series in [30 min](https://github.com/Nixtla/statsforecast/tree/main/experiments/ray) with [ray](https://github.com/ray-project/ray).
+* Replace FB-Prophet in two lines of code and gain speed and accuracy. Check the experiments [here](https://github.com/Nixtla/statsforecast/tree/main/experiments/arima_prophet_adapter).
+* Fit 10 benchmark models on **1,000,000** series in [under **5 min**](./experiments/benchmarks_at_scale/).
+
+
+Missing something? Please open an issue or write us in [](https://join.slack.com/t/nixtlaworkspace/shared_invite/zt-135dssye9-fWTzMpv2WBthq8NK0Yvu6A)
## Examples and Guides
-📚 [End to End
-Walkthrough](https://nixtla.github.io/statsforecast/docs/getting-started/getting_started_complete.html):
-Model training, evaluation and selection for multiple time series
+📚 [End to End Walkthrough](https://nixtla.github.io/statsforecast/docs/getting-started/getting_started_complete.html): Model training, evaluation and selection for multiple time series
+
+🔎 [Anomaly Detection](https://nixtla.github.io/statsforecast/docs/tutorials/anomalydetection.html): detect anomalies for time series using in-sample prediction intervals.
-🔎 [Anomaly
-Detection](https://nixtla.github.io/statsforecast/docs/tutorials/anomalydetection.html):
-detect anomalies for time series using in-sample prediction intervals.
+👩🔬 [Cross Validation](https://nixtla.github.io/statsforecast/docs/tutorials/crossvalidation.html): robust model’s performance evaluation.
-👩🔬 [Cross
-Validation](https://nixtla.github.io/statsforecast/docs/tutorials/crossvalidation.html):
-robust model’s performance evaluation.
+❄️ [Multiple Seasonalities](https://nixtla.github.io/statsforecast/docs/tutorials/multipleseasonalities.html): how to forecast data with multiple seasonalities using an MSTL.
-❄️ [Multiple
-Seasonalities](https://nixtla.github.io/statsforecast/docs/tutorials/multipleseasonalities.html):
-how to forecast data with multiple seasonalities using an MSTL.
+🔌 [Predict Demand Peaks](https://nixtla.github.io/statsforecast/docs/tutorials/electricitypeakforecasting.html): electricity load forecasting for detecting daily peaks and reducing electric bills.
-🔌 [Predict Demand
-Peaks](https://nixtla.github.io/statsforecast/docs/tutorials/electricitypeakforecasting.html):
-electricity load forecasting for detecting daily peaks and reducing
-electric bills.
+📈 [Intermittent Demand](https://nixtla.github.io/statsforecast/docs/tutorials/intermittentdata.html): forecast series with very few non-zero observations.
-📈 [Intermittent
-Demand](https://nixtla.github.io/statsforecast/docs/tutorials/intermittentdata.html):
-forecast series with very few non-zero observations.
+🌡️ [Exogenous Regressors](https://nixtla.github.io/statsforecast/docs/how-to-guides/exogenous.html): like weather or prices
-🌡️ [Exogenous
-Regressors](https://nixtla.github.io/statsforecast/docs/how-to-guides/exogenous.html):
-like weather or prices
## Models
### Automatic Forecasting
+Automatic forecasting tools search for the best parameters and select the best possible model for a group of time series. These tools are useful for large collections of univariate time series.
-Automatic forecasting tools search for the best parameters and select
-the best possible model for a group of time series. These tools are
-useful for large collections of univariate time series.
-
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:-----------------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [AutoARIMA](https://nixtla.github.io/statsforecast/src/core/models.html#autoarima) | ✅ | ✅ | ✅ | ✅ |
-| [AutoETS](https://nixtla.github.io/statsforecast/src/core/models.html#autoets) | ✅ | ✅ | ✅ | ✅ |
-| [AutoCES](https://nixtla.github.io/statsforecast/src/core/models.html#autoces) | ✅ | ✅ | ✅ | ✅ |
-| [AutoTheta](https://nixtla.github.io/statsforecast/src/core/models.html#autotheta) | ✅ | ✅ | ✅ | ✅ |
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[AutoARIMA](https://nixtla.github.io/statsforecast/src/core/models.html#autoarima)|✅|✅|✅|✅|✅|
+|[AutoETS](https://nixtla.github.io/statsforecast/src/core/models.html#autoets)|✅|✅|✅|✅|✅|
+|[AutoCES](https://nixtla.github.io/statsforecast/src/core/models.html#autoces)|✅|✅|✅|✅|✅|
+|[AutoTheta](https://nixtla.github.io/statsforecast/src/core/models.html#autotheta)|✅|✅|✅|✅|✅|
## ARIMA Family
-
These models exploit the existing autocorrelations in the time series.
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:---------------------------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [ARIMA](https://nixtla.github.io/statsforecast/src/core/models.html#arima) | ✅ | ✅ | ✅ | ✅ |
-| [AutoRegressive](https://nixtla.github.io/statsforecast/src/core/models.html#autoregressive) | ✅ | ✅ | ✅ | ✅ |
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[ARIMA](https://nixtla.github.io/statsforecast/src/core/models.html#arima)|✅|✅|✅|✅|✅|
+|[AutoRegressive](https://nixtla.github.io/statsforecast/src/core/models.html#autoregressive)|✅|✅|✅|✅|✅|
### Theta Family
+Fit two theta lines to a deseasonalized time series, using different techniques to obtain and combine the two theta lines to produce the final forecasts.
-Fit two theta lines to a deseasonalized time series, using different
-techniques to obtain and combine the two theta lines to produce the
-final forecasts.
-
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:-----------------------------------------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [Theta](https://nixtla.github.io/statsforecast/src/core/models.html#theta) | ✅ | ✅ | ✅ | ✅ |
-| [OptimizedTheta](https://nixtla.github.io/statsforecast/src/core/models.html#optimizedtheta) | ✅ | ✅ | ✅ | ✅ |
-| [DynamicTheta](https://nixtla.github.io/statsforecast/src/core/models.html#dynamictheta) | ✅ | ✅ | ✅ | ✅ |
-| [DynamicOptimizedTheta](https://nixtla.github.io/statsforecast/src/core/models.html#dynamicoptimizedtheta) | ✅ | ✅ | ✅ | ✅ |
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[Theta](https://nixtla.github.io/statsforecast/src/core/models.html#theta)|✅|✅|✅|✅|✅|
+|[OptimizedTheta](https://nixtla.github.io/statsforecast/src/core/models.html#optimizedtheta)|✅|✅|✅|✅|✅|
+|[DynamicTheta](https://nixtla.github.io/statsforecast/src/core/models.html#dynamictheta)|✅|✅|✅|✅|✅|
+|[DynamicOptimizedTheta](https://nixtla.github.io/statsforecast/src/core/models.html#dynamicoptimizedtheta)|✅|✅|✅|✅|✅|
### Multiple Seasonalities
+Suited for signals with more than one clear seasonality. Useful for low-frequency data like electricity and logs.
-Suited for signals with more than one clear seasonality. Useful for
-low-frequency data like electricity and logs.
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[MSTL](https://nixtla.github.io/statsforecast/src/core/models.html#mstl)|✅|✅|✅|✅|✅|
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:-------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [MSTL](https://nixtla.github.io/statsforecast/src/core/models.html#mstl) | ✅ | ✅ | ✅ | ✅ |
+### GARCH and ARCH Models
+Suited for modeling time series that exhibit non-constant volatility over time. The ARCH model is a particular case of GARCH.
-### GARCH and ARCH Models
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[GARCH](https://nixtla.github.io/statsforecast/src/core/models.html#garch)|✅|✅|✅|✅|✅|
+|[ARCH](https://nixtla.github.io/statsforecast/src/core/models.html#arch)|✅|✅|✅|✅|✅|
-Suited for modeling time series that exhibit non-constant volatility
-over time. The ARCH model is a particular case of GARCH.
-
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:---------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [GARCH](https://nixtla.github.io/statsforecast/src/core/models.html#garch) | ✅ | ✅ | ✅ | ✅ |
-| [ARCH](https://nixtla.github.io/statsforecast/src/core/models.html#arch) | ✅ | ✅ | ✅ | ✅ |
### Baseline Models
-
Classical models for establishing baseline.
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:-----------------------------------------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [HistoricAverage](https://nixtla.github.io/statsforecast/src/core/models.html#historicaverage) | ✅ | ✅ | ✅ | ✅ |
-| [Naive](https://nixtla.github.io/statsforecast/src/core/models.html#naive) | ✅ | ✅ | ✅ | ✅ |
-| [RandomWalkWithDrift](https://nixtla.github.io/statsforecast/src/core/models.html#randomwalkwithdrift) | ✅ | ✅ | ✅ | ✅ |
-| [SeasonalNaive](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalnaive) | ✅ | ✅ | ✅ | ✅ |
-| [WindowAverage](https://nixtla.github.io/statsforecast/src/core/models.html#windowaverage) | ✅ | | | |
-| [SeasonalWindowAverage](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalwindowaverage) | ✅ | | | |
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[HistoricAverage](https://nixtla.github.io/statsforecast/src/core/models.html#historicaverage)|✅|✅|✅|✅|✅|
+|[Naive](https://nixtla.github.io/statsforecast/src/core/models.html#naive)|✅|✅|✅|✅|✅|
+|[RandomWalkWithDrift](https://nixtla.github.io/statsforecast/src/core/models.html#randomwalkwithdrift)|✅|✅|✅|✅|✅|
+|[SeasonalNaive](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalnaive)|✅|✅|✅|✅|✅|
+|[WindowAverage](https://nixtla.github.io/statsforecast/src/core/models.html#windowaverage)|✅|||||
+|[SeasonalWindowAverage](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalwindowaverage)|✅|||||
### Exponential Smoothing
+Uses a weighted average of all past observations where the weights decrease exponentially into the past. Suitable for data with clear trend and/or seasonality. Use the `SimpleExponential` family for data with no clear trend or seasonality.
-Uses a weighted average of all past observations where the weights
-decrease exponentially into the past. Suitable for data with clear trend
-and/or seasonality. Use the `SimpleExponential` family for data with no
-clear trend or seasonality.
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[SimpleExponentialSmoothing](https://nixtla.github.io/statsforecast/src/core/models.html#simpleexponentialsmoothing)|✅|||||
+|[SimpleExponentialSmoothingOptimized](https://nixtla.github.io/statsforecast/src/core/models.html#simpleexponentialsmoothingoptimized)|✅|||||
+|[SeasonalExponentialSmoothing](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalexponentialsmoothing)|✅|||||
+|[SeasonalExponentialSmoothingOptimized](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalexponentialsmoothingoptimized)|✅|||||
+|[Holt](https://nixtla.github.io/statsforecast/src/core/models.html#holt)|✅|✅|✅|✅|✅|
+|[HoltWinters](https://nixtla.github.io/statsforecast/src/core/models.html#holtwinters)|✅|✅|✅|✅|✅|
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:-------------------------------------------------------------------------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [SimpleExponentialSmoothing](https://nixtla.github.io/statsforecast/src/core/models.html#simpleexponentialsmoothing) | ✅ | | | |
-| [SimpleExponentialSmoothingOptimized](https://nixtla.github.io/statsforecast/src/core/models.html#simpleexponentialsmoothingoptimized) | ✅ | | | |
-| [SeasonalExponentialSmoothing](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalexponentialsmoothing) | ✅ | | | |
-| [SeasonalExponentialSmoothingOptimized](https://nixtla.github.io/statsforecast/src/core/models.html#seasonalexponentialsmoothingoptimized) | ✅ | | | |
-| [Holt](https://nixtla.github.io/statsforecast/src/core/models.html#holt) | ✅ | ✅ | ✅ | ✅ |
-| [HoltWinters](https://nixtla.github.io/statsforecast/src/core/models.html#holtwinters) | ✅ | ✅ | ✅ | ✅ |
-
-### Sparse or Inttermitent
+### Sparse or Intermittent
Suited for series with very few non-zero observations
-| Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
-|:-------------------------------------------------------------------------------------------------|:--------------:|:----------------------:|:----------------------:|:---------------------------:|
-| [ADIDA](https://nixtla.github.io/statsforecast/src/core/models.html#adida) | ✅ | | | |
-| [CrostonClassic](https://nixtla.github.io/statsforecast/src/core/models.html#crostonclassic) | ✅ | | | |
-| [CrostonOptimized](https://nixtla.github.io/statsforecast/src/core/models.html#crostonoptimized) | ✅ | | | |
-| [CrostonSBA](https://nixtla.github.io/statsforecast/src/core/models.html#crostonsba) | ✅ | | | |
-| [IMAPA](https://nixtla.github.io/statsforecast/src/core/models.html#imapa) | ✅ | | | |
-| [TSB](https://nixtla.github.io/statsforecast/src/core/models.html#tsb) | ✅ | | | |
-
-## How to contribute
+|Model | Point Forecast | Probabilistic Forecast | Insample fitted values | Probabilistic fitted values |
+|:------|:-------------:|:----------------------:|:---------------------:|:----------------------------:|
+|[ADIDA](https://nixtla.github.io/statsforecast/src/core/models.html#adida)|✅|||||
+|[CrostonClassic](https://nixtla.github.io/statsforecast/src/core/models.html#crostonclassic)|✅|||||
+|[CrostonOptimized](https://nixtla.github.io/statsforecast/src/core/models.html#crostonoptimized)|✅|||||
+|[CrostonSBA](https://nixtla.github.io/statsforecast/src/core/models.html#crostonsba)|✅|||||
+|[IMAPA](https://nixtla.github.io/statsforecast/src/core/models.html#imapa)|✅|||||
+|[TSB](https://nixtla.github.io/statsforecast/src/core/models.html#tsb)|✅|||||
-See
-[CONTRIBUTING.md](https://github.com/Nixtla/statsforecast/blob/main/CONTRIBUTING.md).
+## 🔨 How to contribute
+See [CONTRIBUTING.md](https://github.com/Nixtla/statsforecast/blob/main/CONTRIBUTING.md).
## Citing
-``` bibtex
+```bibtex
@misc{garza2022statsforecast,
author={Federico Garza, Max Mergenthaler Canseco, Cristian Challú, Kin G. Olivares},
title = {{StatsForecast}: Lightning fast forecasting with statistical and econometric models},
@@ -242,3 +199,64 @@ See
url={https://github.com/Nixtla/statsforecast}
}
```
+
+## Contributors ✨
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
\ No newline at end of file
diff --git a/dev/requirements.txt b/dev/requirements.txt
index 4f4a21c28..4109358c4 100644
--- a/dev/requirements.txt
+++ b/dev/requirements.txt
@@ -1,17 +1,21 @@
-holidays<0.21
+holidays<0.21
jupyterlab
matplotlib
numba>=0.55.0
numpy>=1.21.6
pandas>=1.3.5
+pyspark>=3.3
pip
prophet
+pyarrow
scipy>=1.7.3
statsmodels>=0.13.2
tabulate
plotly
+utilsforecast>=0.0.5
+fugue[dask,ray]
nbdev
-tqdm
plotly-resampler
polars
-supersmoother
\ No newline at end of file
+supersmoother
+tqdm
diff --git a/docs-scripts b/docs-scripts
new file mode 160000
index 000000000..d63d02696
--- /dev/null
+++ b/docs-scripts
@@ -0,0 +1 @@
+Subproject commit d63d02696ad23a3104636207152d2ac393291315
diff --git a/nbs/docs/contribute/step-by-step.md b/nbs/docs/contribute/step-by-step.md
index b8215b875..49848ff9d 100644
--- a/nbs/docs/contribute/step-by-step.md
+++ b/nbs/docs/contribute/step-by-step.md
@@ -15,35 +15,35 @@ Sometimes, diving into a new technology can be challenging and overwhelming. We'
7. [Start Coding](#start-coding)
8. [Example with Screen-shots](#example-with-screen-shots)
-## Prerequisites
+## Prerequisites
-- _GitHub_: You should already have a GitHub account and a basic understanding of its functionalities. Alternatively check [this guide](https://docs.github.com/en/get-started).
-- _Python_: Python should be installed on your system. Alternatively check [this guide](https://www.python.org/downloads/).
-- _conda_: You need to have conda installed, along with a good grasp of fundamental operations such as creating environments, and activating and deactivating them. Alternatively check [this guide](https://conda.io/projects/conda/en/latest/user-guide/install/index.html).
+- *GitHub*: You should already have a GitHub account and a basic understanding of its functionalities. Alternatively check [this guide](https://docs.github.com/en/get-started).
+- *Python*: Python should be installed on your system. Alternatively check [this guide](https://www.python.org/downloads/).
+- *conda*: You need to have conda installed, along with a good grasp of fundamental operations such as creating environments, and activating and deactivating them. Alternatively check [this guide](https://conda.io/projects/conda/en/latest/user-guide/install/index.html).
## Git `fork-and-pull` worklow
-**1. Fork the Project:**
+**1. Fork the Project:**
Start by forking the Nixtla repository to your own GitHub account. This creates a personal copy of the project where you can make changes without affecting the main repository.
**2. Clone the Forked Repository**
-Clone the forked repository to your local machine using `git clone https://github.com//nixtla.git`. This allows you to work with the code directly on your system.
+Clone the forked repository to your local machine using `git clone https://github.com//nixtla.git`. This allows you to work with the code directly on your system.
-**3. Create a Branch:**
+**3. Create a Branch:**
Branching in GitHub is a key strategy for effectively managing and isolating changes to your project. It allows you to segregate work on different features, fixes, and issues without interfering with the main, production-ready codebase.
-1. _Main Branch_: The default branch with production-ready code.
+1. *Main Branch*: The default branch with production-ready code.
-2. _Feature Branches_: For new features, create branches prefixed with 'feature/', like `git checkout -b feature/new-model`.
+2. *Feature Branches*: For new features, create branches prefixed with 'feature/', like `git checkout -b feature/new-model`.
-3. _Fix Branches_: For bug fixes, use 'fix/' prefix, like `git checkout -b fix/forecasting-bug`.
+3. *Fix Branches*: For bug fixes, use 'fix/' prefix, like `git checkout -b fix/forecasting-bug`.
-4. _Issue Branches_: For specific issues, use `git checkout -b issue/issue-number` or `git checkout -b issue/issue-description`.
+4. *Issue Branches*: For specific issues, use `git checkout -b issue/issue-number` or `git checkout -b issue/issue-description`.
After testing, branches are merged back into the main branch via a pull request, and then typically deleted to maintain a clean repository. You can read more about github and branching [here](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository).
-## Set Up a Conda Environment
+## Set Up a Conda Environment
> If you want to use Docker or Codespaces, let us know opening an issue and we will set you up.
@@ -51,13 +51,13 @@ Next, you'll need to set up a [Conda](https://docs.conda.io/en/latest/) environm
First, ensure you have Anaconda or Miniconda installed on your system. Alternatively checkout these guides: [Anaconda](https://www.anaconda.com/), [Miniconda](https://docs.conda.io/en/latest/miniconda.html), and [Mamba](https://mamba.readthedocs.io/en/latest/).
-Then, you can create a new environment using `conda create -n nixtla-env python=3.10`.
+Then, you can create a new environment using `conda create -n nixtla-env python=3.10`.
-You can also use mamba for creating the environment (mamba is faster than Conda) using `mamba create -n nixtla-env python=3.10`.
+You can also use mamba for creating the environment (mamba is faster than Conda) using `mamba create -n nixtla-env python=3.10`.
You can replace `nixtla-env` for something more meaningful to you. Eg. `statsforecast-env` or `mlforecast-env`. You can always check the list of environments in your system using `conda env list`.
-Activate your new environment with `conda activate nixtla-env`.
+Activate your new environment with `conda activate nixtla-env`.
## Install required libraries for development
@@ -71,7 +71,7 @@ Sometimes (e.g. StatsForecast) the `enviorment.yml` is sometimes inside a folder
## Start editable mode
-Install the library in editable mode using `pip install -e ".[dev]"`.
+Install the library in editable mode using `pip install -e ".[dev]"`.
This means the package is linked directly to the source code, allowing any changes made to the source code to be immediately reflected in your Python environment without the need to reinstall the package. This is useful for testing changes during package development.
@@ -101,8 +101,9 @@ Open a jupyter notebook using `jupyter lab` (or VS Code).
2. **Commit Your Changes:** Add the changed files using `git add [your_modified_file_0.ipynb] [your_modified_file_1.ipynb]`, then commit these changes using `git commit -m ": "`. Please use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
-3. **Push Your Changes:**
- Push your changes to the remote repository on GitHub with `git push origin feature/your-feature-name`.
+
+3. **Push Your Changes:**
+Push your changes to the remote repository on GitHub with `git push origin feature/your-feature-name`.
4. **Open a Pull Request:** Open a pull request from your new branch on the Nixtla repository on GitHub. Provide a thorough description of your changes when creating the pull request.
@@ -117,7 +118,6 @@ You can find a detailed step by step buide with screen-shots below.
## Example with Screen-shots
### 1. Create a fork of the mlforecast repo
-
The first thing you need to do is create a fork of the GitHub repository to your own account:
@@ -229,5 +229,4 @@ Finally, you will see something like this:
## Notes
-
-- This file was generated using [this file](https://github.com/Nixtla/nixtla-commons/blob/main/docs/contribute/step-by-step.md). Please change that file if you want to enhance the document.
+- This file was generated using [this file](https://github.com/Nixtla/nixtla-commons/blob/main/docs/contribute/step-by-step.md). Please change that file if you want to enhance the document.
\ No newline at end of file
diff --git a/nbs/docs/how-to-guides/migrating_R.qmd b/nbs/docs/how-to-guides/migrating_R.qmd
index e973d9d90..352be78f5 100644
--- a/nbs/docs/how-to-guides/migrating_R.qmd
+++ b/nbs/docs/how-to-guides/migrating_R.qmd
@@ -1,2 +1,6 @@
+---
+title: Migrating from R
+---
+
## 🚧 We are working on this site.
This site is currently in development. If you are particularly interested in this section, please open a GitHub Issue, and we will prioritize it.
\ No newline at end of file
diff --git a/nbs/docs/models/Holt.ipynb b/nbs/docs/models/Holt.ipynb
index 1d961204c..2705fc903 100644
--- a/nbs/docs/models/Holt.ipynb
+++ b/nbs/docs/models/Holt.ipynb
@@ -112,7 +112,7 @@
"\n",
"For each method there exist two models: one with additive errors and one with multiplicative errors. The point forecasts produced by the models are identical if they use the same smoothing parameter values. They will, however, generate different prediction intervals.\n",
"\n",
- "To distinguish between a model with additive errors and one with multiplicative errors. We label each state space model as ETS( .,.,.) for (Error, Trend, Seasonal). This label can also be thought of as ExponenTial Smoothing. Using the same notation as in Table 7.5, the possibilities for each component are: Error =\\\\{A,M } , Trend =\\\\{ N,A,A d} and Seasonal =\\\\{ N,A,M }\n",
+ "To distinguish between a model with additive errors and one with multiplicative errors. We label each state space model as ETS( .,.,.) for (Error, Trend, Seasonal). This label can also be thought of as ExponenTial Smoothing. Using the same notation as in Table 7.5, the possibilities for each component are: $Error=\\{A,M\\}$, $Trend=\\{N,A,A_d\\}$ and $Seasonal=\\{N,A,M\\}$\n",
"\n",
"For our case, the linear Holt model with a trend, we are going to see two cases, both for the additive and the multiplicative\n",
"\n",
diff --git a/nbs/favicon.png b/nbs/favicon.png
new file mode 100644
index 000000000..9fb85089d
Binary files /dev/null and b/nbs/favicon.png differ
diff --git a/nbs/favicon_png.png b/nbs/favicon_png.png
new file mode 100644
index 000000000..7c7684de2
Binary files /dev/null and b/nbs/favicon_png.png differ
diff --git a/nbs/imgs/logo/dark.png b/nbs/imgs/logo/dark.png
deleted file mode 100644
index 4142a0bbd..000000000
Binary files a/nbs/imgs/logo/dark.png and /dev/null differ
diff --git a/nbs/imgs/logo/light.png b/nbs/imgs/logo/light.png
deleted file mode 100644
index bbb99b549..000000000
Binary files a/nbs/imgs/logo/light.png and /dev/null differ
diff --git a/nbs/mint.json b/nbs/mint.json
new file mode 100644
index 000000000..86817bc40
--- /dev/null
+++ b/nbs/mint.json
@@ -0,0 +1,122 @@
+{
+ "$schema": "https://mintlify.com/schema.json",
+ "name": "Nixtla",
+ "logo": {
+ "light": "/light.png",
+ "dark": "/dark.png"
+ },
+ "favicon": "/favicon.svg",
+ "colors": {
+ "primary": "#0E0E0E",
+ "light": "#FAFAFA",
+ "dark": "#0E0E0E",
+ "anchors": {
+ "from": "#2AD0CA",
+ "to": "#0E00F8"
+ }
+ },
+ "topbarCtaButton": {
+ "type": "github",
+ "url": "https://github.com/Nixtla/nixtla"
+ },
+ "topAnchor": {
+ "name": "StatsForecast",
+ "icon": "bolt"
+ },
+ "navigation": [
+ {
+ "group": "",
+ "pages": ["index.html"]
+ },
+ {
+ "group": "Getting Started",
+ "pages": [
+ "docs/getting-started/installation.html",
+ "docs/getting-started/getting_started_short.html",
+ "docs/getting-started/getting_started_complete.html"
+ ]
+ },
+ {
+ "group": "Tutorials",
+ "pages": [
+ "docs/tutorials/anomalydetection.html",
+ "docs/tutorials/conformalprediction.html",
+ "docs/tutorials/crossvalidation.html",
+ "docs/tutorials/electricityloadforecasting.html",
+ "docs/tutorials/electricitypeakforecasting.html",
+ "docs/tutorials/garch_tutorial.html",
+ "docs/tutorials/intermittentdata.html",
+ "docs/tutorials/multipleseasonalities.html",
+ "docs/tutorials/statisticalneuralmethods.html",
+ "docs/tutorials/uncertaintyintervals.html"
+ ]
+ },
+ {
+ "group": "How to Guides",
+ "pages": [
+ "docs/how-to-guides/automatic_forecasting.html",
+ "docs/how-to-guides/amazonstatsforecast.html",
+ "docs/how-to-guides/autoarima_vs_prophet.html",
+ "docs/how-to-guides/dask.html",
+ "docs/how-to-guides/ets_ray_m5.html",
+ "docs/how-to-guides/exogenous.html",
+ "docs/how-to-guides/getting_started_complete_polars.html",
+ "docs/how-to-guides/migrating_R",
+ "docs/how-to-guides/numba_cache.html",
+ "docs/how-to-guides/prophet_spark_m5.html",
+ "docs/how-to-guides/ray.html",
+ "docs/how-to-guides/spark.html"
+ ]
+ },
+ {
+ "group": "Model References",
+ "pages": [
+ "docs/models/adida.html",
+ "docs/models/arch.html",
+ "docs/models/arima.html",
+ "docs/models/autoarima.html",
+ "docs/models/autoces.html",
+ "docs/models/autoets.html",
+ "docs/models/autoregressive.html",
+ "docs/models/autotheta.html",
+ "docs/models/crostonclassic.html",
+ "docs/models/crostonoptimized.html",
+ "docs/models/crostonsba.html",
+ "docs/models/dynamicoptimizedtheta.html",
+ "docs/models/dynamicstandardtheta.html",
+ "docs/models/garch.html",
+ "docs/models/holt.html",
+ "docs/models/holtwinters.html",
+ "docs/models/imapa.html",
+ "docs/models/multipleseasonaltrend.html",
+ "docs/models/optimizedtheta.html",
+ "docs/models/seasonalexponentialsmoothing.html",
+ "docs/models/seasonalexponentialsmoothingoptimized.html",
+ "docs/models/simpleexponentialoptimized.html",
+ "docs/models/simpleexponentialsmoothing.html",
+ "docs/models/standardtheta.html",
+ "docs/models/tsb.html"
+ ]
+ },
+ {
+ "group": "API Reference",
+ "pages": [
+ "src/core/core.html",
+ "src/core/distributed.fugue.html",
+ "src/core/models.html",
+ "src/core/models_intro"
+ ]
+ },
+ {
+ "group": "Contributing",
+ "pages": [
+ "docs/contribute/contribute",
+ "docs/contribute/docs",
+ "docs/contribute/issue-labels",
+ "docs/contribute/issues",
+ "docs/contribute/step-by-step",
+ "docs/contribute/techstack"
+ ]
+ }
+ ]
+}
diff --git a/nbs/sidebar.yml b/nbs/sidebar.yml
new file mode 100644
index 000000000..ce8e9088f
--- /dev/null
+++ b/nbs/sidebar.yml
@@ -0,0 +1,19 @@
+website:
+ reader-mode: false
+ sidebar:
+ collapse-level: 1
+ contents:
+ - index.ipynb
+ - text: "--"
+ - section: "Getting Started"
+ contents: docs/getting-started/*
+ - section: "Tutorials"
+ contents: docs/tutorials/*
+ - section: "How-To Guides"
+ contents: docs/how-to-guides/*
+ - section: "Model References"
+ contents: docs/models/*
+ - section: "API Reference"
+ contents: src/core/*
+ - section: "Contributing"
+ contents: docs/contribute/*
diff --git a/nbs/src/core/core.ipynb b/nbs/src/core/core.ipynb
index 91d28c4f9..5c8308d38 100644
--- a/nbs/src/core/core.ipynb
+++ b/nbs/src/core/core.ipynb
@@ -166,14 +166,21 @@
" return False\n",
" return np.allclose(self.data, other.data) and np.array_equal(self.indptr, other.indptr)\n",
" \n",
- " def fit(self, models):\n",
+ " def fit(self, models, fallback_model=None):\n",
" fm = np.full((self.n_groups, len(models)), np.nan, dtype=object)\n",
" for i, grp in enumerate(self):\n",
" y = grp[:, 0] if grp.ndim == 2 else grp\n",
" X = grp[:, 1:] if (grp.ndim == 2 and grp.shape[1] > 1) else None\n",
" for i_model, model in enumerate(models):\n",
- " new_model = model.new()\n",
- " fm[i, i_model] = new_model.fit(y=y, X=X)\n",
+ " try:\n",
+ " new_model = model.new()\n",
+ " fm[i, i_model] = new_model.fit(y=y, X=X)\n",
+ " except Exception as error:\n",
+ " if fallback_model is not None:\n",
+ " new_fallback_model = fallback_model.new()\n",
+ " fm[i, i_model] = new_fallback_model.fit(y=y, X=X)\n",
+ " else:\n",
+ " raise error\n",
" return fm\n",
" \n",
" def _get_cols(self, models, attr, h, X, level=tuple()):\n",
@@ -354,9 +361,13 @@
" else:\n",
" if i_window == 0:\n",
" # for the first window we have to fit each model\n",
- " model = model.fit(y=y_train, X=X_train)\n",
- " if fallback_model is not None:\n",
- " fallback_model = fallback_model.fit(y=y_train, X=X_train)\n",
+ " try:\n",
+ " model = model.fit(y=y_train, X=X_train)\n",
+ " except Exception as error:\n",
+ " if fallback_model is not None:\n",
+ " fallback_model = fallback_model.fit(y=y_train, X=X_train)\n",
+ " else:\n",
+ " raise error\n",
" try:\n",
" res_i = model.forward(h=h, y=y_train, X=X_train, \n",
" X_future=X_future, fitted=fitted, **kwargs)\n",
@@ -682,6 +693,47 @@
"np.testing.assert_array_equal(fcst_cv_f['fitted']['values'], fcst_cv_naive['fitted']['values'])"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e05f8b34",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "# test fallback model under failed fit for cross validation\n",
+ "class FailedFit:\n",
+ "\n",
+ " def __init__(self):\n",
+ " pass\n",
+ "\n",
+ " def forecast(self):\n",
+ " pass\n",
+ "\n",
+ " def fit(self, y, X):\n",
+ " raise Exception('Failed fit')\n",
+ "\n",
+ " def __repr__(self):\n",
+ " return \"FailedFit\"\n",
+ "\n",
+ "fcst_cv_f = ga.cross_validation(\n",
+ " models=[FailedFit()], \n",
+ " fallback_model=Naive(), h=2, \n",
+ " test_size=5,\n",
+ " refit=False,\n",
+ " fitted=True,\n",
+ ")\n",
+ "fcst_cv_naive = ga.cross_validation(\n",
+ " models=[Naive()], \n",
+ " h=2, \n",
+ " test_size=5,\n",
+ " refit=False,\n",
+ " fitted=True,\n",
+ ")\n",
+ "test_eq(fcst_cv_f['forecasts'], fcst_cv_naive['forecasts'])\n",
+ "np.testing.assert_array_equal(fcst_cv_f['fitted']['values'], fcst_cv_naive['fitted']['values'])"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -1333,7 +1385,7 @@
" self._set_prediction_intervals(prediction_intervals=prediction_intervals)\n",
" self._prepare_fit(df, sort_df)\n",
" if self.n_jobs == 1:\n",
- " self.fitted_ = self.ga.fit(models=self.models)\n",
+ " self.fitted_ = self.ga.fit(models=self.models, fallback_model=self.fallback_model)\n",
" else:\n",
" self.fitted_ = self._fit_parallel()\n",
" return self\n",
@@ -1725,10 +1777,10 @@
" with Pool(self.n_jobs, **pool_kwargs) as executor:\n",
" futures = []\n",
" for ga in gas:\n",
- " future = executor.apply_async(ga.fit, (self.models,))\n",
+ " future = executor.apply_async(ga.fit, (self.models, self.fallback_model))\n",
" futures.append(future)\n",
" fm = np.vstack([f.get() for f in futures])\n",
- " return fm\n",
+ " return fm \n",
" \n",
" def _get_gas_Xs(self, X):\n",
" gas = self.ga.split(self.n_jobs)\n",
diff --git a/nbs/src/core/distributed.fugue.ipynb b/nbs/src/core/distributed.fugue.ipynb
index bc7ea0a38..0b9c39cc6 100644
--- a/nbs/src/core/distributed.fugue.ipynb
+++ b/nbs/src/core/distributed.fugue.ipynb
@@ -447,7 +447,7 @@
"source": [
"### Distributed Forecast\n",
"\n",
- "For extremely fast distributed predictions we use FugueBackend as backend that operates like the original [StatsForecast.forecast](https://nixtla.github.io/statsforecast/core.html#statsforecast.forecast) method.\n",
+ "For extremely fast distributed predictions we use FugueBackend as backend that operates like the original [StatsForecast.forecast](https://nixtla.github.io/statsforecast/src/core/core.html#statsforecast.forecast) method.\n",
"\n",
"It receives as input a pandas.DataFrame with columns [`unique_id`,`ds`,`y`] and exogenous, where the `ds` (datestamp) column should be of a format expected by Pandas. The `y` column must be numeric, and represents the measurement we wish to forecast. And the `unique_id` uniquely identifies the series in the panel data."
]
@@ -566,7 +566,7 @@
"source": [
"### Distributed Cross-Validation\n",
"\n",
- "For extremely fast distributed temporcal cross-validation we use `cross_validation` method that operates like the original [StatsForecast.cross_validation](https://nixtla.github.io/statsforecast/core.html#statsforecast) method."
+ "For extremely fast distributed temporcal cross-validation we use `cross_validation` method that operates like the original [StatsForecast.cross_validation](https://nixtla.github.io/statsforecast/src/core/core.html#statsforecast.cross_validation) method."
]
},
{
diff --git a/nbs/src/core/models.ipynb b/nbs/src/core/models.ipynb
index 217c8e54a..3c14b4f69 100644
--- a/nbs/src/core/models.ipynb
+++ b/nbs/src/core/models.ipynb
@@ -5429,6 +5429,40 @@
" residuals = y - out[\"fitted\"]\n",
" sigma = _calculate_sigma(residuals, len(residuals) - 1)\n",
" res = _add_fitted_pi(res=res, se=sigma, level=level)\n",
+ " return res\n",
+ " \n",
+ " def forward(\n",
+ " self, \n",
+ " y: np.ndarray,\n",
+ " h: int,\n",
+ " X: Optional[np.ndarray] = None,\n",
+ " X_future: Optional[np.ndarray] = None,\n",
+ " level: Optional[List[int]] = None,\n",
+ " fitted: bool = False,\n",
+ " ):\n",
+ " \"\"\" Apply fitted model to an new/updated series.\n",
+ "\n",
+ " Parameters\n",
+ " ----------\n",
+ " y : numpy.array \n",
+ " Clean time series of shape (n,). \n",
+ " h: int\n",
+ " Forecast horizon.\n",
+ " X : array-like\n",
+ " Optional insample exogenous of shape (t, n_x). \n",
+ " X_future : array-like\n",
+ " Optional exogenous of shape (h, n_x). \n",
+ " level : List[float]\n",
+ " Confidence levels (0-100) for prediction intervals.\n",
+ " fitted : bool\n",
+ " Whether or not to return insample predictions.\n",
+ "\n",
+ " Returns\n",
+ " -------\n",
+ " forecasts : dict\n",
+ " Dictionary with entries `mean` for point predictions and `level_*` for probabilistic predictions.\n",
+ " \"\"\"\n",
+ " res = self.forecast(y=y, h=h, X=X, X_future=X_future, level=level, fitted=fitted)\n",
" return res"
]
},
@@ -5489,6 +5523,23 @@
"_plot_insample_pi(fcst_naive)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "# Unit test forward:=forecast\n",
+ "naive = Naive()\n",
+ "fcst_naive = naive.forward(ap,12,None,None,(80,95), True)\n",
+ "np.testing.assert_almost_equal(\n",
+ " fcst_naive['lo-80'],\n",
+ " np.array([388.7984, 370.9037, 357.1726, 345.5967, 335.3982, 326.1781, 317.6992, 309.8073, 302.3951, 295.3845, 288.7164, 282.3452]),\n",
+ " decimal=4\n",
+ ") # this is almost equal since Hyndman's forecasts are rounded up to 4 decimals"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -10914,6 +10965,40 @@
" if fitted:\n",
" res[f'fitted-lo-{lv}'] = fitted_vals\n",
" res[f'fitted-hi-{lv}'] = fitted_vals\n",
+ " return res\n",
+ " \n",
+ " def forward(\n",
+ " self,\n",
+ " y: np.ndarray,\n",
+ " h: int,\n",
+ " X: Optional[np.ndarray] = None,\n",
+ " X_future: Optional[np.ndarray] = None,\n",
+ " level: Optional[List[int]] = None,\n",
+ " fitted: bool = False,\n",
+ " ):\n",
+ " \"\"\"Apply Constant model predictions to a new/updated time series.\n",
+ "\n",
+ " Parameters\n",
+ " ----------\n",
+ " y : numpy.array \n",
+ " Clean time series of shape (n, ). \n",
+ " h : int \n",
+ " Forecast horizon.\n",
+ " X : array-like \n",
+ " Optional insample exogenous of shape (t, n_x). \n",
+ " X_future : array-like \n",
+ " Optional exogenous of shape (h, n_x). \n",
+ " level : List[float]\n",
+ " Confidence levels for prediction intervals.\n",
+ " fitted : bool \n",
+ " Whether or not returns insample predictions.\n",
+ "\n",
+ " Returns\n",
+ " -------\n",
+ " forecasts : dict \n",
+ " Dictionary with entries `constant` for point predictions and `level_*` for probabilistic predictions.\n",
+ " \"\"\"\n",
+ " res = self.forecast(y=y, h=h, X=X, X_future=X_future, level=level, fitted=fitted)\n",
" return res"
]
},
@@ -10938,6 +11023,16 @@
"constant_model.forecast(ap, 12, level=[90, 80])"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "constant_model.forward(ap, 12, level=[90, 80])"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -11001,6 +11096,15 @@
"show_doc(ConstantModel.predict_in_sample, title_level=3)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_doc(ConstantModel.forward, title_level=3)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -11069,6 +11173,16 @@
"zero_model.forecast(ap, 12, level=[90, 80])"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#| hide\n",
+ "zero_model.forward(ap, 12, level=[90, 80])"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -11132,6 +11246,15 @@
"show_doc(ZeroModel.predict_in_sample, title_level=3, name='ZeroModel.predict_in_sample')"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_doc(ZeroModel.forward, title_level=3, name='ZeroModel.forward')"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
diff --git a/settings.ini b/settings.ini
index ab5f35046..abdcc7497 100644
--- a/settings.ini
+++ b/settings.ini
@@ -20,7 +20,7 @@ ray_requirements = fugue[ray]>=0.8.1 protobuf>=3.15.3,<4.0.0
dask_requirements = fugue[dask]>=0.8.1
spark_requirements = fugue[spark]>=0.8.1
plotly_requirements = plotly plotly-resampler
-dev_requirements = nbdev black mypy flake8 ray protobuf>=3.15.3,<4.0.0 matplotlib pmdarima prophet scikit-learn fugue[dask,ray,spark]>=0.8.1 datasetsforecast supersmoother
+dev_requirements = nbdev black mypy flake8 ray protobuf>=3.15.3,<4.0.0 matplotlib pmdarima prophet scikit-learn fugue[dask,ray,spark]>=0.8.1 datasetsforecast supersmoother nbdev_plotly
nbs_path = nbs
doc_path = _docs
recursive = True
@@ -32,4 +32,4 @@ title = %(lib_name)s
black_formatting = True
jupyter_hooks = True
clean_ids = True
-readme_nb = index.ipynb
\ No newline at end of file
+readme_nb = index.ipynb
diff --git a/statsforecast/_modidx.py b/statsforecast/_modidx.py
index 30f335f72..91c4471ea 100644
--- a/statsforecast/_modidx.py
+++ b/statsforecast/_modidx.py
@@ -371,6 +371,8 @@
'statsforecast/models.py'),
'statsforecast.models.ConstantModel.forecast': ( 'src/core/models.html#constantmodel.forecast',
'statsforecast/models.py'),
+ 'statsforecast.models.ConstantModel.forward': ( 'src/core/models.html#constantmodel.forward',
+ 'statsforecast/models.py'),
'statsforecast.models.ConstantModel.predict': ( 'src/core/models.html#constantmodel.predict',
'statsforecast/models.py'),
'statsforecast.models.ConstantModel.predict_in_sample': ( 'src/core/models.html#constantmodel.predict_in_sample',
@@ -498,6 +500,8 @@
'statsforecast.models.Naive.fit': ('src/core/models.html#naive.fit', 'statsforecast/models.py'),
'statsforecast.models.Naive.forecast': ( 'src/core/models.html#naive.forecast',
'statsforecast/models.py'),
+ 'statsforecast.models.Naive.forward': ( 'src/core/models.html#naive.forward',
+ 'statsforecast/models.py'),
'statsforecast.models.Naive.predict': ( 'src/core/models.html#naive.predict',
'statsforecast/models.py'),
'statsforecast.models.Naive.predict_in_sample': ( 'src/core/models.html#naive.predict_in_sample',
diff --git a/statsforecast/core.py b/statsforecast/core.py
index 44f1e70d7..1b9829190 100644
--- a/statsforecast/core.py
+++ b/statsforecast/core.py
@@ -61,14 +61,21 @@ def __eq__(self, other):
self.indptr, other.indptr
)
- def fit(self, models):
+ def fit(self, models, fallback_model=None):
fm = np.full((self.n_groups, len(models)), np.nan, dtype=object)
for i, grp in enumerate(self):
y = grp[:, 0] if grp.ndim == 2 else grp
X = grp[:, 1:] if (grp.ndim == 2 and grp.shape[1] > 1) else None
for i_model, model in enumerate(models):
- new_model = model.new()
- fm[i, i_model] = new_model.fit(y=y, X=X)
+ try:
+ new_model = model.new()
+ fm[i, i_model] = new_model.fit(y=y, X=X)
+ except Exception as error:
+ if fallback_model is not None:
+ new_fallback_model = fallback_model.new()
+ fm[i, i_model] = new_fallback_model.fit(y=y, X=X)
+ else:
+ raise error
return fm
def _get_cols(self, models, attr, h, X, level=tuple()):
@@ -331,11 +338,15 @@ def cross_validation(
else:
if i_window == 0:
# for the first window we have to fit each model
- model = model.fit(y=y_train, X=X_train)
- if fallback_model is not None:
- fallback_model = fallback_model.fit(
- y=y_train, X=X_train
- )
+ try:
+ model = model.fit(y=y_train, X=X_train)
+ except Exception as error:
+ if fallback_model is not None:
+ fallback_model = fallback_model.fit(
+ y=y_train, X=X_train
+ )
+ else:
+ raise error
try:
res_i = model.forward(
h=h,
@@ -401,7 +412,7 @@ def split_fm(self, fm, n_chunks):
if x.size
]
-# %% ../nbs/src/core/core.ipynb 22
+# %% ../nbs/src/core/core.ipynb 23
class DataFrameProcessing:
"""
A utility to process Pandas or Polars dataframes for time series forecasting.
@@ -442,6 +453,7 @@ def __init__(
sort_dataframe: bool,
validate: Optional[bool] = True,
):
+
self.dataframe = dataframe
self.sort_dataframe = sort_dataframe
self.validate = validate
@@ -692,7 +704,7 @@ def _check_datetime(self, arr: np.ndarray) -> Union[pd.DatetimeIndex, np.ndarray
raise Exception(msg) from e
return arr
-# %% ../nbs/src/core/core.ipynb 25
+# %% ../nbs/src/core/core.ipynb 26
def _cv_dates(last_dates, freq, h, test_size, step_size=1):
# assuming step_size = 1
if (test_size - h) % step_size:
@@ -731,7 +743,7 @@ def _cv_dates(last_dates, freq, h, test_size, step_size=1):
dates = dates.reset_index(drop=True)
return dates
-# %% ../nbs/src/core/core.ipynb 29
+# %% ../nbs/src/core/core.ipynb 30
def _get_n_jobs(n_groups, n_jobs):
if n_jobs == -1 or (n_jobs is None):
actual_n_jobs = cpu_count()
@@ -739,7 +751,7 @@ def _get_n_jobs(n_groups, n_jobs):
actual_n_jobs = n_jobs
return min(n_groups, actual_n_jobs)
-# %% ../nbs/src/core/core.ipynb 32
+# %% ../nbs/src/core/core.ipynb 33
def _parse_ds_type(df):
dt_col = df["ds"]
dt_check = pd.api.types.is_datetime64_any_dtype(dt_col)
@@ -757,7 +769,7 @@ def _parse_ds_type(df):
raise Exception(msg) from e
return df
-# %% ../nbs/src/core/core.ipynb 33
+# %% ../nbs/src/core/core.ipynb 34
class _StatsForecast:
def __init__(
self,
@@ -871,7 +883,9 @@ def fit(
self._set_prediction_intervals(prediction_intervals=prediction_intervals)
self._prepare_fit(df, sort_df)
if self.n_jobs == 1:
- self.fitted_ = self.ga.fit(models=self.models)
+ self.fitted_ = self.ga.fit(
+ models=self.models, fallback_model=self.fallback_model
+ )
else:
self.fitted_ = self._fit_parallel()
return self
@@ -1300,7 +1314,9 @@ def _fit_parallel(self):
with Pool(self.n_jobs, **pool_kwargs) as executor:
futures = []
for ga in gas:
- future = executor.apply_async(ga.fit, (self.models,))
+ future = executor.apply_async(
+ ga.fit, (self.models, self.fallback_model)
+ )
futures.append(future)
fm = np.vstack([f.get() for f in futures])
return fm
@@ -1533,7 +1549,7 @@ def plot(
def __repr__(self):
return f"StatsForecast(models=[{','.join(map(repr, self.models))}])"
-# %% ../nbs/src/core/core.ipynb 34
+# %% ../nbs/src/core/core.ipynb 35
class ParallelBackend:
def forecast(self, df, models, freq, fallback_model=None, **kwargs: Any) -> Any:
model = _StatsForecast(
@@ -1554,7 +1570,7 @@ def cross_validation(
def make_backend(obj: Any, *args: Any, **kwargs: Any) -> ParallelBackend:
return ParallelBackend()
-# %% ../nbs/src/core/core.ipynb 35
+# %% ../nbs/src/core/core.ipynb 36
class StatsForecast(_StatsForecast):
"""Train statistical models.
diff --git a/statsforecast/models.py b/statsforecast/models.py
index 479757de2..8d7a81037 100644
--- a/statsforecast/models.py
+++ b/statsforecast/models.py
@@ -2550,6 +2550,7 @@ def __init__(
alias: str = "Holt",
prediction_intervals: Optional[ConformalIntervals] = None,
):
+
self.season_length = season_length
self.error_type = error_type
self.alias = alias
@@ -2748,6 +2749,7 @@ def forecast(
level: Optional[List[int]] = None,
fitted: bool = False,
):
+
"""Memory Efficient HistoricAverage predictions.
This method avoids memory burden due from object storage.
@@ -2969,7 +2971,43 @@ def forecast(
res = _add_fitted_pi(res=res, se=sigma, level=level)
return res
-# %% ../nbs/src/core/models.ipynb 217
+ def forward(
+ self,
+ y: np.ndarray,
+ h: int,
+ X: Optional[np.ndarray] = None,
+ X_future: Optional[np.ndarray] = None,
+ level: Optional[List[int]] = None,
+ fitted: bool = False,
+ ):
+ """Apply fitted model to an new/updated series.
+
+ Parameters
+ ----------
+ y : numpy.array
+ Clean time series of shape (n,).
+ h: int
+ Forecast horizon.
+ X : array-like
+ Optional insample exogenous of shape (t, n_x).
+ X_future : array-like
+ Optional exogenous of shape (h, n_x).
+ level : List[float]
+ Confidence levels (0-100) for prediction intervals.
+ fitted : bool
+ Whether or not to return insample predictions.
+
+ Returns
+ -------
+ forecasts : dict
+ Dictionary with entries `mean` for point predictions and `level_*` for probabilistic predictions.
+ """
+ res = self.forecast(
+ y=y, h=h, X=X, X_future=X_future, level=level, fitted=fitted
+ )
+ return res
+
+# %% ../nbs/src/core/models.ipynb 218
@njit(nogil=NOGIL, cache=CACHE)
def _random_walk_with_drift(
y: np.ndarray, # time series
@@ -2989,7 +3027,7 @@ def _random_walk_with_drift(
fcst["fitted"] = fitted_vals
return fcst
-# %% ../nbs/src/core/models.ipynb 218
+# %% ../nbs/src/core/models.ipynb 219
class RandomWalkWithDrift(_TS):
def __init__(
self,
@@ -3166,7 +3204,7 @@ def forecast(
return res
-# %% ../nbs/src/core/models.ipynb 232
+# %% ../nbs/src/core/models.ipynb 233
class SeasonalNaive(_TS):
def __init__(
self,
@@ -3355,7 +3393,7 @@ def forecast(
return res
-# %% ../nbs/src/core/models.ipynb 246
+# %% ../nbs/src/core/models.ipynb 247
@njit(nogil=NOGIL, cache=CACHE)
def _window_average(
y: np.ndarray, # time series
@@ -3371,7 +3409,7 @@ def _window_average(
mean = _repeat_val(val=wavg, h=h)
return {"mean": mean}
-# %% ../nbs/src/core/models.ipynb 247
+# %% ../nbs/src/core/models.ipynb 248
class WindowAverage(_TS):
def __init__(
self,
@@ -3529,7 +3567,7 @@ def forecast(
raise Exception("You must pass `prediction_intervals` to " "compute them.")
return res
-# %% ../nbs/src/core/models.ipynb 258
+# %% ../nbs/src/core/models.ipynb 259
@njit(nogil=NOGIL, cache=CACHE)
def _seasonal_window_average(
y: np.ndarray,
@@ -3550,7 +3588,7 @@ def _seasonal_window_average(
out = _repeat_val_seas(season_vals=season_avgs, h=h, season_length=season_length)
return {"mean": out}
-# %% ../nbs/src/core/models.ipynb 259
+# %% ../nbs/src/core/models.ipynb 260
class SeasonalWindowAverage(_TS):
def __init__(
self,
@@ -3724,7 +3762,7 @@ def forecast(
raise Exception("You must pass `prediction_intervals` to compute them.")
return res
-# %% ../nbs/src/core/models.ipynb 271
+# %% ../nbs/src/core/models.ipynb 272
def _adida(
y: np.ndarray, # time series
h: int, # forecasting horizon
@@ -3745,7 +3783,7 @@ def _adida(
mean = _repeat_val(val=forecast, h=h)
return {"mean": mean}
-# %% ../nbs/src/core/models.ipynb 272
+# %% ../nbs/src/core/models.ipynb 273
class ADIDA(_TS):
def __init__(
self,
@@ -3907,7 +3945,7 @@ def forecast(
)
return res
-# %% ../nbs/src/core/models.ipynb 284
+# %% ../nbs/src/core/models.ipynb 285
@njit(nogil=NOGIL, cache=CACHE)
def _croston_classic(
y: np.ndarray, # time series
@@ -3929,7 +3967,7 @@ def _croston_classic(
mean = _repeat_val(val=mean, h=h)
return {"mean": mean}
-# %% ../nbs/src/core/models.ipynb 285
+# %% ../nbs/src/core/models.ipynb 286
class CrostonClassic(_TS):
def __init__(
self,
@@ -4086,7 +4124,7 @@ def forecast(
)
return res
-# %% ../nbs/src/core/models.ipynb 296
+# %% ../nbs/src/core/models.ipynb 297
def _croston_optimized(
y: np.ndarray, # time series
h: int, # forecasting horizon
@@ -4107,7 +4145,7 @@ def _croston_optimized(
mean = _repeat_val(val=mean, h=h)
return {"mean": mean}
-# %% ../nbs/src/core/models.ipynb 297
+# %% ../nbs/src/core/models.ipynb 298
class CrostonOptimized(_TS):
def __init__(
self,
@@ -4260,7 +4298,7 @@ def forecast(
raise Exception("You must pass `prediction_intervals` to compute them.")
return res
-# %% ../nbs/src/core/models.ipynb 308
+# %% ../nbs/src/core/models.ipynb 309
@njit(nogil=NOGIL, cache=CACHE)
def _croston_sba(
y: np.ndarray, # time series
@@ -4273,7 +4311,7 @@ def _croston_sba(
mean["mean"] *= 0.95
return mean
-# %% ../nbs/src/core/models.ipynb 309
+# %% ../nbs/src/core/models.ipynb 310
class CrostonSBA(_TS):
def __init__(
self,
@@ -4432,7 +4470,7 @@ def forecast(
)
return res
-# %% ../nbs/src/core/models.ipynb 320
+# %% ../nbs/src/core/models.ipynb 321
def _imapa(
y: np.ndarray, # time series
h: int, # forecasting horizon
@@ -4456,7 +4494,7 @@ def _imapa(
mean = _repeat_val(val=forecast, h=h)
return {"mean": mean}
-# %% ../nbs/src/core/models.ipynb 321
+# %% ../nbs/src/core/models.ipynb 322
class IMAPA(_TS):
def __init__(
self,
@@ -4612,7 +4650,7 @@ def forecast(
)
return res
-# %% ../nbs/src/core/models.ipynb 332
+# %% ../nbs/src/core/models.ipynb 333
@njit(nogil=NOGIL, cache=CACHE)
def _tsb(
y: np.ndarray, # time series
@@ -4633,7 +4671,7 @@ def _tsb(
mean = _repeat_val(val=forecast, h=h)
return {"mean": mean}
-# %% ../nbs/src/core/models.ipynb 333
+# %% ../nbs/src/core/models.ipynb 334
class TSB(_TS):
def __init__(
self,
@@ -4800,7 +4838,7 @@ def forecast(
raise Exception("You must pass `prediction_intervals` to compute them.")
return res
-# %% ../nbs/src/core/models.ipynb 345
+# %% ../nbs/src/core/models.ipynb 346
def _predict_mstl_seas(mstl_ob, h, season_length):
seasoncolumns = mstl_ob.filter(regex="seasonal*").columns
nseasons = len(seasoncolumns)
@@ -4817,7 +4855,7 @@ def _predict_mstl_seas(mstl_ob, h, season_length):
lastseas = seascomp.sum(axis=1)
return lastseas
-# %% ../nbs/src/core/models.ipynb 346
+# %% ../nbs/src/core/models.ipynb 347
class MSTL(_TS):
"""MSTL model.
@@ -4853,6 +4891,7 @@ def __init__(
alias: str = "MSTL",
prediction_intervals: Optional[ConformalIntervals] = None,
):
+
# check ETS model doesnt have seasonality
if repr(trend_forecaster) == "AutoETS":
if trend_forecaster.model[2] != "N":
@@ -5091,7 +5130,7 @@ def forward(
}
return res
-# %% ../nbs/src/core/models.ipynb 362
+# %% ../nbs/src/core/models.ipynb 363
class Theta(AutoTheta):
"""Standard Theta Method.
@@ -5127,7 +5166,7 @@ def __init__(
prediction_intervals=prediction_intervals,
)
-# %% ../nbs/src/core/models.ipynb 375
+# %% ../nbs/src/core/models.ipynb 376
class OptimizedTheta(AutoTheta):
"""Optimized Theta Method.
@@ -5163,7 +5202,7 @@ def __init__(
prediction_intervals=prediction_intervals,
)
-# %% ../nbs/src/core/models.ipynb 388
+# %% ../nbs/src/core/models.ipynb 389
class DynamicTheta(AutoTheta):
"""Dynamic Standard Theta Method.
@@ -5199,7 +5238,7 @@ def __init__(
prediction_intervals=prediction_intervals,
)
-# %% ../nbs/src/core/models.ipynb 401
+# %% ../nbs/src/core/models.ipynb 402
class DynamicOptimizedTheta(AutoTheta):
"""Dynamic Optimized Theta Method.
@@ -5235,7 +5274,7 @@ def __init__(
prediction_intervals=prediction_intervals,
)
-# %% ../nbs/src/core/models.ipynb 415
+# %% ../nbs/src/core/models.ipynb 416
class GARCH(_TS):
"""Generalized Autoregressive Conditional Heteroskedasticity (GARCH) model.
@@ -5429,7 +5468,7 @@ def forecast(
res = _add_fitted_pi(res=res, se=se, level=level)
return res
-# %% ../nbs/src/core/models.ipynb 428
+# %% ../nbs/src/core/models.ipynb 429
class ARCH(GARCH):
"""Autoregressive Conditional Heteroskedasticity (ARCH) model.
@@ -5475,7 +5514,7 @@ def __init__(
def __repr__(self):
return self.alias
-# %% ../nbs/src/core/models.ipynb 439
+# %% ../nbs/src/core/models.ipynb 440
class ConstantModel(_TS):
def __init__(self, constant: float, alias: str = "ConstantModel"):
"""Constant Model.
@@ -5624,7 +5663,43 @@ def forecast(
res[f"fitted-hi-{lv}"] = fitted_vals
return res
-# %% ../nbs/src/core/models.ipynb 450
+ def forward(
+ self,
+ y: np.ndarray,
+ h: int,
+ X: Optional[np.ndarray] = None,
+ X_future: Optional[np.ndarray] = None,
+ level: Optional[List[int]] = None,
+ fitted: bool = False,
+ ):
+ """Apply Constant model predictions to a new/updated time series.
+
+ Parameters
+ ----------
+ y : numpy.array
+ Clean time series of shape (n, ).
+ h : int
+ Forecast horizon.
+ X : array-like
+ Optional insample exogenous of shape (t, n_x).
+ X_future : array-like
+ Optional exogenous of shape (h, n_x).
+ level : List[float]
+ Confidence levels for prediction intervals.
+ fitted : bool
+ Whether or not returns insample predictions.
+
+ Returns
+ -------
+ forecasts : dict
+ Dictionary with entries `constant` for point predictions and `level_*` for probabilistic predictions.
+ """
+ res = self.forecast(
+ y=y, h=h, X=X, X_future=X_future, level=level, fitted=fitted
+ )
+ return res
+
+# %% ../nbs/src/core/models.ipynb 453
class ZeroModel(ConstantModel):
def __init__(self, alias: str = "ZeroModel"):
"""Returns Zero forecasts.
@@ -5638,7 +5713,7 @@ def __init__(self, alias: str = "ZeroModel"):
"""
super().__init__(constant=0, alias=alias)
-# %% ../nbs/src/core/models.ipynb 461
+# %% ../nbs/src/core/models.ipynb 466
class NaNModel(ConstantModel):
def __init__(self, alias: str = "NaNModel"):
"""NaN Model.