Skip to content

Commit ec905fa

Browse files
authored
Merge branch 'main' into feature/enable_sparse
2 parents 03db1ec + 163ce5e commit ec905fa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1045
-333
lines changed

.circleci/config.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ jobs:
66
- image: python:3.10-slim
77
steps:
88
- checkout
9+
- run:
10+
name: Install Git
11+
command: |
12+
apt-get update && apt-get install -y git
913
- run:
1014
name: Install dependencies
1115
command: |
1216
pip install uv
1317
uv venv --python 3.10
1418
- run:
15-
name: Run nbdev tests
19+
name: Run tests
1620
command: |
1721
source .venv/bin/activate
1822
uv pip install -e ".[dev,polars]"
@@ -33,6 +37,10 @@ jobs:
3337
- image: python:3.10-slim
3438
steps:
3539
- checkout
40+
- run:
41+
name: Install Git
42+
command: |
43+
apt-get update && apt-get install -y git
3644
- run:
3745
name: Install dependencies
3846
command: |
@@ -57,6 +65,10 @@ jobs:
5765
- image: python:3.10-slim
5866
steps:
5967
- checkout
68+
- run:
69+
name: Install Git
70+
command: |
71+
apt-get update && apt-get install -y git
6072
- run:
6173
name: Install dependencies
6274
command: |

.github/workflows/build-docs.yaml

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,31 @@ on:
66
branches: [main]
77
release:
88
types: [published]
9+
workflow_dispatch:
10+
inputs:
11+
environment:
12+
description: "The environment to deploy to"
13+
required: true
14+
type: choice
15+
default: "staging"
16+
options:
17+
- staging
18+
- production
919

1020
jobs:
1121
build-docs:
1222
runs-on: ubuntu-latest
1323
steps:
14-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
1525
with:
1626
submodules: "true"
1727

18-
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
28+
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
1929
with:
2030
python-version: '3.10'
2131

2232
- name: Install dependencies
23-
run: |
24-
pip install -e . lazydocs pyyaml
33+
run: pip install uv && uv pip install --system '.[dev,polars]'
2534

2635
# setup quarto for rendering example/tutorial nbs
2736
- uses: quarto-dev/quarto-actions/setup@v2
@@ -32,7 +41,7 @@ jobs:
3241
run: make all_docs
3342

3443
- name: Deploy (Push to main or Pull Request)
35-
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request'
44+
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging')
3645
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
3746
with:
3847
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -42,7 +51,7 @@ jobs:
4251
user_email: 41898282+github-actions[bot]@users.noreply.github.com
4352

4453
- name: Deploy (Release)
45-
if: github.event_name == 'release'
54+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production')
4655
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
4756
with:
4857
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -52,27 +61,27 @@ jobs:
5261
user_email: 41898282+github-actions[bot]@users.noreply.github.com
5362

5463
- name: Trigger mintlify workflow (Push to main or Pull Request)
55-
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request'
56-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
64+
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging')
65+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
5766
with:
5867
github-token: ${{ secrets.DOCS_WORKFLOW_TOKEN }}
5968
script: |
6069
await github.rest.actions.createWorkflowDispatch({
6170
owner: 'nixtla',
6271
repo: 'docs',
63-
workflow_id: 'mintlify-action-preview.yml',
72+
workflow_id: 'preview.yml',
6473
ref: 'main',
6574
});
6675
6776
- name: Trigger mintlify workflow (Release)
68-
if: github.event_name == 'release'
69-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
77+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production')
78+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
7079
with:
7180
github-token: ${{ secrets.DOCS_WORKFLOW_TOKEN }}
7281
script: |
7382
await github.rest.actions.createWorkflowDispatch({
7483
owner: 'nixtla',
7584
repo: 'docs',
76-
workflow_id: 'mintlify-action.yml',
85+
workflow_id: 'production.yml',
7786
ref: 'main',
7887
});

.github/workflows/lint.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Clone repo
14-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
14+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
1515

1616
- name: Set up python
17-
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
17+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 6.0.0
1818
with:
1919
python-version: 3.9
2020

2121
- name: Install dependencies
22-
run: pip install black nbdev pre-commit
22+
run: pip install uv && uv pip install --system pre-commit
2323

