yhatlabs 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.
- yhatlabs-0.1.0/.github/CODEOWNERS +1 -0
- yhatlabs-0.1.0/.github/ISSUE_TEMPLATE/bug.md +15 -0
- yhatlabs-0.1.0/.github/dependabot.yml +8 -0
- yhatlabs-0.1.0/.github/pull_request_template.md +9 -0
- yhatlabs-0.1.0/.github/workflows/ci.yml +29 -0
- yhatlabs-0.1.0/.github/workflows/release.yml +28 -0
- yhatlabs-0.1.0/.gitignore +8 -0
- yhatlabs-0.1.0/CHANGELOG.md +45 -0
- yhatlabs-0.1.0/CONTRIBUTING.md +14 -0
- yhatlabs-0.1.0/LICENSE +17 -0
- yhatlabs-0.1.0/PKG-INFO +441 -0
- yhatlabs-0.1.0/README.md +390 -0
- yhatlabs-0.1.0/SECURITY.md +9 -0
- yhatlabs-0.1.0/docs/images/forecast.png +0 -0
- yhatlabs-0.1.0/docs/images/tabular.png +0 -0
- yhatlabs-0.1.0/docs/readme_images.py +78 -0
- yhatlabs-0.1.0/examples/backtest.py +25 -0
- yhatlabs-0.1.0/examples/compare_gradient_boosting.py +22 -0
- yhatlabs-0.1.0/examples/decision_threshold.py +31 -0
- yhatlabs-0.1.0/examples/energy_end_to_end.py +47 -0
- yhatlabs-0.1.0/examples/fit_and_explain.py +22 -0
- yhatlabs-0.1.0/examples/forecast.py +34 -0
- yhatlabs-0.1.0/examples/hierarchy.py +27 -0
- yhatlabs-0.1.0/examples/sensors_gaps_and_anomalies.py +26 -0
- yhatlabs-0.1.0/examples/tabular.py +21 -0
- yhatlabs-0.1.0/examples/time_split.py +28 -0
- yhatlabs-0.1.0/pyproject.toml +68 -0
- yhatlabs-0.1.0/src/yhatlabs/__init__.py +63 -0
- yhatlabs-0.1.0/src/yhatlabs/_anomaly.py +129 -0
- yhatlabs-0.1.0/src/yhatlabs/_check.py +447 -0
- yhatlabs-0.1.0/src/yhatlabs/_cli.py +230 -0
- yhatlabs-0.1.0/src/yhatlabs/_client.py +1000 -0
- yhatlabs-0.1.0/src/yhatlabs/_errors.py +114 -0
- yhatlabs-0.1.0/src/yhatlabs/_evaluate.py +363 -0
- yhatlabs-0.1.0/src/yhatlabs/_fits.py +286 -0
- yhatlabs-0.1.0/src/yhatlabs/_forecast.py +452 -0
- yhatlabs-0.1.0/src/yhatlabs/_plot.py +60 -0
- yhatlabs-0.1.0/src/yhatlabs/_progress.py +64 -0
- yhatlabs-0.1.0/src/yhatlabs/_results.py +159 -0
- yhatlabs-0.1.0/src/yhatlabs/_sklearn.py +181 -0
- yhatlabs-0.1.0/src/yhatlabs/_tabular.py +342 -0
- yhatlabs-0.1.0/src/yhatlabs/_transport.py +289 -0
- yhatlabs-0.1.0/src/yhatlabs/_version.py +1 -0
- yhatlabs-0.1.0/src/yhatlabs/features.py +81 -0
- yhatlabs-0.1.0/src/yhatlabs/integrations.py +5 -0
- yhatlabs-0.1.0/src/yhatlabs/py.typed +0 -0
- yhatlabs-0.1.0/src/yhatlabs/skill.md +58 -0
- yhatlabs-0.1.0/tests/conftest.py +254 -0
- yhatlabs-0.1.0/tests/test_errors.py +89 -0
- yhatlabs-0.1.0/tests/test_evaluate.py +54 -0
- yhatlabs-0.1.0/tests/test_fits.py +122 -0
- yhatlabs-0.1.0/tests/test_forecast.py +172 -0
- yhatlabs-0.1.0/tests/test_live.py +22 -0
- yhatlabs-0.1.0/tests/test_tabular.py +167 -0
- yhatlabs-0.1.0/tests/test_timeseries_tools.py +138 -0
- yhatlabs-0.1.0/tests/test_tools.py +155 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @shubham-forum
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something does not work as documented
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
**What happened** (include the `request_id` from the error, if any; never paste your API key)
|
|
7
|
+
|
|
8
|
+
**What you expected**
|
|
9
|
+
|
|
10
|
+
**Minimal example**
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Versions**: `python -c "import yhatlabs, sys; print(yhatlabs.__version__, sys.version)"`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## What changes for users
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences, in the words a user would use. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] `CHANGELOG.md` has a line under "Unreleased" (or the PR has the `no-changelog` label)
|
|
8
|
+
- [ ] Tests cover the change (`pytest -m "not live"`), and the live test still passes if the API contract changed
|
|
9
|
+
- [ ] Only public model names (`chakra-ts`, `chakra-tab`) appear in code, docs and examples
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
jobs:
|
|
7
|
+
test:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
python: ["3.9", "3.11", "3.13"]
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v7
|
|
14
|
+
- uses: astral-sh/setup-uv@v7
|
|
15
|
+
- run: uv venv -p ${{ matrix.python }} && uv pip install -e ".[dev]"
|
|
16
|
+
- run: uv run --no-sync ruff check src tests examples && uv run --no-sync ruff format --check src tests examples
|
|
17
|
+
- run: uv run --no-sync pytest -q
|
|
18
|
+
changelog:
|
|
19
|
+
if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'no-changelog')
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v7
|
|
23
|
+
with: { fetch-depth: 0 }
|
|
24
|
+
- name: CHANGELOG.md updated when src/ changes
|
|
25
|
+
run: |
|
|
26
|
+
changed=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
|
|
27
|
+
if echo "$changed" | grep -q '^src/' && ! echo "$changed" | grep -q '^CHANGELOG.md$'; then
|
|
28
|
+
echo "src/ changed but CHANGELOG.md did not; add a line under Unreleased or label the PR no-changelog."; exit 1
|
|
29
|
+
fi
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
# Publishes to PyPI when a version tag is pushed (git tag v0.1.0 && git push origin v0.1.0). No token: PyPI trusted publishing
|
|
3
|
+
# (pending publisher: project yhatlabs, repo yhatlabs/yhatlabs-python, workflow release.yml, environment pypi).
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v7
|
|
12
|
+
- uses: astral-sh/setup-uv@v7
|
|
13
|
+
- name: Tag matches the package version
|
|
14
|
+
run: test "v$(grep -oP '__version__ = "\K[^"]+' src/yhatlabs/_version.py)" = "${GITHUB_REF_NAME}"
|
|
15
|
+
- run: uv venv && uv pip install -e ".[dev]" && uv run --no-sync pytest -q -m "not live"
|
|
16
|
+
- run: uv build
|
|
17
|
+
- uses: actions/upload-artifact@v7
|
|
18
|
+
with: { name: dist, path: dist/ }
|
|
19
|
+
publish:
|
|
20
|
+
needs: build
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
environment: pypi
|
|
23
|
+
permissions:
|
|
24
|
+
id-token: write
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/download-artifact@v8
|
|
27
|
+
with: { name: dist, path: dist/ }
|
|
28
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-10-15)
|
|
4
|
+
|
|
5
|
+
- **Data checks:** `yhatlabs.check()` runs locally for series and tables, gives findings with suggested fixes, and
|
|
6
|
+
`.repaired()` returns the cleaned series.
|
|
7
|
+
- **Fit records:** `FittedModel.save()` and `yhatlabs.load()` reuse a fit in another process or on another machine.
|
|
8
|
+
- **Client settings:** `Client.with_options()`, `Client.whoami()`, and limits read lazily from the API.
|
|
9
|
+
- **Visibility:**
|
|
10
|
+
- `YHAT_LOG` request logging;
|
|
11
|
+
- a terminal activity indicator (`YHAT_PROGRESS=0` turns it off);
|
|
12
|
+
- `.provenance` and `.timing` on results.
|
|
13
|
+
- **Forecasting:** `calendar=` for forecast, evaluate and detect_anomalies; `EvaluationResult.calibration` and
|
|
14
|
+
`plot_calibration()`.
|
|
15
|
+
- **Polars:** polars input, and `.to_polars()` on results.
|
|
16
|
+
- **CLI:** `yhatlabs check`, `whoami`, `skills install` (agent skill), and `--json` output.
|
|
17
|
+
|
|
18
|
+
- **Fit once, predict many:** `Client.fit` returns a `FittedModel` with `predict`, `predict_proba`, `importance`, `delete`,
|
|
19
|
+
and a context manager. The scikit-learn estimators use it, and fall back to one-shot calls on an API without `/v1/fits`.
|
|
20
|
+
- **Regression prediction intervals** (`quantiles=`), from split-conformal out-of-sample errors.
|
|
21
|
+
- **Permutation importance** per input column, and `Client.impute` for filling missing cells in tables.
|
|
22
|
+
- **Time series:**
|
|
23
|
+
- `static=` covariates and the `level=` alias;
|
|
24
|
+
- `scenarios` (what-if) and `detect_anomalies`;
|
|
25
|
+
- `fill_gaps`;
|
|
26
|
+
- `evaluate(compare=...)`, to score your own forecasts alongside;
|
|
27
|
+
- `on_error="warn"` for partial results;
|
|
28
|
+
- `AsyncClient.evaluate`;
|
|
29
|
+
- `ForecastResult.plot()`.
|
|
30
|
+
- `yhatlabs.features`: `add_calendar` (with holidays) and `make_future`.
|
|
31
|
+
- The `yhat` command line.
|
|
32
|
+
- **Plumbing:**
|
|
33
|
+
- orjson encoding when installed;
|
|
34
|
+
- large tables go to parquet without building JSON rows first;
|
|
35
|
+
- `server_limits()` reads `/v1/limits`;
|
|
36
|
+
- asking for intervals from an API that does not return them raises an error.
|
|
37
|
+
|
|
38
|
+
- `Client` / `AsyncClient` with `forecast`, `predict`, `models`, `usage`; `Client.evaluate` for rolling-origin backtests.
|
|
39
|
+
- Client-side time handling: frequency inference, gap filling, future timestamps, known/past covariate alignment, batching.
|
|
40
|
+
- Quantile selection with interpolation between native levels (reported in `derived`).
|
|
41
|
+
- scikit-learn estimators `ChakraTabClassifier` and `ChakraTabRegressor`.
|
|
42
|
+
- Typed errors with request ids; retries with backoff on transient failures.
|
|
43
|
+
- Tabular: float class labels survive the JSON round trip; local checks for one-class targets, duplicate column names,
|
|
44
|
+
eval_metric names and the 64 MB body cap; huge ints sent as text; tz-aware timestamps sent as UTC.
|
|
45
|
+
- Errors streamed in a 200 body are raised, and retried once at most (a long fit is not re-run several times).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
uv venv && uv pip install -e ".[dev]"
|
|
5
|
+
.venv/bin/ruff check src tests examples && .venv/bin/ruff format --check src tests examples
|
|
6
|
+
.venv/bin/pytest -m "not live" # offline, against a fake API
|
|
7
|
+
YHAT_API_KEY=... .venv/bin/pytest -m live # against the real API
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
- Every user-visible change gets a line in `CHANGELOG.md` under "Unreleased" (CI checks this on pull requests).
|
|
11
|
+
- Public API docstrings use Google style; ruff checks them.
|
|
12
|
+
- Only the public model names `chakra-ts` and `chakra-tab` appear anywhere in this repository.
|
|
13
|
+
- Releases: bump `src/yhatlabs/_version.py`, move "Unreleased" to the version in `CHANGELOG.md`, tag `vX.Y.Z` and push the
|
|
14
|
+
tag. The release workflow tests, builds and publishes to PyPI via trusted publishing.
|
yhatlabs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 YHat Labs
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
yhatlabs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: yhatlabs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official Python library for the YHat Labs time-series and tabular foundation models.
|
|
5
|
+
Project-URL: Homepage, https://yhatlabs.com/models
|
|
6
|
+
Project-URL: Documentation, https://yhatlabs.com/models
|
|
7
|
+
Project-URL: Repository, https://github.com/yhatlabs/yhatlabs-python
|
|
8
|
+
Project-URL: Changelog, https://github.com/yhatlabs/yhatlabs-python/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: YHat Labs <founders@yhatlabs.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: API,forecasting,foundation models,prediction,tabular,time series
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Requires-Dist: httpx>=0.24
|
|
22
|
+
Requires-Dist: numpy>=1.22
|
|
23
|
+
Requires-Dist: pandas>=1.5
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: holidays>=0.40; extra == 'all'
|
|
26
|
+
Requires-Dist: matplotlib>=3.5; extra == 'all'
|
|
27
|
+
Requires-Dist: orjson>=3.9; extra == 'all'
|
|
28
|
+
Requires-Dist: polars>=0.20; extra == 'all'
|
|
29
|
+
Requires-Dist: pyarrow>=12; extra == 'all'
|
|
30
|
+
Requires-Dist: scikit-learn>=1.2; extra == 'all'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: holidays>=0.40; extra == 'dev'
|
|
33
|
+
Requires-Dist: matplotlib>=3.5; extra == 'dev'
|
|
34
|
+
Requires-Dist: orjson>=3.9; extra == 'dev'
|
|
35
|
+
Requires-Dist: polars>=0.20; extra == 'dev'
|
|
36
|
+
Requires-Dist: pyarrow>=12; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
40
|
+
Requires-Dist: scikit-learn>=1.2; extra == 'dev'
|
|
41
|
+
Provides-Extra: fast
|
|
42
|
+
Requires-Dist: orjson>=3.9; extra == 'fast'
|
|
43
|
+
Requires-Dist: pyarrow>=12; extra == 'fast'
|
|
44
|
+
Provides-Extra: parquet
|
|
45
|
+
Requires-Dist: pyarrow>=12; extra == 'parquet'
|
|
46
|
+
Provides-Extra: plot
|
|
47
|
+
Requires-Dist: matplotlib>=3.5; extra == 'plot'
|
|
48
|
+
Provides-Extra: sklearn
|
|
49
|
+
Requires-Dist: scikit-learn>=1.2; extra == 'sklearn'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# YHat Labs Python Library
|
|
53
|
+
|
|
54
|
+
The official Python library for the YHat Labs time-series and tabular foundation models. Frontier AI models for
|
|
55
|
+
forecasting, anomaly detection, classification, regression and feature importance, built for production
|
|
56
|
+
workloads: no feature engineering or model training, with uncertainty and explanations built in.
|
|
57
|
+
|
|
58
|
+
- **Time-series forecasting** with **ChakraTS**: a drop-in alternative to ARIMA, Prophet or a trained deep-learning model.
|
|
59
|
+
It forecasts one series or thousands, with a range of likely outcomes around every point.
|
|
60
|
+
- **Tabular prediction** with **ChakraTab**: classification and regression on tables, typically more accurate than tuned
|
|
61
|
+
gradient boosting such as XGBoost or LightGBM on public benchmarks.
|
|
62
|
+
|
|
63
|
+
Both are pretrained foundation models: they work on your data as it is, with nothing to train, tune or maintain.
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pip install yhatlabs
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Optional extras: `[sklearn]` for the scikit-learn estimators, `[parquet]` for faster large tables, `[plot]` for charts,
|
|
72
|
+
`[fast]` for faster encoding, or `[all]`. Requires Python 3.9 or later.
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
The full documentation of the models and the API is in the [Docs](https://yhatlabs.com/models/docs).
|
|
77
|
+
|
|
78
|
+
Every call needs an API key. [Get an API key here](https://yhatlabs.com/models/access), then set it in your environment:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
export YHAT_API_KEY="<your-key>"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The client reads it from there, so it never ends up in your code. You can also pass `Client(api_key=...)`, or keep it in a
|
|
85
|
+
`.env` file loaded with [python-dotenv](https://pypi.org/project/python-dotenv/).
|
|
86
|
+
|
|
87
|
+
To use your own deployment instead of the hosted API, set its address:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
export YHAT_BASE_URL="https://your-deployment.example.com"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Pick what you want to do:
|
|
94
|
+
|
|
95
|
+
- **[Time-series forecasting](#time-series-forecasting)**: forecasts with uncertainty, backtests, what-if scenarios, anomalies
|
|
96
|
+
and gaps.
|
|
97
|
+
- **[Tabular prediction](#tabular-prediction)**: classification and regression, prediction intervals, column importance,
|
|
98
|
+
scikit-learn estimators.
|
|
99
|
+
- **[Check your data first](#check-your-data-first)**: local checks before anything is sent.
|
|
100
|
+
- **[Advanced usage](#advanced-usage)**: the command line, coding agents, async, configuration and errors.
|
|
101
|
+
|
|
102
|
+
## Time-series forecasting
|
|
103
|
+
|
|
104
|
+
ChakraTS forecasts one series or many at once. Every forecast comes with an uncertainty range, so you see both the most
|
|
105
|
+
likely path and how far reality could stray from it.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import pandas as pd
|
|
109
|
+
from yhatlabs import Client
|
|
110
|
+
|
|
111
|
+
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/"
|
|
112
|
+
df = pd.read_csv(url + "airline-passengers.csv")
|
|
113
|
+
|
|
114
|
+
yh = Client()
|
|
115
|
+
fc = yh.forecast(
|
|
116
|
+
df, h=24, time_col="Month", target_col="Passengers", level=[80]
|
|
117
|
+
)
|
|
118
|
+
print(fc.df.head(3))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
unique_id Month q10 median q90
|
|
123
|
+
0 series_0 1961-01-01 428.7 446.2 466.0
|
|
124
|
+
1 series_0 1961-02-01 406.1 428.9 449.8
|
|
125
|
+
2 series_0 1961-03-01 442.7 482.7 507.2
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+

|
|
129
|
+
|
|
130
|
+
For many series, add an id column (`unique_id`, `id` or `item_id`). The time column (`ds`, `timestamp`, `date`) and the target
|
|
131
|
+
(`y`, `target`, `value`) are found by name as well. The client parses timestamps, infers the frequency, fills missing
|
|
132
|
+
timestamps and splits large inputs into requests for you.
|
|
133
|
+
|
|
134
|
+
### What you can do
|
|
135
|
+
|
|
136
|
+
| Goal | Call |
|
|
137
|
+
|---|---|
|
|
138
|
+
| Forecast with a range of outcomes | `yh.forecast(df, h)` |
|
|
139
|
+
| Use inputs you know ahead (prices, weather, promotions) | `yh.forecast(df, h, known=[...], future=...)` |
|
|
140
|
+
| Add calendar effects (hour, weekday, holidays) | `yh.forecast(df, h, calendar=True)` |
|
|
141
|
+
| Measure accuracy on your own history | `yh.evaluate(df, h, windows=3)` |
|
|
142
|
+
| Compare with the forecast you use today | `yh.evaluate(df, h, compare={"current": mine})` |
|
|
143
|
+
| Ask "what if" (a heatwave, a price change) | `yh.scenarios(df, h, {"base": f0, "heatwave": f1}, known=[...])` |
|
|
144
|
+
| Flag unusual points | `yh.detect_anomalies(df)` |
|
|
145
|
+
| Fill gaps inside series | `yh.fill_gaps(df)` |
|
|
146
|
+
|
|
147
|
+
### Inputs and uncertainty
|
|
148
|
+
|
|
149
|
+
Forecasts get better when you tell the model what you already know about the future, and more useful when the uncertainty
|
|
150
|
+
matches your decision.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from yhatlabs.features import make_future
|
|
154
|
+
|
|
155
|
+
# the next 24 timestamps of every series, to fill in with what you know ahead
|
|
156
|
+
future = make_future(df, h=24)
|
|
157
|
+
future["temperature"] = weather_forecast
|
|
158
|
+
|
|
159
|
+
fc = yh.forecast(
|
|
160
|
+
df,
|
|
161
|
+
h=24,
|
|
162
|
+
known=["temperature"],
|
|
163
|
+
future=future,
|
|
164
|
+
calendar=True,
|
|
165
|
+
quantiles=[0.05, 0.5, 0.95],
|
|
166
|
+
)
|
|
167
|
+
fc.plot(history=df)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- **Uncertainty:** the default is the median plus the 0.1 and 0.9 quantiles.
|
|
171
|
+
- `quantiles=` takes any levels between 0.1 and 0.9.
|
|
172
|
+
- `level=[80]` gives central intervals.
|
|
173
|
+
- `quantiles="all"` returns every level the model produces.
|
|
174
|
+
- Levels between those are interpolated and listed in `fc.derived`, so nothing derived is presented as a model output.
|
|
175
|
+
- **Covariates:**
|
|
176
|
+
- `known=[...]` are columns you know ahead, with their future values in `future=`;
|
|
177
|
+
- `past=[...]` are observed only in the history;
|
|
178
|
+
- `static=[...]` are constants per series, such as region or store size;
|
|
179
|
+
- `calendar=True` adds hour, weekday, month and weekend, with no future frame needed; `calendar={"holidays": "US"}` adds
|
|
180
|
+
holidays.
|
|
181
|
+
- **Several targets per series:** `target_col=["load", "price"]` forecasts them jointly.
|
|
182
|
+
|
|
183
|
+
### Backtesting
|
|
184
|
+
|
|
185
|
+
Before relying on a forecast, measure it on your own history: what would it have predicted at past points, and how close
|
|
186
|
+
was it?
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
ev = yh.evaluate(
|
|
190
|
+
df, h=24, windows=4, time_col="Month", target_col="Passengers"
|
|
191
|
+
)
|
|
192
|
+
ev.summary # accuracy per model, and skill vs seasonal naive
|
|
193
|
+
ev.calibration # does the 80% interval really hold 80%?
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- **How it works:** `evaluate` cuts each series at several points near its end, forecasts from each cut using only the data
|
|
197
|
+
before it, and scores the forecasts against what happened.
|
|
198
|
+
- **Skill:** `skill_<metric>` compares with the first baseline. Above 0 means the model beats it.
|
|
199
|
+
- **Your own forecast:** `compare={"current": df}` scores it alongside. It takes id and time plus quantile columns
|
|
200
|
+
(`median`, `q10`, ...) or a single `forecast` column.
|
|
201
|
+
- **Calibration:** `ev.calibration` reports the observed share below each quantile and inside each interval, next to the
|
|
202
|
+
nominal level. `ev.plot_calibration()` draws it.
|
|
203
|
+
|
|
204
|
+
### Anomalies and gaps
|
|
205
|
+
|
|
206
|
+
The same model that forecasts can tell you which recent points it did not expect, and what a missing value most likely was.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# a score and an anomaly flag for each recent point
|
|
210
|
+
flags = yh.detect_anomalies(df, h=24, windows=5)
|
|
211
|
+
|
|
212
|
+
# missing values filled from the history before each gap
|
|
213
|
+
filled = yh.fill_gaps(df)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
- **Scores:** each point is compared with a forecast made from the data before it. The score is the distance from the median
|
|
217
|
+
in units of that forecast's own spread, so a point the model was unsure about needs a larger miss to count. `threshold`
|
|
218
|
+
(default 3) sets what gets flagged.
|
|
219
|
+
- **Gaps:** each gap gets the median forecast from the data before it, and `<target>_filled` marks what was filled.
|
|
220
|
+
|
|
221
|
+
## Tabular prediction
|
|
222
|
+
|
|
223
|
+
ChakraTab learns the relationship between the columns of a table from your labeled rows, then predicts the answer for new
|
|
224
|
+
rows. It handles numbers, categories, dates and missing values as they are.
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from sklearn.datasets import load_breast_cancer
|
|
228
|
+
from sklearn.model_selection import train_test_split
|
|
229
|
+
from yhatlabs import Client
|
|
230
|
+
|
|
231
|
+
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
|
|
232
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
|
|
233
|
+
|
|
234
|
+
yh = Client()
|
|
235
|
+
model = yh.fit(X_train.assign(diagnosis=y_train), target="diagnosis")
|
|
236
|
+
res = model.predict(X_test)
|
|
237
|
+
print(res.df.head(3))
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
prediction p_0 p_1
|
|
242
|
+
512 0 1.000 0.000
|
|
243
|
+
457 1 0.018 0.982
|
|
244
|
+
439 1 0.000 1.000
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The fit happens once, on our side; every `predict` after it sends only the new rows. For a number instead of a class,
|
|
248
|
+
ask for an interval as well:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
model = yh.fit(train, target="progression", task="regress")
|
|
252
|
+
# prediction, q5, median and q95 for every test row
|
|
253
|
+
res = model.predict(test, quantiles=[0.05, 0.5, 0.95])
|
|
254
|
+
|
|
255
|
+
# which columns drive the predictions
|
|
256
|
+
model.importance(holdout)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+

|
|
260
|
+
|
|
261
|
+
### What you can do
|
|
262
|
+
|
|
263
|
+
| Goal | Call |
|
|
264
|
+
|---|---|
|
|
265
|
+
| Classify (with probabilities) or regress | `yh.fit(train, target).predict(test)` |
|
|
266
|
+
| Get prediction intervals for numbers | `model.predict(test, quantiles=[0.05, 0.95])` |
|
|
267
|
+
| See which columns matter | `model.importance(holdout)` |
|
|
268
|
+
| Reuse a fit later, anywhere | `model.save("model.json")`, then `yhatlabs.load("model.json")` |
|
|
269
|
+
| Predict once, keeping nothing | `yh.predict(train, target, test)` |
|
|
270
|
+
| Fill missing cells | `yh.impute(df)` |
|
|
271
|
+
| Use it in scikit-learn | `ChakraTabClassifier()`, `ChakraTabRegressor()` |
|
|
272
|
+
|
|
273
|
+
### Fitting, intervals and importance
|
|
274
|
+
|
|
275
|
+
A fit stays available so you can score new rows as they arrive, explain the model, and share it with a teammate, without
|
|
276
|
+
sending the training data again.
|
|
277
|
+
|
|
278
|
+
- **Lifetime:** a fit is kept for `ttl_hours` (default 24, up to 168), or until `model.delete()`. A `with yh.fit(...) as
|
|
279
|
+
model:` block deletes it on exit. `model.save(path)` writes a small record, with no training data, that `yhatlabs.load()`
|
|
280
|
+
reattaches anywhere.
|
|
281
|
+
- **Classes and numbers:** classification returns `predictions` and `probabilities`. Regression returns `predictions` and,
|
|
282
|
+
with `quantiles=`, intervals built from out-of-sample errors, so a 90% interval holds about 90% of new values. Pass
|
|
283
|
+
`task="classify"` or `task="regress"` for integer targets, which could be either.
|
|
284
|
+
- **Presets:** `fast` is the default; `medium` is more accurate and about 3x slower. `eval_metric=`, `time_limit=` and
|
|
285
|
+
`seed=` are also accepted.
|
|
286
|
+
- **Importance:** how much the score drops when a column is shuffled. Labeled rows the model has not seen give the most honest
|
|
287
|
+
numbers.
|
|
288
|
+
|
|
289
|
+
### scikit-learn
|
|
290
|
+
|
|
291
|
+
ChakraTab drops into existing scikit-learn code as an estimator, so pipelines, grid searches and cross-validation work
|
|
292
|
+
unchanged.
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from yhatlabs.integrations import ChakraTabClassifier
|
|
296
|
+
from sklearn.model_selection import cross_val_score
|
|
297
|
+
|
|
298
|
+
cross_val_score(ChakraTabClassifier(preset="medium"), X, y, cv=5)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The estimators fit once and reuse the fit for every prediction, and they pickle. `ChakraTabRegressor.predict_quantiles(X)`
|
|
302
|
+
gives intervals.
|
|
303
|
+
|
|
304
|
+
## Check your data first
|
|
305
|
+
|
|
306
|
+
Most surprises in a forecast or a prediction start in the data. `check` looks at it locally, before anything is sent, and
|
|
307
|
+
tells you what it found and how to fix it.
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
import yhatlabs
|
|
311
|
+
|
|
312
|
+
# series: duplicates, gaps, frequency, short history, extreme values
|
|
313
|
+
yhatlabs.check(df, h=24)
|
|
314
|
+
|
|
315
|
+
# tables: id-like columns, columns that leak the target, imbalance
|
|
316
|
+
yhatlabs.check(train, target="churn")
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
- **Local and free:** it needs no key, and nothing leaves your machine.
|
|
320
|
+
- **What you get:** each finding has a severity, a location and a suggested fix. `.ok` is False when something would fail.
|
|
321
|
+
- **Seeing the clean-up:** for series, `.repaired()` returns the data after the same clean-up `forecast` applies.
|
|
322
|
+
|
|
323
|
+
## Advanced usage
|
|
324
|
+
|
|
325
|
+
<details>
|
|
326
|
+
<summary><b>Command line and coding agents</b></summary>
|
|
327
|
+
|
|
328
|
+
Everything above also runs from the terminal, on CSV or parquet files, which suits scripts, schedulers and AI coding
|
|
329
|
+
agents.
|
|
330
|
+
|
|
331
|
+
```sh
|
|
332
|
+
yhatlabs check sales.csv --h 28
|
|
333
|
+
yhatlabs forecast sales.csv --h 28 --level 80 -o forecast.csv
|
|
334
|
+
yhatlabs evaluate sales.csv --h 28 --windows 3
|
|
335
|
+
yhatlabs anomalies meters.parquet --only
|
|
336
|
+
yhatlabs predict train.csv --target churn --test new.csv -o scored.csv
|
|
337
|
+
yhatlabs whoami
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
- **Machine-readable output:** `--json` on any command. `check` exits with code 2 on errors, so it can gate a pipeline.
|
|
341
|
+
- **Coding agents:** `yhatlabs skills install` writes a skill into the current project for Claude Code, Cursor, Codex and
|
|
342
|
+
similar agents, so they know the calls and the rules.
|
|
343
|
+
- **Short alias:** `yhat` works too.
|
|
344
|
+
|
|
345
|
+
</details>
|
|
346
|
+
|
|
347
|
+
<details>
|
|
348
|
+
<summary><b>Async</b></summary>
|
|
349
|
+
|
|
350
|
+
For services and notebooks that already run an event loop, `AsyncClient` offers the same calls as coroutines.
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from yhatlabs import AsyncClient
|
|
354
|
+
|
|
355
|
+
async with AsyncClient() as yh:
|
|
356
|
+
fc = await yh.forecast(df, h=24)
|
|
357
|
+
model = await yh.fit(train, target="churn")
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
</details>
|
|
361
|
+
|
|
362
|
+
<details>
|
|
363
|
+
<summary><b>Configuration and visibility</b></summary>
|
|
364
|
+
|
|
365
|
+
The defaults suit most work. These settings are there for long jobs, self-hosted endpoints and debugging.
|
|
366
|
+
|
|
367
|
+
| Setting | How |
|
|
368
|
+
|---|---|
|
|
369
|
+
| API key | `Client(api_key=...)` or `YHAT_API_KEY` |
|
|
370
|
+
| Server | `base_url=` or `YHAT_BASE_URL` (default `https://api.yhatlabs.com`) |
|
|
371
|
+
| Timeout and retries | `timeout=` (default 300 s), `max_retries=` (default 3); for a single call, `yh.with_options(timeout=1800)` |
|
|
372
|
+
| Parallel requests | `max_concurrency=` (default 4) |
|
|
373
|
+
| Request logging | `YHAT_LOG=info`: method, path, status, time and request id (never data or keys) |
|
|
374
|
+
| Activity line | shown in terminals for calls over 2 s; `YHAT_PROGRESS=0` turns it off |
|
|
375
|
+
|
|
376
|
+
- **Request caps:** read from the API on first use, and applied when splitting inputs.
|
|
377
|
+
- **Your key:** `yh.whoami()` checks it and shows the models it can use and its quota.
|
|
378
|
+
- **Provenance:** every result carries `.provenance`: model, settings, request ids, SDK version and a fingerprint of the
|
|
379
|
+
input, enough to rerun or cite it. `.timing` gives the client-side total and, for tables, the server's fit/predict split.
|
|
380
|
+
- **Polars:** input works wherever pandas does, and results have `.to_polars()`.
|
|
381
|
+
|
|
382
|
+
</details>
|
|
383
|
+
|
|
384
|
+
<details>
|
|
385
|
+
<summary><b>Errors and retries</b></summary>
|
|
386
|
+
|
|
387
|
+
Errors tell you whose side the problem is on and what to do next. Temporary problems are retried for you.
|
|
388
|
+
|
|
389
|
+
- **Before anything is sent:** `DataError` says how to fix the input, and `UnsupportedError` means the model can't do what
|
|
390
|
+
was asked.
|
|
391
|
+
- **From the API:** `APIError` and its subclasses (`AuthenticationError`, `RateLimitError`, `ServiceUnavailableError`, ...)
|
|
392
|
+
carry `.status`, `.type` and `.request_id`. Quote the request id to us and we can trace the call.
|
|
393
|
+
- **Retries:** timeouts, connection errors, 408, 409, 429 and 5xx are retried with backoff, honouring `Retry-After`. A
|
|
394
|
+
used-up quota is not retried.
|
|
395
|
+
- **Partial results:** for large forecasts, `on_error="warn"` returns the series from batches that succeeded and lists the
|
|
396
|
+
rest in `fc.errors`.
|
|
397
|
+
|
|
398
|
+
</details>
|
|
399
|
+
|
|
400
|
+
<details>
|
|
401
|
+
<summary><b>Your data</b></summary>
|
|
402
|
+
|
|
403
|
+
Your inputs are sent to the YHat Labs API to be processed.
|
|
404
|
+
- **One-shot calls:** nothing is kept beyond our request archive.
|
|
405
|
+
- **Fits:** a fitted model keeps its training table only until it expires or you delete it.
|
|
406
|
+
- **More:** see [SECURITY.md](SECURITY.md) for how keys and data are handled, and how to report a security issue.
|
|
407
|
+
|
|
408
|
+
</details>
|
|
409
|
+
|
|
410
|
+
<details>
|
|
411
|
+
<summary><b>Examples</b></summary>
|
|
412
|
+
|
|
413
|
+
Runnable scripts in [`examples/`](examples):
|
|
414
|
+
- **Time series:** energy load with weather and a heatwave scenario; sensors with gaps and a fault; stores that add up to a
|
|
415
|
+
region.
|
|
416
|
+
- **Tables:** a fit with intervals and importance; a decision threshold set from business costs; validation for rows that
|
|
417
|
+
arrive over time; a comparison with gradient boosting.
|
|
418
|
+
|
|
419
|
+
</details>
|
|
420
|
+
|
|
421
|
+
<details>
|
|
422
|
+
<summary><b>Development</b></summary>
|
|
423
|
+
|
|
424
|
+
```sh
|
|
425
|
+
git clone https://github.com/yhatlabs/yhatlabs-python && cd yhatlabs-python
|
|
426
|
+
uv venv && uv pip install -e ".[dev]"
|
|
427
|
+
.venv/bin/pytest -m "not live" # offline tests against a fake API
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the checks, the changelog rule and how releases are made.
|
|
431
|
+
`python docs/readme_images.py` regenerates the images in this README from live calls.
|
|
432
|
+
|
|
433
|
+
</details>
|
|
434
|
+
|
|
435
|
+
## Support
|
|
436
|
+
|
|
437
|
+
Open an issue (include the `request_id` from any error, never your key), or write to founders@yhatlabs.com.
|
|
438
|
+
|
|
439
|
+
## License
|
|
440
|
+
|
|
441
|
+
Apache-2.0
|