Skip to main content

license-shield interrogate-badge Ruff coverage-badge versions-shield

Time based cross validation

timebasedcv is a Python codebase that provides a cross validation strategy based on time.


Documentation | Repository | Issue Tracker


Disclaimer ⚠️

This codebase is experimental and is working for my use cases. It is very probable that there are cases not entirely covered and for which it could break (badly). If you find them, please feel free to open an issue in the issue page of the repo.

Description ✨

The current implementation of scikit-learn TimeSeriesSplit lacks the flexibility of having multiple samples within the same time period (or time unit).

timebasedcv addresses such problem by providing a cross validation strategy based on a time period rather than the number of samples. This is useful when the data is time dependent, and the split should keep together samples within the same time window.

Temporal data leakage is an issue and we want to prevent it from happening by providing splits that make sure the past and the future are well separated, so that data leakage does not spoil in a model cross validation.

Again, these splits points solely depend on the time period and not the number of observations.

Features 📜

We introduce two main classes:

  • TimeBasedSplit allows to define a split based on time unit (frequency), train size, test size, gap, stride, window type and mode. Remark that TimeBasedSplit is not compatible with scikit-learn CV Splitters. In fact, we have made the (opinioned) choice to:

    • Return the sliced arrays from .split(...), while scikit-learn CV Splitters return train and test indices of the split.
    • Require to pass the time series as input to .split(...) method, while scikit-learn CV Splitters require to provide only X, y, groups to .split(...).
    • Such time series is used to generate the boolean masks with which we slice the original arrays into train and test for each split.
  • Considering the above choices, we also provide a scikit-learn compatible splitter: TimeBasedCVSplitter. Considering the signature that .split(...) requires and the fact that CV Splitters need to know a priori the number of splits, TimeBasedCVSplitter is initialized with the time series containing the time information used to generate the train and test indices of each split.

Dataframe and array agnostic

  • Thanks to Narwhals, TimeBasedSplit works out of the box with pandas, polars, pyarrow and any other dataframe library supported by Narwhals.
  • Thanks to the array API, TimeBasedSplit works out of the box with numpy, cupy, dask.array and any other array library that support slicing à la numpy.

Installation 💻

TL;DR:

python -m pip install timebasedcv

For further information, please refer to the dedicated installation section.

Quickstart 🏃

The following code snippet is all you need to get started, yet consider checking out the getting started section of the documentation for a detailed guide on how to use the library.

The main takeaway should be that TimeBasedSplit allows for a lot of flexibility at the cost of having to specify a long list of parameters. This is what makes the library so powerful and flexible to cover the large majority of use cases.

First let's generate some data with different number of points per day:

import numpy as np
import pandas as pd

RNG = np.random.default_rng(seed=42)

dates = pd.Series(pd.date_range("2023-01-01", "2023-01-31", freq="D"))
size = len(dates)

df = (pd.concat([
        pd.DataFrame({
            "time": pd.date_range(start, end, periods=_size, inclusive="left"),
            "a": RNG.normal(size=_size-1),
            "b": RNG.normal(size=_size-1),
        })
        for start, end, _size in zip(dates[:-1], dates[1:], RNG.integers(2, 24, size-1))
    ])
    .reset_index(drop=True)
    .assign(y=lambda t: t[["a", "b"]].sum(axis=1) + RNG.normal(size=t.shape[0])/25)
)

df.set_index("time").resample("D").agg(count=("y", np.size)).head(5)
            count
time
2023-01-01      2
2023-01-02     18
2023-01-03     15
2023-01-04     10
2023-01-05     10

Then lets instantiate the TimeBasedSplit class:

from timebasedcv import TimeBasedSplit

tbs = TimeBasedSplit(
    frequency="days",
    train_size=10,
    forecast_horizon=5,
    gap=1,
    stride=3,
    window="rolling",
    mode="forward",
)

Now let's run split the data with the provided TimeBasedSplit instance:

X, y, time_series = df.loc[:, ["a", "b"]], df["y"], df["time"]

for X_train, X_forecast, y_train, y_forecast in tbs.split(X, y, time_series=time_series):
    print(f"Train: {X_train.shape}, Forecast: {X_forecast.shape}")
Train: (100, 2), Forecast: (51, 2)
Train: (114, 2), Forecast: (50, 2)
...
Train: (124, 2), Forecast: (40, 2)
Train: (137, 2), Forecast: (22, 2)

As we can see, each split does not necessarely have the same number of points, this is because the time series has a different number of points per day.

A picture is worth a thousand words, let's visualize the splits (blue dots represent the train points, while the red dots represent the forecastng points):

cross-validation

Contributing ✌️

Please read the Contributing guidelines in the documentation site.

License 👀

The project has a MIT Licence

Metadata

Release files for timebasedcv 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for timebasedcv 0.3.0
File Size Uploaded
timebasedcv-0.3.0.tar.gz 16.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for timebasedcv 0.3.0
File Interpreter ABI Platform
timebasedcv-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.2 kB

Release files / timebasedcv-0.3.0.tar.gz

Download URL timebasedcv-0.3.0.tar.gz
Size 16.7 kB
Tags Source
SHA-256 checksum
How to use checksums
7b2d393b63ee264077d9703658c80c6f15513976fb790698bc676a7295c3a9f1
BLAKE2b-256 checksum
How to use checksums
531edf8c165f29bc34264484ef84f37a51776f02372a426679967cee07e3e63f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.5.11

Release files / timebasedcv-0.3.0-py3-none-any.whl

Download URL timebasedcv-0.3.0-py3-none-any.whl
Size 19.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
16be0c6458f248dff3b435165561682674a8b8778006a13da1f311c2a32178b9
BLAKE2b-256 checksum
How to use checksums
1fd9d6ec24c094aeb06748b50413f5e8b751bfeb0391a85a549485f0f9e804a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.5.11

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page