2424
- name: Run pre-commit
25-
run: pre-commit run --files hierarchicalforecast/*
25+
run: uv run pre-commit run --files hierarchicalforecast/*

.github/workflows/pytest.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ jobs:
2121
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
2222
steps:
2323
- name: Clone repo
24-
uses: actions/checkout@v4
24+
uses: actions/checkout@v5
2525

26-
- uses: actions/setup-python@v5
26+
- uses: actions/setup-python@v6
2727
with:
2828
python-version: ${{ matrix.python-version }}
2929

3030
- name: Install dependencies
31-
run: pip install -e ".[dev,polars]"
31+
run: pip install uv && uv pip install --system ".[dev,polars]"
3232

3333
- name: Run pytest
34-
run: pytest
34+
run: uv run pytest
3535

.github/workflows/python-publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ jobs:
2121
runs-on: ubuntu-latest
2222

2323
steps:
24-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
2525
- name: Set up Python
26-
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
26+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 6.0.0
2727
with:
2828
python-version: '3.x'
2929
- name: Install dependencies
@@ -33,7 +33,7 @@ jobs:
3333
- name: Build package
3434
run: python -m build
3535
- name: Publish package
36-
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
36+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
3737
with:
3838
user: __token__
3939
password: ${{ secrets.PYPI_API_TOKEN }}

Makefile

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ load_docs_scripts:
55
fi
66

77
api_docs:
8-
lazydocs .hierarchicalforecast --no-watermark
9-
python docs/to_mdx.py
8+
python docs/to_mdx.py docs
109

1110
examples_docs:
1211
mkdir -p nbs/_extensions
@@ -15,17 +14,26 @@ examples_docs:
1514
quarto render nbs --output-dir ../docs/mintlify/
1615

1716
format_docs:
18-
# replace _docs with docs
19-
sed -i -e 's/_docs/docs/g' ./docs-scripts/docs-final-formatting.bash
17+
# replace _docs with docs and remove the sed that breaks LaTeX (converts \\\\ to \\)
18+
@if [[ "$$OSTYPE" == "darwin"* ]]; then \
19+
sed -i '' -e 's/_docs/docs/g' -e '/\\\\\\\\.*\\\\/d' ./docs-scripts/docs-final-formatting.bash; \
20+
else \
21+
sed -i -e 's/_docs/docs/g' -e '/\\\\\\\\.*\\\\/d' ./docs-scripts/docs-final-formatting.bash; \
22+
fi
2023
bash ./docs-scripts/docs-final-formatting.bash
21-
24+
find docs/mintlify/examples -name "*.mdx" ! -name "*.html.mdx" -type f -exec sh -c 'mv "$$1" "$${1%.mdx}.html.mdx"' _ {} \;
2225

2326
preview_docs:
2427
cd docs/mintlify && mintlify dev
2528

2629
clean:
27-
rm -f docs/*.md
2830
find docs/mintlify -name "*.mdx" -exec rm -f {} +
2931

3032

31-
all_docs: load_docs_scripts api_docs examples_docs format_docs
33+
all_docs: load_docs_scripts api_docs examples_docs format_docs
34+
35+
licenses:
36+
pip-licenses --format=csv --with-authors --with-urls > third_party_licenses.csv
37+
python scripts/filter_licenses.py
38+
rm -f third_party_licenses.csv
39+
@echo "✓ THIRD_PARTY_LICENSES.md updated"

THIRD_PARTY_LICENSES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| Name | Version | License | Author | URL |
2+
|:----------|:----------|:--------------------------------------------------|:-----------------------------------------------|:------------------------------------------|
3+
| certifi | 2025.10.5 | Mozilla Public License 2.0 (MPL 2.0) | Kenneth Reitz | https://github.com/certifi/python-certifi |
4+
| pathspec | 0.12.1 | Mozilla Public License 2.0 (MPL 2.0) | "Caleb P. Burns" <cpburnz@gmail.com> | UNKNOWN |
5+
| qpsolvers | 4.8.1 | GNU Lesser General Public License v3 (LGPLv3) | Stéphane Caron <stephane.caron@normalesup.org> | https://github.com/qpsolvers/qpsolvers |
6+
| tqdm | 4.67.1 | MIT License; Mozilla Public License 2.0 (MPL 2.0) | UNKNOWN | https://tqdm.github.io |

docs/core.html.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
title: Core
3+
description: Core
4+
---
5+
6+
##
7+
8+
HierarchicalForecast contains pure Python implementations of
9+
hierarchical reconciliation methods as well as a
10+
`core.HierarchicalReconciliation` wrapper class that enables easy
11+
interaction with these methods through pandas DataFrames containing the
12+
hierarchical time series and the base predictions.
13+
14+
The `core.HierarchicalReconciliation` reconciliation class operates with
15+
the hierarchical time series pd.DataFrame `Y_df`, the base predictions
16+
pd.DataFrame `Y_hat_df`, the aggregation constraints matrix `S_df`. For
17+
more information on the creation of aggregation constraints matrix see
18+
the utils [aggregation
19+
method](https://nixtlaverse.nixtla.io/hierarchicalforecast/src/utils.html#aggregate)
20+
21+
::: hierarchicalforecast.core.HierarchicalReconciliation
22+
options:
23+
members:
24+
- reconcile
25+
- bootstrap_reconcile
26+
27+
### Example
28+
29+
```python
30+
import pandas as pd
31+
32+
from hierarchicalforecast.core import HierarchicalReconciliation
33+
from hierarchicalforecast.methods import BottomUp, MinTrace
34+
from hierarchicalforecast.utils import aggregate
35+
from hierarchicalforecast.evaluation import evaluate
36+
from statsforecast.core import StatsForecast
37+
from statsforecast.models import AutoETS
38+
from utilsforecast.losses import mase, rmse
39+
from functools import partial
40+
41+
# Load TourismSmall dataset
42+
df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')
43+
df = df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)
44+
df.insert(0, 'Country', 'Australia')
45+
qs = df['ds'].str.replace(r'(\d+) (Q\d)', r'\1-\2', regex=True)
46+
df['ds'] = pd.PeriodIndex(qs, freq='Q').to_timestamp()
47+
48+
# Create hierarchical seires based on geographic levels and purpose
49+
# And Convert quarterly ds string to pd.datetime format
50+
hierarchy_levels = [['Country'],
51+
['Country', 'State'],
52+
['Country', 'Purpose'],
53+
['Country', 'State', 'Region'],
54+
['Country', 'State', 'Purpose'],
55+
['Country', 'State', 'Region', 'Purpose']]
56+
57+
Y_df, S_df, tags = aggregate(df=df, spec=hierarchy_levels)
58+
59+
# Split train/test sets
60+
Y_test_df = Y_df.groupby('unique_id').tail(8)
61+
Y_train_df = Y_df.drop(Y_test_df.index)
62+
63+
# Compute base auto-ETS predictions
64+
# Careful identifying correct data freq, this data quarterly 'Q'
65+
fcst = StatsForecast(models=[AutoETS(season_length=4, model='ZZA')], freq='QS', n_jobs=-1)
66+
Y_hat_df = fcst.forecast(df=Y_train_df, h=8, fitted=True)
67+
Y_fitted_df = fcst.forecast_fitted_values()
68+
69+
reconcilers = [
70+
BottomUp(),
71+
MinTrace(method='ols'),
72+
MinTrace(method='mint_shrink'),
73+
]
74+
hrec = HierarchicalReconciliation(reconcilers=reconcilers)
75+
Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df,
76+
Y_df=Y_fitted_df,
77+
S_df=S_df, tags=tags)
78+
79+
# Evaluate
80+
eval_tags = {}
81+
eval_tags['Total'] = tags['Country']
82+
eval_tags['Purpose'] = tags['Country/Purpose']
83+
eval_tags['State'] = tags['Country/State']
84+
eval_tags['Regions'] = tags['Country/State/Region']
85+
eval_tags['Bottom'] = tags['Country/State/Region/Purpose']
86+
87+
Y_rec_df_with_y = Y_rec_df.merge(Y_test_df, on=['unique_id', 'ds'], how='left')
88+
mase_p = partial(mase, seasonality=4)
89+
90+
evaluation = evaluate(Y_rec_df_with_y,
91+
metrics=[mase_p, rmse],
92+
tags=eval_tags,
93+
train_df=Y_train_df)
94+
95+
numeric_cols = evaluation.select_dtypes(include="number").columns
96+
evaluation[numeric_cols] = evaluation[numeric_cols].map('{:.2f}'.format)
97+
```

0 commit comments

Comments
 (0)