pyforesight 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ rust:
9
+ name: fmt and clippy
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: dtolnay/rust-toolchain@stable
14
+ with:
15
+ components: rustfmt, clippy
16
+ - run: cargo fmt --check
17
+ - run: cargo clippy --all-targets -- -D warnings
18
+
19
+ test:
20
+ name: test (${{ matrix.os }}, python ${{ matrix.python }})
21
+ runs-on: ${{ matrix.os }}
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ os: [ubuntu-latest, macos-latest, windows-latest]
26
+ python: ["3.9", "3.13"]
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: actions/setup-python@v5
30
+ with:
31
+ python-version: ${{ matrix.python }}
32
+ - uses: dtolnay/rust-toolchain@stable
33
+ - name: Build and install
34
+ run: pip install ".[test,pandas]" numpy
35
+ - name: Test
36
+ run: pytest -v
37
+ - name: Type stubs match the module
38
+ if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
39
+ run: |
40
+ pip install mypy
41
+ python -m mypy.stubtest foresight._foresight --ignore-disjoint-bases --allowlist stubtest-allowlist.txt
@@ -0,0 +1,38 @@
1
+ name: pages
2
+
3
+ # Publishes the static landing page in site/ to GitHub Pages on every push to
4
+ # main. Pages must be set to "GitHub Actions" as its source (Settings → Pages).
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ paths:
10
+ - "site/**"
11
+ - ".github/workflows/pages.yml"
12
+ workflow_dispatch:
13
+
14
+ permissions:
15
+ contents: read
16
+ pages: write
17
+ id-token: write
18
+
19
+ concurrency:
20
+ group: pages
21
+ cancel-in-progress: true
22
+
23
+ jobs:
24
+ deploy:
25
+ environment:
26
+ name: github-pages
27
+ url: ${{ steps.deployment.outputs.page_url }}
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - name: Disable Jekyll
32
+ run: touch site/.nojekyll
33
+ - uses: actions/configure-pages@v5
34
+ - uses: actions/upload-pages-artifact@v3
35
+ with:
36
+ path: site
37
+ - id: deployment
38
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,68 @@
1
+ name: release
2
+
3
+ # The file name workflow.yml is the one registered on PyPI as trusted
4
+ # publisher: do not rename it.
5
+ #
6
+ # Wheels for every platform on demand; on a tag vX.Y.Z they are also
7
+ # published to PyPI by trusted publishing (the project must be registered as
8
+ # a trusted publisher on pypi.org, environment "pypi").
9
+
10
+ on:
11
+ push:
12
+ tags: ["v*"]
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ wheels:
20
+ name: wheel (${{ matrix.os }}, ${{ matrix.target }})
21
+ runs-on: ${{ matrix.os }}
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ include:
26
+ - { os: ubuntu-latest, target: x86_64 }
27
+ - { os: ubuntu-latest, target: aarch64 }
28
+ - { os: macos-latest, target: aarch64 }
29
+ - { os: macos-latest, target: x86_64 }
30
+ - { os: windows-latest, target: x64 }
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: PyO3/maturin-action@v1
34
+ with:
35
+ target: ${{ matrix.target }}
36
+ args: --release --out dist
37
+ manylinux: auto
38
+ - uses: actions/upload-artifact@v4
39
+ with:
40
+ name: wheel-${{ matrix.os }}-${{ matrix.target }}
41
+ path: dist
42
+
43
+ sdist:
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: PyO3/maturin-action@v1
48
+ with:
49
+ command: sdist
50
+ args: --out dist
51
+ - uses: actions/upload-artifact@v4
52
+ with:
53
+ name: sdist
54
+ path: dist
55
+
56
+ publish:
57
+ if: startsWith(github.ref, 'refs/tags/v')
58
+ needs: [wheels, sdist]
59
+ runs-on: ubuntu-latest
60
+ environment: pypi
61
+ permissions:
62
+ id-token: write
63
+ steps:
64
+ - uses: actions/download-artifact@v4
65
+ with:
66
+ path: dist
67
+ merge-multiple: true
68
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ /target
2
+ __pycache__/
3
+ *.so
4
+ *.pyd
5
+ .venv/
6
+ dist/
7
+ .pytest_cache/
8
+ CLAUDE.md
@@ -0,0 +1,24 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this package, please cite it as below."
3
+ title: "pyforesight: time series forecasting that picks its model by what would have worked"
4
+ type: software
5
+ license: MIT
6
+ repository-code: "https://github.com/StrategicProjects/pyforesight"
7
+ url: "https://strategicprojects.github.io/pyforesight/"
8
+ authors:
9
+ - family-names: Leite
10
+ given-names: André
11
+ email: leite@castlab.org
12
+ orcid: "https://orcid.org/0000-0002-4718-9766"
13
+ - family-names: Wasiliew
14
+ given-names: Marcos
15
+ orcid: "https://orcid.org/0009-0004-4694-3159"
16
+ - family-names: Vasconcelos
17
+ given-names: Hugo
18
+ orcid: "https://orcid.org/0000-0001-6249-0920"
19
+ - family-names: Amorim
20
+ given-names: Carlos
21
+ orcid: "https://orcid.org/0000-0001-6315-8305"
22
+ - family-names: Bezerra
23
+ given-names: Diogo
24
+ orcid: "https://orcid.org/0000-0002-1216-8674"
@@ -0,0 +1,139 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "foresight"
7
+ version = "0.7.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "87da2acde51ebebfa0f2e5608b7998b917e101a43d91822c1254c768bc8dc6a0"
10
+
11
+ [[package]]
12
+ name = "heck"
13
+ version = "0.5.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
16
+
17
+ [[package]]
18
+ name = "libc"
19
+ version = "0.2.189"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
22
+
23
+ [[package]]
24
+ name = "once_cell"
25
+ version = "1.21.4"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
28
+
29
+ [[package]]
30
+ name = "portable-atomic"
31
+ version = "1.15.0"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
34
+
35
+ [[package]]
36
+ name = "proc-macro2"
37
+ version = "1.0.107"
38
+ source = "registry+https://github.com/rust-lang/crates.io-index"
39
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
40
+ dependencies = [
41
+ "unicode-ident",
42
+ ]
43
+
44
+ [[package]]
45
+ name = "pyforesight"
46
+ version = "0.1.0"
47
+ dependencies = [
48
+ "foresight",
49
+ "pyo3",
50
+ ]
51
+
52
+ [[package]]
53
+ name = "pyo3"
54
+ version = "0.29.2"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
57
+ dependencies = [
58
+ "libc",
59
+ "once_cell",
60
+ "portable-atomic",
61
+ "pyo3-build-config",
62
+ "pyo3-ffi",
63
+ "pyo3-macros",
64
+ ]
65
+
66
+ [[package]]
67
+ name = "pyo3-build-config"
68
+ version = "0.29.2"
69
+ source = "registry+https://github.com/rust-lang/crates.io-index"
70
+ checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
71
+ dependencies = [
72
+ "target-lexicon",
73
+ ]
74
+
75
+ [[package]]
76
+ name = "pyo3-ffi"
77
+ version = "0.29.2"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
80
+ dependencies = [
81
+ "libc",
82
+ "pyo3-build-config",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "pyo3-macros"
87
+ version = "0.29.2"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
90
+ dependencies = [
91
+ "proc-macro2",
92
+ "pyo3-macros-backend",
93
+ "quote",
94
+ "syn",
95
+ ]
96
+
97
+ [[package]]
98
+ name = "pyo3-macros-backend"
99
+ version = "0.29.2"
100
+ source = "registry+https://github.com/rust-lang/crates.io-index"
101
+ checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
102
+ dependencies = [
103
+ "heck",
104
+ "proc-macro2",
105
+ "quote",
106
+ "syn",
107
+ ]
108
+
109
+ [[package]]
110
+ name = "quote"
111
+ version = "1.0.47"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
114
+ dependencies = [
115
+ "proc-macro2",
116
+ ]
117
+
118
+ [[package]]
119
+ name = "syn"
120
+ version = "2.0.119"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
123
+ dependencies = [
124
+ "proc-macro2",
125
+ "quote",
126
+ "unicode-ident",
127
+ ]
128
+
129
+ [[package]]
130
+ name = "target-lexicon"
131
+ version = "0.13.5"
132
+ source = "registry+https://github.com/rust-lang/crates.io-index"
133
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
134
+
135
+ [[package]]
136
+ name = "unicode-ident"
137
+ version = "1.0.26"
138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
139
+ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
@@ -0,0 +1,21 @@
1
+ [package]
2
+ name = "pyforesight"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Python bindings of the foresight forecasting crate"
6
+ license = "MIT"
7
+ repository = "https://github.com/StrategicProjects/pyforesight"
8
+ publish = false
9
+ readme = "README.md"
10
+
11
+ [lib]
12
+ name = "_foresight"
13
+ crate-type = ["cdylib"]
14
+
15
+ [dependencies]
16
+ foresight = "=0.7.1"
17
+ pyo3 = { version = "0.29", features = ["abi3-py39"] }
18
+
19
+ [profile.release]
20
+ lto = "fat"
21
+ codegen-units = 1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 André Leite, Marcos Wasiliew, Hugo Vasconcelos, Carlos Amorim and Diogo Bezerra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyforesight
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Science/Research
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Rust
8
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
9
+ Classifier: Typing :: Typed
10
+ Requires-Dist: pandas>=1.5 ; extra == 'pandas'
11
+ Requires-Dist: pytest>=7 ; extra == 'test'
12
+ Provides-Extra: pandas
13
+ Provides-Extra: test
14
+ License-File: LICENSE
15
+ Summary: Time series forecasting that picks its model by what would have worked: rolling-origin backtests, empirical intervals, ARIMA, ETS, Prophet, TBATS, STL and ensembles, in Rust.
16
+ Keywords: forecasting,time series,arima,ets,prophet,backtest
17
+ Author-email: André Leite <leite@castlab.org>, Marcos Wasiliew <marcos.wasiliew@gmail.com>, Hugo Vasconcelos <hugo.vasconcelos@ufpe.br>, Carlos Amorim <carlos.agaf@ufpe.br>, Diogo Bezerra <diogo.bezerra@ufpe.br>
18
+ Maintainer-email: André Leite <leite@castlab.org>
19
+ License-Expression: MIT
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
22
+ Project-URL: Homepage, https://strategicprojects.github.io/pyforesight/
23
+ Project-URL: Issues, https://github.com/StrategicProjects/pyforesight/issues
24
+ Project-URL: Rust crate, https://crates.io/crates/foresight
25
+ Project-URL: Source, https://github.com/StrategicProjects/pyforesight
26
+
27
+ # pyforesight
28
+
29
+ [![CI](https://github.com/StrategicProjects/pyforesight/actions/workflows/ci.yml/badge.svg)](https://github.com/StrategicProjects/pyforesight/actions/workflows/ci.yml)
30
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
31
+ [![Dependencies: none](https://img.shields.io/badge/dependencies-none-brightgreen.svg)](pyproject.toml)
32
+
33
+ Time series forecasting in Python that picks its model by what would have
34
+ worked.
35
+
36
+ `foresight` fits several models to a series, replays the past to see how each
37
+ would have done, chooses by out-of-sample error and reports intervals taken
38
+ from the errors actually observed, including intervals for the total of the
39
+ next k periods.
40
+
41
+ The models, the backtest and the utilities are the Rust crate
42
+ [foresight](https://github.com/milkway/foresight), compiled into the package:
43
+ the numbers are the crate's, the backtest runs on all cores, and nothing else
44
+ is needed at run time. NumPy and pandas are accepted and, for pandas, produced
45
+ on request, but neither is required.
46
+
47
+ **Website:** <https://strategicprojects.github.io/pyforesight/> ·
48
+ [Português](README.pt-BR.md)
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install git+https://github.com/StrategicProjects/pyforesight
54
+ ```
55
+
56
+ Python 3.9 or later. Installing from the repository compiles the Rust code,
57
+ so it needs a Rust toolchain (<https://rustup.rs>).
58
+
59
+ ## Use
60
+
61
+ ```python
62
+ import foresight as fs
63
+
64
+ # monthly data whose first observation is in March
65
+ y = fs.monthly(values, first_month=3)
66
+
67
+ # replay the last 36 months, 12 months ahead, with every built-in model
68
+ report = fs.backtest(y)
69
+
70
+ best = report.best
71
+ print(f"{best.name}: MAPE {best.score:.1f}%")
72
+ for p in best.forecast:
73
+ lo, hi = p.interval(0.80)
74
+ print(p.horizon, round(p.mean), round(lo), round(hi))
75
+
76
+ half_year = best.cumulative(6) # the total of the next six months, with its own interval
77
+ report.to_pandas() # one row per candidate (needs pandas)
78
+ best.to_pandas() # the forecast with its intervals
79
+ ```
80
+
81
+ One model on its own:
82
+
83
+ ```python
84
+ # seasonal ARIMA on the log scale
85
+ fit = fs.log(fs.Arima.airline()).fit(y)
86
+ next_year = fit.forecast(12)
87
+
88
+ # orders chosen from the data, inspected
89
+ auto = fs.AutoArima().fit(fs.monthly(log_values, first_month=3))
90
+ auto.details["order"], auto.details["seasonal_order"], auto.aicc
91
+ ```
92
+
93
+ A trend that bends, with dated events:
94
+
95
+ ```python
96
+ model = fs.Prophet(
97
+ events={"campaign": [10, 34, 58, 82, 106, 130]}, # future ones included
98
+ steps={"new_law": 80}, # a lasting change of level
99
+ )
100
+ fit = model.fit(y)
101
+ fit.details["changepoints"], fit.details["effects"]
102
+ ```
103
+
104
+ Several models combined, and the wider set of candidates:
105
+
106
+ ```python
107
+ ensemble = fs.Ensemble(fs.defaults(), weighting="stacked")
108
+ report = fs.backtest(y, fs.thorough() + [ensemble.named("my_ensemble")])
109
+ ```
110
+
111
+ Any sequence of numbers works where a series is expected: a list, a NumPy
112
+ array, a pandas Series. Without `fs.Series` (or `fs.monthly`,
113
+ `fs.quarterly`), pass the seasonal period: `fs.Theta().fit(values, period=12)`.
114
+ Missing values (`None`, NaN) are only accepted by the cleaning functions.
115
+
116
+ ## What is in it
117
+
118
+ | Piece | What it does |
119
+ |---|---|
120
+ | `Series`, `monthly`, `quarterly` | values + seasonal period + season of the first observation |
121
+ | `Model` / `Fit` | fit once, forecast any horizon, inspect `params`, `details`, likelihood and residuals |
122
+ | Models | `Mean`, `Naive`, `Drift`, `SeasonalNaive`, `Theta`, `HoltWinters`, `LogLinear` (optionally deflated by a price index), `Arima` (seasonal, exact maximum likelihood, optionally with regressors), `AutoArima`, `Ets`, `AutoEts`, `Prophet` (changepoints, Fourier seasonality, dated events and steps), `Tbats` (several seasonal periods, not necessarily whole numbers), `Croston` (with SBA and TSB) |
123
+ | `Transformed`, `log` | any model on the log or another Box-Cox scale, λ fixed or by Guerrero's method |
124
+ | `Decomposed`, `stl`, `mstl` | trend, seasonal patterns and remainder by LOESS; any model on the seasonally adjusted series |
125
+ | `Ensemble` | average, median, weights by inverse error or stacked weights |
126
+ | `Regressors` | external variables, Fourier terms, seasonal dummies |
127
+ | `defaults`, `thorough` | ready sets of 11 and 18 candidates |
128
+ | `backtest` | rolling origin (expanding or fixed window) on all cores; MAPE, MAE, RMSE, MASE and bias by horizon; average of the best models; choice by out-of-sample error; empirical intervals by horizon and for totals |
129
+ | `interpolate`, `outliers`, `clean` | gaps filled and outliers found and replaced, with the season taken into account |
130
+ | Measures and tests | `mape`, `bias`, `mae`, `rmse`, `mase`, `acf`, `difference`, `kpss`, `ndiffs`, `nsdiffs`, `seasonal_strength`, `box_cox`, `inv_box_cox`, `guerrero` |
131
+
132
+ ## How it differs from the usual toolkits
133
+
134
+ Most forecasting libraries choose a model by an in-sample information
135
+ criterion and derive intervals from distributional assumptions. Here the
136
+ choice and the intervals both come from forecasts made without seeing the
137
+ future they are judged against. The interval for a total (say, the rest of a
138
+ fiscal year) is measured on totals, because adding up monthly limits
139
+ overstates its uncertainty.
140
+
141
+ ## Checked
142
+
143
+ The package runs the Rust crate, so its numbers are the crate's; the tests
144
+ check that nothing is lost on the way, against results recorded by the crate:
145
+ ARIMA, regression with ARIMA errors, ETS, Prophet, TBATS, STL and MSTL,
146
+ Croston, cleaning, ensembles, tests of stationarity and seasonality, and the
147
+ backtests of 11 and 18 candidates on three public series. The crate itself is
148
+ compared with the R packages `forecast` 9.0.2 and `prophet` 1.1.7, and
149
+ reproduced independently by the Go edition
150
+ [foresight-go](https://github.com/milkway/foresight-go).
151
+
152
+ ```bash
153
+ pip install maturin pytest
154
+ maturin develop --release
155
+ pytest # about 30 s; pytest -m "not slow" skips TBATS and the thorough backtest
156
+ ```
157
+
158
+ ## Data
159
+
160
+ `tests/data` has two public series: the monthly ICMS and FPE revenue of the
161
+ state of Piauí, Brazil (Siconfi/STN, with the IPCA price index from the
162
+ Central Bank of Brazil), and the airline passengers of Box & Jenkins.
163
+
164
+ ## Authors
165
+
166
+ André Leite, Marcos Wasiliew, Hugo Vasconcelos, Carlos Amorim and Diogo
167
+ Bezerra.
168
+
169
+ ## License
170
+
171
+ MIT.
172
+
@@ -0,0 +1,145 @@
1
+ # pyforesight
2
+
3
+ [![CI](https://github.com/StrategicProjects/pyforesight/actions/workflows/ci.yml/badge.svg)](https://github.com/StrategicProjects/pyforesight/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![Dependencies: none](https://img.shields.io/badge/dependencies-none-brightgreen.svg)](pyproject.toml)
6
+
7
+ Time series forecasting in Python that picks its model by what would have
8
+ worked.
9
+
10
+ `foresight` fits several models to a series, replays the past to see how each
11
+ would have done, chooses by out-of-sample error and reports intervals taken
12
+ from the errors actually observed, including intervals for the total of the
13
+ next k periods.
14
+
15
+ The models, the backtest and the utilities are the Rust crate
16
+ [foresight](https://github.com/milkway/foresight), compiled into the package:
17
+ the numbers are the crate's, the backtest runs on all cores, and nothing else
18
+ is needed at run time. NumPy and pandas are accepted and, for pandas, produced
19
+ on request, but neither is required.
20
+
21
+ **Website:** <https://strategicprojects.github.io/pyforesight/> ·
22
+ [Português](README.pt-BR.md)
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install git+https://github.com/StrategicProjects/pyforesight
28
+ ```
29
+
30
+ Python 3.9 or later. Installing from the repository compiles the Rust code,
31
+ so it needs a Rust toolchain (<https://rustup.rs>).
32
+
33
+ ## Use
34
+
35
+ ```python
36
+ import foresight as fs
37
+
38
+ # monthly data whose first observation is in March
39
+ y = fs.monthly(values, first_month=3)
40
+
41
+ # replay the last 36 months, 12 months ahead, with every built-in model
42
+ report = fs.backtest(y)
43
+
44
+ best = report.best
45
+ print(f"{best.name}: MAPE {best.score:.1f}%")
46
+ for p in best.forecast:
47
+ lo, hi = p.interval(0.80)
48
+ print(p.horizon, round(p.mean), round(lo), round(hi))
49
+
50
+ half_year = best.cumulative(6) # the total of the next six months, with its own interval
51
+ report.to_pandas() # one row per candidate (needs pandas)
52
+ best.to_pandas() # the forecast with its intervals
53
+ ```
54
+
55
+ One model on its own:
56
+
57
+ ```python
58
+ # seasonal ARIMA on the log scale
59
+ fit = fs.log(fs.Arima.airline()).fit(y)
60
+ next_year = fit.forecast(12)
61
+
62
+ # orders chosen from the data, inspected
63
+ auto = fs.AutoArima().fit(fs.monthly(log_values, first_month=3))
64
+ auto.details["order"], auto.details["seasonal_order"], auto.aicc
65
+ ```
66
+
67
+ A trend that bends, with dated events:
68
+
69
+ ```python
70
+ model = fs.Prophet(
71
+ events={"campaign": [10, 34, 58, 82, 106, 130]}, # future ones included
72
+ steps={"new_law": 80}, # a lasting change of level
73
+ )
74
+ fit = model.fit(y)
75
+ fit.details["changepoints"], fit.details["effects"]
76
+ ```
77
+
78
+ Several models combined, and the wider set of candidates:
79
+
80
+ ```python
81
+ ensemble = fs.Ensemble(fs.defaults(), weighting="stacked")
82
+ report = fs.backtest(y, fs.thorough() + [ensemble.named("my_ensemble")])
83
+ ```
84
+
85
+ Any sequence of numbers works where a series is expected: a list, a NumPy
86
+ array, a pandas Series. Without `fs.Series` (or `fs.monthly`,
87
+ `fs.quarterly`), pass the seasonal period: `fs.Theta().fit(values, period=12)`.
88
+ Missing values (`None`, NaN) are only accepted by the cleaning functions.
89
+
90
+ ## What is in it
91
+
92
+ | Piece | What it does |
93
+ |---|---|
94
+ | `Series`, `monthly`, `quarterly` | values + seasonal period + season of the first observation |
95
+ | `Model` / `Fit` | fit once, forecast any horizon, inspect `params`, `details`, likelihood and residuals |
96
+ | Models | `Mean`, `Naive`, `Drift`, `SeasonalNaive`, `Theta`, `HoltWinters`, `LogLinear` (optionally deflated by a price index), `Arima` (seasonal, exact maximum likelihood, optionally with regressors), `AutoArima`, `Ets`, `AutoEts`, `Prophet` (changepoints, Fourier seasonality, dated events and steps), `Tbats` (several seasonal periods, not necessarily whole numbers), `Croston` (with SBA and TSB) |
97
+ | `Transformed`, `log` | any model on the log or another Box-Cox scale, λ fixed or by Guerrero's method |
98
+ | `Decomposed`, `stl`, `mstl` | trend, seasonal patterns and remainder by LOESS; any model on the seasonally adjusted series |
99
+ | `Ensemble` | average, median, weights by inverse error or stacked weights |
100
+ | `Regressors` | external variables, Fourier terms, seasonal dummies |
101
+ | `defaults`, `thorough` | ready sets of 11 and 18 candidates |
102
+ | `backtest` | rolling origin (expanding or fixed window) on all cores; MAPE, MAE, RMSE, MASE and bias by horizon; average of the best models; choice by out-of-sample error; empirical intervals by horizon and for totals |
103
+ | `interpolate`, `outliers`, `clean` | gaps filled and outliers found and replaced, with the season taken into account |
104
+ | Measures and tests | `mape`, `bias`, `mae`, `rmse`, `mase`, `acf`, `difference`, `kpss`, `ndiffs`, `nsdiffs`, `seasonal_strength`, `box_cox`, `inv_box_cox`, `guerrero` |
105
+
106
+ ## How it differs from the usual toolkits
107
+
108
+ Most forecasting libraries choose a model by an in-sample information
109
+ criterion and derive intervals from distributional assumptions. Here the
110
+ choice and the intervals both come from forecasts made without seeing the
111
+ future they are judged against. The interval for a total (say, the rest of a
112
+ fiscal year) is measured on totals, because adding up monthly limits
113
+ overstates its uncertainty.
114
+
115
+ ## Checked
116
+
117
+ The package runs the Rust crate, so its numbers are the crate's; the tests
118
+ check that nothing is lost on the way, against results recorded by the crate:
119
+ ARIMA, regression with ARIMA errors, ETS, Prophet, TBATS, STL and MSTL,
120
+ Croston, cleaning, ensembles, tests of stationarity and seasonality, and the
121
+ backtests of 11 and 18 candidates on three public series. The crate itself is
122
+ compared with the R packages `forecast` 9.0.2 and `prophet` 1.1.7, and
123
+ reproduced independently by the Go edition
124
+ [foresight-go](https://github.com/milkway/foresight-go).
125
+
126
+ ```bash
127
+ pip install maturin pytest
128
+ maturin develop --release
129
+ pytest # about 30 s; pytest -m "not slow" skips TBATS and the thorough backtest
130
+ ```
131
+
132
+ ## Data
133
+
134
+ `tests/data` has two public series: the monthly ICMS and FPE revenue of the
135
+ state of Piauí, Brazil (Siconfi/STN, with the IPCA price index from the
136
+ Central Bank of Brazil), and the airline passengers of Box & Jenkins.
137
+
138
+ ## Authors
139
+
140
+ André Leite, Marcos Wasiliew, Hugo Vasconcelos, Carlos Amorim and Diogo
141
+ Bezerra.
142
+
143
+ ## License
144
+
145
+ MIT.