chronobox 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.
- chronobox-0.1.0/.gitignore +30 -0
- chronobox-0.1.0/.pre-commit-config.yaml +27 -0
- chronobox-0.1.0/CHANGELOG.md +38 -0
- chronobox-0.1.0/LICENSE +21 -0
- chronobox-0.1.0/PKG-INFO +74 -0
- chronobox-0.1.0/README.md +35 -0
- chronobox-0.1.0/chronobox/__init__.py +42 -0
- chronobox-0.1.0/chronobox/__version__.py +1 -0
- chronobox-0.1.0/chronobox/_logging.py +16 -0
- chronobox-0.1.0/chronobox/analysis/__init__.py +25 -0
- chronobox-0.1.0/chronobox/analysis/counterfactual.py +260 -0
- chronobox-0.1.0/chronobox/analysis/fevd.py +232 -0
- chronobox-0.1.0/chronobox/analysis/granger.py +184 -0
- chronobox-0.1.0/chronobox/analysis/hd.py +381 -0
- chronobox-0.1.0/chronobox/analysis/irf.py +390 -0
- chronobox-0.1.0/chronobox/analysis/spillover.py +456 -0
- chronobox-0.1.0/chronobox/cli/__init__.py +5 -0
- chronobox-0.1.0/chronobox/cli/main.py +667 -0
- chronobox-0.1.0/chronobox/core/__init__.py +7 -0
- chronobox-0.1.0/chronobox/core/config.py +19 -0
- chronobox-0.1.0/chronobox/core/dates.py +107 -0
- chronobox-0.1.0/chronobox/core/lag_polynomial.py +134 -0
- chronobox-0.1.0/chronobox/core/results.py +352 -0
- chronobox-0.1.0/chronobox/core/transforms.py +244 -0
- chronobox-0.1.0/chronobox/core/tsdata.py +298 -0
- chronobox-0.1.0/chronobox/datasets/__init__.py +270 -0
- chronobox-0.1.0/chronobox/datasets/data/brazil/ipca_monthly.csv +361 -0
- chronobox-0.1.0/chronobox/datasets/data/classic/airline.csv +145 -0
- chronobox-0.1.0/chronobox/datasets/data/classic/co2.csv +469 -0
- chronobox-0.1.0/chronobox/datasets/data/classic/lynx.csv +115 -0
- chronobox-0.1.0/chronobox/datasets/data/classic/nile.csv +101 -0
- chronobox-0.1.0/chronobox/datasets/data/classic/sunspot.csv +310 -0
- chronobox-0.1.0/chronobox/datasets/data/classic/uspop.csv +23 -0
- chronobox-0.1.0/chronobox/datasets/data/macro/canada.csv +85 -0
- chronobox-0.1.0/chronobox/datasets/data/macro/us_gdp.csv +313 -0
- chronobox-0.1.0/chronobox/datasets/data/macro/us_macro_quarterly.csv +245 -0
- chronobox-0.1.0/chronobox/datasets/load.py +11 -0
- chronobox-0.1.0/chronobox/datasets/scripts/__init__.py +1 -0
- chronobox-0.1.0/chronobox/datasets/scripts/download_bcb.py +233 -0
- chronobox-0.1.0/chronobox/datasets/simulated.py +218 -0
- chronobox-0.1.0/chronobox/decomposition/__init__.py +6 -0
- chronobox-0.1.0/chronobox/decomposition/classical.py +157 -0
- chronobox-0.1.0/chronobox/decomposition/stl.py +387 -0
- chronobox-0.1.0/chronobox/decomposition/x13_wrapper.py +241 -0
- chronobox-0.1.0/chronobox/experiment/__init__.py +15 -0
- chronobox-0.1.0/chronobox/experiment/experiment.py +987 -0
- chronobox-0.1.0/chronobox/filters/__init__.py +26 -0
- chronobox-0.1.0/chronobox/filters/bk.py +166 -0
- chronobox-0.1.0/chronobox/filters/bn.py +278 -0
- chronobox-0.1.0/chronobox/filters/cf.py +130 -0
- chronobox-0.1.0/chronobox/filters/hamilton.py +208 -0
- chronobox-0.1.0/chronobox/filters/hp.py +128 -0
- chronobox-0.1.0/chronobox/models/__init__.py +42 -0
- chronobox-0.1.0/chronobox/models/ardl.py +506 -0
- chronobox-0.1.0/chronobox/models/arfima.py +694 -0
- chronobox-0.1.0/chronobox/models/arima.py +854 -0
- chronobox-0.1.0/chronobox/models/bvar.py +885 -0
- chronobox-0.1.0/chronobox/models/ecm.py +313 -0
- chronobox-0.1.0/chronobox/models/ets.py +1024 -0
- chronobox-0.1.0/chronobox/models/favar.py +462 -0
- chronobox-0.1.0/chronobox/models/gvar.py +464 -0
- chronobox-0.1.0/chronobox/models/holtwinters.py +466 -0
- chronobox-0.1.0/chronobox/models/svar.py +563 -0
- chronobox-0.1.0/chronobox/models/theta.py +224 -0
- chronobox-0.1.0/chronobox/models/tvpvar.py +392 -0
- chronobox-0.1.0/chronobox/models/var.py +820 -0
- chronobox-0.1.0/chronobox/models/vecm.py +891 -0
- chronobox-0.1.0/chronobox/py.typed +0 -0
- chronobox-0.1.0/chronobox/reports/__init__.py +21 -0
- chronobox-0.1.0/chronobox/reports/css_manager.py +439 -0
- chronobox-0.1.0/chronobox/reports/exporters/__init__.py +13 -0
- chronobox-0.1.0/chronobox/reports/exporters/html_exporter.py +277 -0
- chronobox-0.1.0/chronobox/reports/exporters/latex_exporter.py +255 -0
- chronobox-0.1.0/chronobox/reports/exporters/markdown_exporter.py +130 -0
- chronobox-0.1.0/chronobox/reports/report_manager.py +485 -0
- chronobox-0.1.0/chronobox/reports/template_manager.py +307 -0
- chronobox-0.1.0/chronobox/reports/templates/arima_report.html +125 -0
- chronobox-0.1.0/chronobox/reports/templates/base.html +123 -0
- chronobox-0.1.0/chronobox/reports/templates/generic_report.html +4 -0
- chronobox-0.1.0/chronobox/reports/templates/spillover_report.html +18 -0
- chronobox-0.1.0/chronobox/reports/templates/svar_report.html +15 -0
- chronobox-0.1.0/chronobox/reports/templates/tests_report.html +36 -0
- chronobox-0.1.0/chronobox/reports/templates/var_report.html +41 -0
- chronobox-0.1.0/chronobox/reports/transformers/__init__.py +34 -0
- chronobox-0.1.0/chronobox/reports/transformers/ardl_transformer.py +102 -0
- chronobox-0.1.0/chronobox/reports/transformers/arima_transformer.py +336 -0
- chronobox-0.1.0/chronobox/reports/transformers/base_transformer.py +194 -0
- chronobox-0.1.0/chronobox/reports/transformers/bvar_transformer.py +73 -0
- chronobox-0.1.0/chronobox/reports/transformers/ets_transformer.py +85 -0
- chronobox-0.1.0/chronobox/reports/transformers/spillover_transformer.py +134 -0
- chronobox-0.1.0/chronobox/reports/transformers/svar_transformer.py +86 -0
- chronobox-0.1.0/chronobox/reports/transformers/tests_transformer.py +139 -0
- chronobox-0.1.0/chronobox/reports/transformers/var_transformer.py +261 -0
- chronobox-0.1.0/chronobox/selection/__init__.py +6 -0
- chronobox-0.1.0/chronobox/selection/auto_arima.py +344 -0
- chronobox-0.1.0/chronobox/selection/auto_ets.py +269 -0
- chronobox-0.1.0/chronobox/selection/lag_selection.py +291 -0
- chronobox-0.1.0/chronobox/tests_stat/__init__.py +90 -0
- chronobox-0.1.0/chronobox/tests_stat/base.py +116 -0
- chronobox-0.1.0/chronobox/tests_stat/cointegration/__init__.py +22 -0
- chronobox-0.1.0/chronobox/tests_stat/cointegration/bounds_test.py +236 -0
- chronobox-0.1.0/chronobox/tests_stat/cointegration/engle_granger.py +180 -0
- chronobox-0.1.0/chronobox/tests_stat/cointegration/gregory_hansen.py +217 -0
- chronobox-0.1.0/chronobox/tests_stat/cointegration/phillips_ouliaris.py +161 -0
- chronobox-0.1.0/chronobox/tests_stat/critical_values/__init__.py +31 -0
- chronobox-0.1.0/chronobox/tests_stat/critical_values/mackinnon.py +214 -0
- chronobox-0.1.0/chronobox/tests_stat/critical_values/osterwald_lenum.py +151 -0
- chronobox-0.1.0/chronobox/tests_stat/critical_values/pss_bounds.py +205 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/__init__.py +34 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/arch_lm.py +89 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/bds.py +148 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/breusch_godfrey.py +109 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/durbin_watson.py +78 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/jarque_bera.py +89 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/ljung_box.py +104 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/reset.py +107 -0
- chronobox-0.1.0/chronobox/tests_stat/specification/white.py +103 -0
- chronobox-0.1.0/chronobox/tests_stat/structural_breaks/__init__.py +24 -0
- chronobox-0.1.0/chronobox/tests_stat/structural_breaks/bai_perron.py +278 -0
- chronobox-0.1.0/chronobox/tests_stat/structural_breaks/chow.py +120 -0
- chronobox-0.1.0/chronobox/tests_stat/structural_breaks/cusum.py +219 -0
- chronobox-0.1.0/chronobox/tests_stat/structural_breaks/qlr.py +146 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/__init__.py +31 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/adf.py +275 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/ers.py +251 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/hegy.py +230 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/kpss.py +175 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/lee_strazicich.py +278 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/pp.py +205 -0
- chronobox-0.1.0/chronobox/tests_stat/unit_root/zivot_andrews.py +225 -0
- chronobox-0.1.0/chronobox/utils/__init__.py +21 -0
- chronobox-0.1.0/chronobox/utils/array_ops.py +78 -0
- chronobox-0.1.0/chronobox/utils/logging.py +82 -0
- chronobox-0.1.0/chronobox/utils/numba_core.py +257 -0
- chronobox-0.1.0/chronobox/utils/validation.py +101 -0
- chronobox-0.1.0/chronobox/visualization/__init__.py +62 -0
- chronobox-0.1.0/chronobox/visualization/coef_plot.py +237 -0
- chronobox-0.1.0/chronobox/visualization/decomposition_plot.py +212 -0
- chronobox-0.1.0/chronobox/visualization/diagnostics_plot.py +373 -0
- chronobox-0.1.0/chronobox/visualization/export.py +279 -0
- chronobox-0.1.0/chronobox/visualization/fevd_plot.py +257 -0
- chronobox-0.1.0/chronobox/visualization/forecast_plot.py +258 -0
- chronobox-0.1.0/chronobox/visualization/hd_plot.py +224 -0
- chronobox-0.1.0/chronobox/visualization/irf_plot.py +295 -0
- chronobox-0.1.0/chronobox/visualization/spillover_plot.py +427 -0
- chronobox-0.1.0/chronobox/visualization/test_plot.py +462 -0
- chronobox-0.1.0/chronobox/visualization/themes.py +340 -0
- chronobox-0.1.0/chronobox/visualization/ts_plot.py +246 -0
- chronobox-0.1.0/mkdocs.yml +141 -0
- chronobox-0.1.0/pyproject.toml +145 -0
- chronobox-0.1.0/pyrightconfig.json +22 -0
- chronobox-0.1.0/ruff.toml +44 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.venv/
|
|
9
|
+
|
|
10
|
+
# Testing
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
.hypothesis/
|
|
15
|
+
.mutmut-cache
|
|
16
|
+
mutants/
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
|
|
22
|
+
# Docs
|
|
23
|
+
site/
|
|
24
|
+
|
|
25
|
+
# Logs
|
|
26
|
+
logs/
|
|
27
|
+
|
|
28
|
+
# OS
|
|
29
|
+
.DS_Store
|
|
30
|
+
Thumbs.db
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.5.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-json
|
|
9
|
+
- id: check-toml
|
|
10
|
+
- id: check-added-large-files
|
|
11
|
+
args: ['--maxkb=500']
|
|
12
|
+
- id: check-merge-conflict
|
|
13
|
+
- id: detect-private-key
|
|
14
|
+
|
|
15
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
16
|
+
rev: v0.4.0
|
|
17
|
+
hooks:
|
|
18
|
+
- id: ruff
|
|
19
|
+
args: [--fix, --exit-non-zero-on-fix]
|
|
20
|
+
- id: ruff-format
|
|
21
|
+
|
|
22
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
23
|
+
rev: v1.9.0
|
|
24
|
+
hooks:
|
|
25
|
+
- id: mypy
|
|
26
|
+
additional_dependencies: [numpy, pandas-stubs]
|
|
27
|
+
args: [--ignore-missing-imports]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-03-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- ARIMA/SARIMA model with MLE via Kalman filter (kalmanbox)
|
|
12
|
+
- Auto-ARIMA with stepwise algorithm and information criteria
|
|
13
|
+
- VAR model with lag selection, Granger causality, IRF, FEVD
|
|
14
|
+
- SVAR model with Cholesky, short-run, and long-run identification
|
|
15
|
+
- VECM model with Johansen cointegration test
|
|
16
|
+
- ARDL model with bounds testing and ECM
|
|
17
|
+
- Unit root tests: ADF, Phillips-Perron, KPSS, ERS/DF-GLS
|
|
18
|
+
- Cointegration tests: Johansen trace and max eigenvalue
|
|
19
|
+
- Diagnostic tests: Ljung-Box, Breusch-Godfrey, ARCH-LM
|
|
20
|
+
- Filters: Hodrick-Prescott, Baxter-King, Christiano-Fitzgerald, Hamilton
|
|
21
|
+
- Time series decomposition (STL, classical)
|
|
22
|
+
- Visualization module with diagnostic plots
|
|
23
|
+
- Report generation (HTML, professional themes)
|
|
24
|
+
- ChronoExperiment pattern for systematic model comparison
|
|
25
|
+
- CLI with 5 commands: estimate, test, forecast, decompose, filter
|
|
26
|
+
- ~30 built-in datasets (classic, macro, finance, simulated)
|
|
27
|
+
- Download scripts for Brazilian macro data (BCB SGS, IBGE SIDRA)
|
|
28
|
+
- Numba optimization for critical loops (@optional_jit)
|
|
29
|
+
- MkDocs documentation with ~40 pages
|
|
30
|
+
- CI/CD with GitHub Actions
|
|
31
|
+
- 10 quality assurance phases
|
|
32
|
+
|
|
33
|
+
### Dependencies
|
|
34
|
+
- kalmanbox (Kalman filter and MLE)
|
|
35
|
+
- NumPy >= 1.24
|
|
36
|
+
- SciPy >= 1.10
|
|
37
|
+
- pandas >= 2.0
|
|
38
|
+
- matplotlib >= 3.7
|
chronobox-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NodesEcon
|
|
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.
|
chronobox-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: chronobox
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Time series analysis library with ARIMA, state-space models, and automatic model selection
|
|
5
|
+
Author: NodesEcon
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: kalmanbox
|
|
17
|
+
Requires-Dist: matplotlib>=3.7
|
|
18
|
+
Requires-Dist: numpy>=1.24
|
|
19
|
+
Requires-Dist: pandas>=2.0
|
|
20
|
+
Requires-Dist: scipy>=1.10
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: bandit>=1.7; extra == 'dev'
|
|
23
|
+
Requires-Dist: hypothesis>=6.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: interrogate>=1.5; extra == 'dev'
|
|
25
|
+
Requires-Dist: mkdocs-material>=9.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'dev'
|
|
27
|
+
Requires-Dist: mutmut>=2.4; extra == 'dev'
|
|
28
|
+
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: radon>=6.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
34
|
+
Requires-Dist: safety>=2.3; extra == 'dev'
|
|
35
|
+
Requires-Dist: structlog>=23.0; extra == 'dev'
|
|
36
|
+
Provides-Extra: numba
|
|
37
|
+
Requires-Dist: numba>=0.57; extra == 'numba'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# chronobox
|
|
41
|
+
|
|
42
|
+
Time series analysis library with ARIMA, state-space models, and automatic model selection.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install -e ".[dev]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from chronobox import ARIMA
|
|
54
|
+
from chronobox.datasets import load_dataset
|
|
55
|
+
|
|
56
|
+
airline = load_dataset('airline')
|
|
57
|
+
model = ARIMA(order=(0,1,1), seasonal_order=(0,1,1,12))
|
|
58
|
+
results = model.fit(airline['passengers'])
|
|
59
|
+
print(results.summary())
|
|
60
|
+
forecast = results.forecast(steps=12)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Auto-ARIMA
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from chronobox import auto_arima
|
|
67
|
+
|
|
68
|
+
best = auto_arima(airline['passengers'], seasonal=True, m=12)
|
|
69
|
+
print(best.summary())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# chronobox
|
|
2
|
+
|
|
3
|
+
Time series analysis library with ARIMA, state-space models, and automatic model selection.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e ".[dev]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from chronobox import ARIMA
|
|
15
|
+
from chronobox.datasets import load_dataset
|
|
16
|
+
|
|
17
|
+
airline = load_dataset('airline')
|
|
18
|
+
model = ARIMA(order=(0,1,1), seasonal_order=(0,1,1,12))
|
|
19
|
+
results = model.fit(airline['passengers'])
|
|
20
|
+
print(results.summary())
|
|
21
|
+
forecast = results.forecast(steps=12)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Auto-ARIMA
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from chronobox import auto_arima
|
|
28
|
+
|
|
29
|
+
best = auto_arima(airline['passengers'], seasonal=True, m=12)
|
|
30
|
+
print(best.summary())
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""chronobox - Time series analysis library with ARIMA and automatic model selection."""
|
|
2
|
+
|
|
3
|
+
from chronobox.__version__ import __version__
|
|
4
|
+
from chronobox.analysis.counterfactual import Counterfactual
|
|
5
|
+
from chronobox.analysis.hd import HistoricalDecomposition
|
|
6
|
+
from chronobox.decomposition import STL, ClassicalDecomposition
|
|
7
|
+
from chronobox.models.arfima import ARFIMA
|
|
8
|
+
from chronobox.models.arima import ARIMA
|
|
9
|
+
from chronobox.models.bvar import BayesianVAR
|
|
10
|
+
from chronobox.models.ets import ETS
|
|
11
|
+
from chronobox.models.favar import FAVAR
|
|
12
|
+
from chronobox.models.gvar import GVAR
|
|
13
|
+
from chronobox.models.holtwinters import HoltWinters
|
|
14
|
+
from chronobox.models.svar import SVAR
|
|
15
|
+
from chronobox.models.theta import ThetaMethod
|
|
16
|
+
from chronobox.models.tvpvar import TVPVAR
|
|
17
|
+
from chronobox.models.var import VAR
|
|
18
|
+
from chronobox.models.vecm import VECM
|
|
19
|
+
from chronobox.selection.auto_arima import auto_arima
|
|
20
|
+
from chronobox.selection.auto_ets import auto_ets
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ARFIMA",
|
|
24
|
+
"ARIMA",
|
|
25
|
+
"ETS",
|
|
26
|
+
"FAVAR",
|
|
27
|
+
"GVAR",
|
|
28
|
+
"STL",
|
|
29
|
+
"SVAR",
|
|
30
|
+
"TVPVAR",
|
|
31
|
+
"VAR",
|
|
32
|
+
"VECM",
|
|
33
|
+
"BayesianVAR",
|
|
34
|
+
"ClassicalDecomposition",
|
|
35
|
+
"Counterfactual",
|
|
36
|
+
"HistoricalDecomposition",
|
|
37
|
+
"HoltWinters",
|
|
38
|
+
"ThetaMethod",
|
|
39
|
+
"__version__",
|
|
40
|
+
"auto_arima",
|
|
41
|
+
"auto_ets",
|
|
42
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Logging configuration for chronobox."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
_LOG_FORMAT = "%(asctime)s [%(name)s] %(levelname)s: %(message)s"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_logger(name: str) -> logging.Logger:
|
|
9
|
+
"""Get a logger with the chronobox namespace."""
|
|
10
|
+
logger = logging.getLogger(f"chronobox.{name}")
|
|
11
|
+
if not logger.handlers:
|
|
12
|
+
handler = logging.StreamHandler()
|
|
13
|
+
handler.setFormatter(logging.Formatter(_LOG_FORMAT))
|
|
14
|
+
logger.addHandler(handler)
|
|
15
|
+
logger.setLevel(logging.WARNING)
|
|
16
|
+
return logger
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Time series analysis tools: IRF, FEVD, Granger causality, HD, Counterfactual, Spillover."""
|
|
2
|
+
|
|
3
|
+
from chronobox.analysis.counterfactual import Counterfactual, CounterfactualResult
|
|
4
|
+
from chronobox.analysis.fevd import FEVD
|
|
5
|
+
from chronobox.analysis.granger import granger_causality
|
|
6
|
+
from chronobox.analysis.hd import HistoricalDecomposition, HistoricalDecompositionResult
|
|
7
|
+
from chronobox.analysis.irf import IRF
|
|
8
|
+
from chronobox.analysis.spillover import (
|
|
9
|
+
RollingSpilloverResult,
|
|
10
|
+
SpilloverIndex,
|
|
11
|
+
SpilloverResult,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"FEVD",
|
|
16
|
+
"IRF",
|
|
17
|
+
"Counterfactual",
|
|
18
|
+
"CounterfactualResult",
|
|
19
|
+
"HistoricalDecomposition",
|
|
20
|
+
"HistoricalDecompositionResult",
|
|
21
|
+
"RollingSpilloverResult",
|
|
22
|
+
"SpilloverIndex",
|
|
23
|
+
"SpilloverResult",
|
|
24
|
+
"granger_causality",
|
|
25
|
+
]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Counterfactual analysis based on historical decomposition.
|
|
3
|
+
|
|
4
|
+
Answers: "What would have happened if shock k had not occurred?"
|
|
5
|
+
|
|
6
|
+
References
|
|
7
|
+
----------
|
|
8
|
+
- Kilian, L. & Lutkepohl, H. (2017). Structural Vector Autoregressive
|
|
9
|
+
Analysis. Cambridge University Press. Chapter 4.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from numpy.typing import NDArray
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class CounterfactualResult:
|
|
23
|
+
"""Results from counterfactual analysis.
|
|
24
|
+
|
|
25
|
+
Attributes
|
|
26
|
+
----------
|
|
27
|
+
counterfactual : ndarray of shape (T, K_vars)
|
|
28
|
+
Counterfactual time series.
|
|
29
|
+
observed : ndarray of shape (T, K_vars)
|
|
30
|
+
Observed time series.
|
|
31
|
+
removed_contribution : ndarray of shape (T, K_vars)
|
|
32
|
+
The contribution that was removed.
|
|
33
|
+
shock_index : int | list[int]
|
|
34
|
+
Index(es) of the shock(s) removed.
|
|
35
|
+
scale : float
|
|
36
|
+
Scale factor applied (0 = full removal, 0.5 = half removal).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
counterfactual: NDArray[np.floating[Any]]
|
|
40
|
+
observed: NDArray[np.floating[Any]]
|
|
41
|
+
removed_contribution: NDArray[np.floating[Any]]
|
|
42
|
+
shock_index: int | list[int]
|
|
43
|
+
scale: float
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Counterfactual:
|
|
47
|
+
"""Counterfactual analysis based on historical decomposition.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
hd : HistoricalDecomposition or HistoricalDecompositionResult
|
|
52
|
+
Historical decomposition results.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, hd: Any) -> None:
|
|
56
|
+
# Accept either HistoricalDecomposition or HistoricalDecompositionResult
|
|
57
|
+
if hasattr(hd, "result"):
|
|
58
|
+
self._hd_result = hd.result
|
|
59
|
+
elif hasattr(hd, "decomposition") and hasattr(hd, "base"):
|
|
60
|
+
self._hd_result = hd
|
|
61
|
+
else:
|
|
62
|
+
msg = "hd must be a HistoricalDecomposition or HistoricalDecompositionResult"
|
|
63
|
+
raise TypeError(msg)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def decomposition(self) -> NDArray[np.floating[Any]]:
|
|
67
|
+
"""Decomposition array (T, K_shocks, K_vars)."""
|
|
68
|
+
return self._hd_result.decomposition
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def base(self) -> NDArray[np.floating[Any]]:
|
|
72
|
+
"""Base forecast (T, K_vars)."""
|
|
73
|
+
return self._hd_result.base
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def observed(self) -> NDArray[np.floating[Any]]:
|
|
77
|
+
"""Observed values (T, K_vars)."""
|
|
78
|
+
return self._hd_result.observed
|
|
79
|
+
|
|
80
|
+
def without_shock(
|
|
81
|
+
self, shock_index: int | list[int]
|
|
82
|
+
) -> NDArray[np.floating[Any]]:
|
|
83
|
+
"""Compute counterfactual without specified shock(s).
|
|
84
|
+
|
|
85
|
+
Y_cf = Y - HD_k (for single shock)
|
|
86
|
+
Y_cf = Y - sum(HD_k for k in shock_indices) (for multiple shocks)
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
shock_index : int or list[int]
|
|
91
|
+
Index(es) of the structural shock(s) to remove.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
ndarray of shape (T, K_vars)
|
|
96
|
+
"""
|
|
97
|
+
if isinstance(shock_index, int):
|
|
98
|
+
shock_index = [shock_index]
|
|
99
|
+
|
|
100
|
+
removed = np.zeros_like(self.observed)
|
|
101
|
+
for k in shock_index:
|
|
102
|
+
removed += self.decomposition[:, k, :]
|
|
103
|
+
|
|
104
|
+
return self.observed - removed
|
|
105
|
+
|
|
106
|
+
def with_modified_shock(
|
|
107
|
+
self, shock_index: int, scale: float = 0.5
|
|
108
|
+
) -> NDArray[np.floating[Any]]:
|
|
109
|
+
"""Compute counterfactual with modified (scaled) shock.
|
|
110
|
+
|
|
111
|
+
Y_cf = Y - HD_k * (1 - scale)
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
shock_index : int
|
|
116
|
+
Index of the structural shock to modify.
|
|
117
|
+
scale : float
|
|
118
|
+
Scale factor. 0 = remove entirely, 1 = keep as-is, 0.5 = halve.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
ndarray of shape (T, K_vars)
|
|
123
|
+
"""
|
|
124
|
+
contribution = self.decomposition[:, shock_index, :]
|
|
125
|
+
return self.observed - contribution * (1.0 - scale)
|
|
126
|
+
|
|
127
|
+
def without_all_shocks(self) -> NDArray[np.floating[Any]]:
|
|
128
|
+
"""Remove all shocks, leaving only the base forecast.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
ndarray of shape (T, K_vars)
|
|
133
|
+
Should equal self.base.
|
|
134
|
+
"""
|
|
135
|
+
n_shocks = self.decomposition.shape[1]
|
|
136
|
+
return self.without_shock(list(range(n_shocks)))
|
|
137
|
+
|
|
138
|
+
def compute(
|
|
139
|
+
self,
|
|
140
|
+
shock_index: int | list[int],
|
|
141
|
+
scale: float = 0.0,
|
|
142
|
+
) -> CounterfactualResult:
|
|
143
|
+
"""Compute counterfactual with full result object.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
shock_index : int or list[int]
|
|
148
|
+
Shock(s) to modify.
|
|
149
|
+
scale : float
|
|
150
|
+
Scale factor (0 = remove, 1 = keep).
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
CounterfactualResult
|
|
155
|
+
"""
|
|
156
|
+
if isinstance(shock_index, int):
|
|
157
|
+
cf = self.with_modified_shock(shock_index, scale)
|
|
158
|
+
removed = self.decomposition[:, shock_index, :] * (1.0 - scale)
|
|
159
|
+
else:
|
|
160
|
+
removed = np.zeros_like(self.observed)
|
|
161
|
+
for k in shock_index:
|
|
162
|
+
removed += self.decomposition[:, k, :] * (1.0 - scale)
|
|
163
|
+
cf = self.observed - removed
|
|
164
|
+
|
|
165
|
+
return CounterfactualResult(
|
|
166
|
+
counterfactual=cf,
|
|
167
|
+
observed=self.observed,
|
|
168
|
+
removed_contribution=removed,
|
|
169
|
+
shock_index=shock_index,
|
|
170
|
+
scale=scale,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def plot(
|
|
174
|
+
self,
|
|
175
|
+
variable: int,
|
|
176
|
+
shock_index: int | list[int],
|
|
177
|
+
scale: float = 0.0,
|
|
178
|
+
figsize: tuple[int, int] = (12, 6),
|
|
179
|
+
title: str | None = None,
|
|
180
|
+
variable_name: str | None = None,
|
|
181
|
+
shock_name: str | None = None,
|
|
182
|
+
) -> Any:
|
|
183
|
+
"""Plot counterfactual vs observed.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
variable : int
|
|
188
|
+
Variable index to plot.
|
|
189
|
+
shock_index : int or list[int]
|
|
190
|
+
Shock(s) to remove.
|
|
191
|
+
scale : float
|
|
192
|
+
Scale factor (0 = remove, 1 = keep).
|
|
193
|
+
figsize : tuple
|
|
194
|
+
Figure size.
|
|
195
|
+
title : str or None
|
|
196
|
+
Plot title.
|
|
197
|
+
variable_name : str or None
|
|
198
|
+
Name of the variable for the plot label.
|
|
199
|
+
shock_name : str or None
|
|
200
|
+
Name of the shock for the plot label.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
matplotlib Figure
|
|
205
|
+
"""
|
|
206
|
+
import matplotlib.pyplot as plt
|
|
207
|
+
|
|
208
|
+
result = self.compute(shock_index, scale)
|
|
209
|
+
|
|
210
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
211
|
+
|
|
212
|
+
n_t = result.observed.shape[0]
|
|
213
|
+
t_axis = np.arange(n_t)
|
|
214
|
+
|
|
215
|
+
ax.plot(
|
|
216
|
+
t_axis,
|
|
217
|
+
result.observed[:, variable],
|
|
218
|
+
"b-",
|
|
219
|
+
linewidth=1.5,
|
|
220
|
+
label="Observed",
|
|
221
|
+
)
|
|
222
|
+
ax.plot(
|
|
223
|
+
t_axis,
|
|
224
|
+
result.counterfactual[:, variable],
|
|
225
|
+
"r--",
|
|
226
|
+
linewidth=1.5,
|
|
227
|
+
label="Counterfactual",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Shade the difference
|
|
231
|
+
ax.fill_between(
|
|
232
|
+
t_axis,
|
|
233
|
+
result.observed[:, variable],
|
|
234
|
+
result.counterfactual[:, variable],
|
|
235
|
+
alpha=0.2,
|
|
236
|
+
color="gray",
|
|
237
|
+
label="Shock contribution",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if variable_name is None:
|
|
241
|
+
variable_name = f"Variable {variable}"
|
|
242
|
+
if shock_name is None:
|
|
243
|
+
shock_name = f"Shock {shock_index}"
|
|
244
|
+
|
|
245
|
+
if title is None:
|
|
246
|
+
if scale == 0:
|
|
247
|
+
title = (
|
|
248
|
+
f"Counterfactual: {variable_name} without {shock_name}"
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
title = f"Counterfactual: {variable_name} with {shock_name} scaled by {scale:.1f}"
|
|
252
|
+
|
|
253
|
+
ax.set_title(title)
|
|
254
|
+
ax.set_xlabel("Time")
|
|
255
|
+
ax.set_ylabel("Value")
|
|
256
|
+
ax.legend()
|
|
257
|
+
ax.grid(True, alpha=0.3)
|
|
258
|
+
|
|
259
|
+
plt.tight_layout()
|
|
260
|
+
return fig
|