Metadata-Version: 2.4
Name: wavetrainer
Version: 0.3.27
Summary: A library for automatically finding the optimal model within feature and hyperparameter space.
Home-page: https://github.com/8W9aG/wavetrainer
Author: Will Sackfield
Author-email: will.sackfield@gmail.com
License: MIT
Keywords: machine-learning,ML,hyperparameter,features
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas>=2.2.3
Requires-Dist: optuna>=4.2.1
Requires-Dist: scikit-learn>=1.5.2
Requires-Dist: feature-engine>=1.8.3
Requires-Dist: tqdm>=4.67.1
Requires-Dist: numpy>=1.26.4
Requires-Dist: scipy>=1.15.2
Requires-Dist: catboost>=1.2.7
Requires-Dist: venn-abers==1.4.6
Requires-Dist: pytz>=2025.1
Requires-Dist: torch>=2.6.0
Requires-Dist: pytest-is-running>=1.5.1
Requires-Dist: xgboost>=3.0.0
Requires-Dist: jax>=0.6.1
Requires-Dist: hyperopt>=0.2.7
Requires-Dist: pycaleva>=0.8.2
Requires-Dist: lightgbm>=4.6.0
Requires-Dist: kaleido>=0.2.1
Requires-Dist: memory-profiler>=0.61.0
Requires-Dist: matplotlib>=3.10.1
Requires-Dist: pyarrow>=19.0.1
Requires-Dist: scores>=2.3.0
Requires-Dist: xarray>=2025.11.0
Requires-Dist: filelock>=3.16.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: summary

# wavetrainer

<a href="https://pypi.org/project/wavetrainer/">
    <img alt="PyPi" src="https://img.shields.io/pypi/v/wavetrainer">
</a>

A library for automatically finding the optimal model within feature and hyperparameter space on time series models.

<p align="center">
    <img src="wavetrain.png" alt="wavetrain" width="200"/>
</p>

## Dependencies :globe_with_meridians:

Python 3.11.6:

- [pandas](https://pandas.pydata.org/)
- [optuna](https://optuna.readthedocs.io/en/stable/)
- [scikit-learn](https://scikit-learn.org/)
- [feature-engine](https://feature-engine.trainindata.com/en/latest/)
- [tqdm](https://github.com/tqdm/tqdm)
- [numpy](https://numpy.org/)
- [scipy](https://scipy.org/)
- [catboost](https://catboost.ai/)
- [venn-abers](https://github.com/ip200/venn-abers)
- [pytz](https://pythonhosted.org/pytz/)
- [torch](https://pytorch.org/)
- [pytest-is-running](https://github.com/adamchainz/pytest-is-running)
- [xgboost](https://xgboost.readthedocs.io/en/release_3.0.0/)
- [jax](https://github.com/jax-ml/jax)
- [hyperopt](https://github.com/hyperopt/hyperopt)
- [pycaleva](https://github.com/MartinWeigl/pycaleva)
- [lightgbm](https://github.com/microsoft/LightGBM)
- [kaleido](https://github.com/plotly/Kaleido)
- [memory-profiler](https://github.com/pythonprofilers/memory_profiler)
- [matplotlib](https://matplotlib.org/)
- [pyarrow](https://arrow.apache.org/docs/python/index.html)
- [scores](https://scores.readthedocs.io/en/stable/)
- [xarray](https://docs.xarray.dev/en/stable/)
- [filelock](https://py-filelock.readthedocs.io/en/latest/)

## Raison D'être :thought_balloon:

`wavetrainer` aims to split out the various aspects of creating a good model into different composable pieces and searches the space of these different pieces to find an optimal model. This came about after doing code like this multiple times on multiple projects. This is specifically geared towards time series models, validating itself through walk-forward analysis.

## Architecture :triangular_ruler:

`wavetrainer` is an object orientated library. The entities are organised like so:

* **Trainer**: A sklearn compatible object that can fit and predict data.
    * **Reducer**: An object that can reduce the feature space based on heuristics.
    * **Weights**: An object that adds weights to the features.
    * **Selector**: An object that can select which features to include from the training set.
    * **Calibrator**: An object that can calibrate the probabilities produced by the model.
    * **Model**: An object that represents the underlying model architecture being used.
    * **Windower**: An object that represents the lookback window of the data.

## Installation :inbox_tray:

This is a python package hosted on pypi, so to install simply run the following command:

`pip install wavetrainer`

or install using this local repository:

`python setup.py install --old-and-unmanageable`

## Usage example :eyes:

The use of `wavetrainer` is entirely through code due to it being a library. It attempts to hide most of its complexity from the user, so it only has a few functions of relevance in its outward API.

### Training

To train a model:

```python
import wavetrainer as wt
import pandas as pd
import numpy as np
import random

data_size = 10
df = pd.DataFrame(
    np.random.randint(0, 30, size=data_size),
    columns=["X"],
    index=pd.date_range("20180101", periods=data_size),
)
df["Y"] = [random.choice([True, False]) for _ in range(data_size)]

X = df["X"]
Y = df["Y"]

wavetrainer = wt.create("my_wavetrain")
wavetrainer = wavetrainer.fit(X, y=Y)
```

This will save it to the folder `my_wavetrain`.

#### Parallel trials

Passing `n_jobs` greater than 1 (or a negative value, which counts back from the number of CPUs as in joblib) runs the Optuna trials in that many worker processes. With `n_jobs=1`, the default, trials run one at a time in the calling process.

- Workers are started with `spawn`, so a script that trains in parallel must guard its entry point with `if __name__ == "__main__":`.
- Parallel studies are stored in `study.journal` next to `study.db`. The first parallel run on a column copies an existing `study.db` into the journal, and from then on the journal is used whatever `n_jobs` is. `study.db` and its sampler (renamed `sampler.pkl.sqlite`) are left as a backup.
- The training frame is written once to a hidden `.wavetrainer-frame-*.joblib` file in the training folder and memory-mapped by the workers. It is removed when `fit` returns.
- If a worker dies (for example, a segfault in native code), the failure is logged, its running trial is marked as failed, its remaining trials are given to a new worker, and training carries on.
- Each worker gets its own GPU when there are at least as many GPUs as workers. Otherwise the workers train on the CPU. Saving the final model for each walk-forward fold runs in the calling process, on the GPU if it has one.

### Load

To load a trainer (as well as its composite states):

```python
import wavetrainer as wt

wavetrainer = wt.load("my_wavetrain")
```

### Predict

To make a prediction from new data:

```python
import wavetrainer as wt
import pandas as pd
import numpy as np

wavetrainer = wt.load("my_wavetrain")
data_size = 1
df = pd.DataFrame(
    np.random.randint(0, 30, size=data_size),
    columns=["X"],
    index=pd.date_range("20180101", periods=data_size),
)
X = df["X"]

preds = wavetrainer.predict(X)
```

`preds` will now contain both the predictions and the probabilities associated with those predictions.

### Debugging

Set the `WAVETRAINER_DUMP_INFERENCE` environment variable to snapshot the input frame of each CatBoost inference to a parquet file. It is unset (disabled) by default, since the write is expensive when predicting over many columns.

- `WAVETRAINER_DUMP_INFERENCE=1` writes to `last_catboost_inference.parquet` in the current directory.
- `WAVETRAINER_DUMP_INFERENCE=/path/to/file.parquet` writes to that path.

Each write goes to a temporary file that is then atomically moved into place, so concurrent workers never leave a partially written file; the last complete write wins.

## License :memo:

The project is available under the [MIT License](LICENSE).
