time-split 0.3.0__tar.gz → 0.5.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.
- {time_split-0.3.0 → time_split-0.5.0}/PKG-INFO +25 -5
- {time_split-0.3.0 → time_split-0.5.0}/README.md +18 -1
- {time_split-0.3.0 → time_split-0.5.0}/pyproject.toml +14 -7
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/__init__.py +1 -1
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_limits.py +26 -22
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_process_available.py +7 -5
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_schedule.py +3 -3
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_splitter.py +26 -29
- time_split-0.5.0/src/time_split/_compat.py +49 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_docstrings.py +20 -8
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/__init__.py +2 -1
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_plot.py +35 -14
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_progress.py +3 -2
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_split.py +7 -4
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_to_string.py +51 -2
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/sklearn/_impl.py +5 -24
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/settings.py +14 -9
- time_split-0.5.0/src/time_split/streamlit/README.md +82 -0
- time_split-0.5.0/src/time_split/streamlit/__init__.py +1 -0
- time_split-0.5.0/src/time_split/streamlit/_logging.py +75 -0
- time_split-0.5.0/src/time_split/streamlit/_support.py +67 -0
- time_split-0.5.0/src/time_split/streamlit/_views.py +125 -0
- time_split-0.5.0/src/time_split/streamlit/config.py +68 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/__init__.py +1 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/data/__init__.py +7 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/data/_data.py +336 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/data/_datasets.py +123 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/data/_sample_data.py +147 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/data/load.py +111 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/data/sample.csv +397 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/display/__init__.py +15 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/display/_aggregate.py +227 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/display/_code.py +175 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/display/_overview.py +51 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/display/_performance.py +176 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/display/_plot_folds.py +92 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/__init__.py +21 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_datetime.py +30 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_duration.py +78 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_expand_limits.py +141 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_schedule.py +135 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_schedule_filter.py +43 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_span.py +122 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_splitter_kwargs.py +29 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/ruff.toml +6 -0
- time_split-0.5.0/src/time_split/streamlit/widgets/types.py +351 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/support/__init__.py +4 -1
- time_split-0.5.0/src/time_split/support/_create_explorer_link.py +118 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/types.py +3 -3
- {time_split-0.3.0 → time_split-0.5.0}/LICENSE.md +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_datetime_index_like.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_span.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_weight.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_support.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/_log_progress.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/base.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/pandas/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/pandas/_impl.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/polars/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/polars/_impl.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/sklearn/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/py.typed +0 -0
- {time_split-0.3.0 → time_split-0.5.0}/src/time_split/support/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: time-split
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Time-based k-fold validation splits for heterogeneous data.
|
|
5
5
|
Home-page: https://github.com/rsundqvist/time-split
|
|
6
6
|
Author: Richard Sundqvist
|
|
@@ -19,10 +19,13 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
21
|
Provides-Extra: plotting
|
|
22
|
-
|
|
22
|
+
Provides-Extra: streamlit
|
|
23
|
+
Requires-Dist: croniter ; extra == "streamlit"
|
|
24
|
+
Requires-Dist: matplotlib ; extra == "plotting" or extra == "streamlit"
|
|
23
25
|
Requires-Dist: pandas (>=2.0.3)
|
|
24
|
-
Requires-Dist: rics (>=
|
|
25
|
-
Requires-Dist: seaborn ; extra == "plotting"
|
|
26
|
+
Requires-Dist: rics (>=4.1.1)
|
|
27
|
+
Requires-Dist: seaborn ; extra == "plotting" or extra == "streamlit"
|
|
28
|
+
Requires-Dist: streamlit ; extra == "streamlit"
|
|
26
29
|
Project-URL: Bug Tracker, https://github.com/rsundqvist/time-split/issues
|
|
27
30
|
Project-URL: Changelog, https://github.com/rsundqvist/time-split/blob/master/CHANGELOG.md
|
|
28
31
|
Project-URL: Documentation, https://time-split.readthedocs.io
|
|
@@ -39,6 +42,7 @@ Time-based k-fold validation splits for heterogeneous data.
|
|
|
39
42
|
[](https://codecov.io/gh/rsundqvist/time-split)
|
|
40
43
|
[](https://time-split.readthedocs.io/)
|
|
41
44
|
[](https://pypi.python.org/pypi/time-split)
|
|
45
|
+
[](https://hub.docker.com/r/rsundqvist/time-split/)
|
|
42
46
|
|
|
43
47
|
<div align="center">
|
|
44
48
|
<img alt="Plotted folds on a two-by-two grid."
|
|
@@ -58,13 +62,29 @@ strong non-stationary characteristics.
|
|
|
58
62
|
a CRON-expression, or a pre-defined list.
|
|
59
63
|
- [Data selection](https://time-split.readthedocs.io/en/stable/guide/spans.html) based on a timedelta, or the splitting
|
|
60
64
|
schedule itself.
|
|
61
|
-
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/
|
|
65
|
+
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/expand_limits.html) data limits for
|
|
62
66
|
supported data types.
|
|
63
67
|
- [Plotting function](https://time-split.readthedocs.io/en/stable/generated/time_split.html#time_split.plot) for
|
|
64
68
|
visualization of folds. Display fold sizes (hour/row count), or
|
|
65
69
|
[use custom text](https://time-split.readthedocs.io/en/stable/auto_examples/plotting_with_metrics.html).
|
|
66
70
|
- [Integrations](https://time-split.readthedocs.io/en/stable/generated/time_split.integration.html) for popular
|
|
67
71
|
libraries such as `pandas`, `polars` and `scikit-learn`.
|
|
72
|
+
- Convenient [web application](#experimenting-with-parameters) for exploring folds with different parameters.
|
|
73
|
+
|
|
74
|
+
## Experimenting with parameters
|
|
75
|
+
The **Time Fold Explorer** application
|
|
76
|
+
(available [here](http://20.240.208.202/time-split/?data=1554942900-1557610200&schedule=0+0+%2A+%2A+MON%2CFRI&n_splits=2&step=2&show_removed=True))
|
|
77
|
+
is designed to help evaluate the effects of different
|
|
78
|
+
[parameters](https://time-split.readthedocs.io/en/stable/#parameter-overview).
|
|
79
|
+
To start it locally using
|
|
80
|
+
[](https://hub.docker.com/r/rsundqvist/time-split/)
|
|
81
|
+
Docker, run
|
|
82
|
+
```sh
|
|
83
|
+
docker run -p 8501:8501 rsundqvist/time-split
|
|
84
|
+
```
|
|
85
|
+
in the terminal. You may use
|
|
86
|
+
[`create_explorer_link()`](https://time-split.readthedocs.io/en/stable/generated/time_split.support.html#time_split.support.create_explorer_link)
|
|
87
|
+
to build application URLs with preselected splitting parameters.
|
|
68
88
|
|
|
69
89
|
## Installation
|
|
70
90
|
The package is published through the [Python Package Index (PyPI)]. Source code
|
|
@@ -8,6 +8,7 @@ Time-based k-fold validation splits for heterogeneous data.
|
|
|
8
8
|
[](https://codecov.io/gh/rsundqvist/time-split)
|
|
9
9
|
[](https://time-split.readthedocs.io/)
|
|
10
10
|
[](https://pypi.python.org/pypi/time-split)
|
|
11
|
+
[](https://hub.docker.com/r/rsundqvist/time-split/)
|
|
11
12
|
|
|
12
13
|
<div align="center">
|
|
13
14
|
<img alt="Plotted folds on a two-by-two grid."
|
|
@@ -27,13 +28,29 @@ strong non-stationary characteristics.
|
|
|
27
28
|
a CRON-expression, or a pre-defined list.
|
|
28
29
|
- [Data selection](https://time-split.readthedocs.io/en/stable/guide/spans.html) based on a timedelta, or the splitting
|
|
29
30
|
schedule itself.
|
|
30
|
-
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/
|
|
31
|
+
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/expand_limits.html) data limits for
|
|
31
32
|
supported data types.
|
|
32
33
|
- [Plotting function](https://time-split.readthedocs.io/en/stable/generated/time_split.html#time_split.plot) for
|
|
33
34
|
visualization of folds. Display fold sizes (hour/row count), or
|
|
34
35
|
[use custom text](https://time-split.readthedocs.io/en/stable/auto_examples/plotting_with_metrics.html).
|
|
35
36
|
- [Integrations](https://time-split.readthedocs.io/en/stable/generated/time_split.integration.html) for popular
|
|
36
37
|
libraries such as `pandas`, `polars` and `scikit-learn`.
|
|
38
|
+
- Convenient [web application](#experimenting-with-parameters) for exploring folds with different parameters.
|
|
39
|
+
|
|
40
|
+
## Experimenting with parameters
|
|
41
|
+
The **Time Fold Explorer** application
|
|
42
|
+
(available [here](http://20.240.208.202/time-split/?data=1554942900-1557610200&schedule=0+0+%2A+%2A+MON%2CFRI&n_splits=2&step=2&show_removed=True))
|
|
43
|
+
is designed to help evaluate the effects of different
|
|
44
|
+
[parameters](https://time-split.readthedocs.io/en/stable/#parameter-overview).
|
|
45
|
+
To start it locally using
|
|
46
|
+
[](https://hub.docker.com/r/rsundqvist/time-split/)
|
|
47
|
+
Docker, run
|
|
48
|
+
```sh
|
|
49
|
+
docker run -p 8501:8501 rsundqvist/time-split
|
|
50
|
+
```
|
|
51
|
+
in the terminal. You may use
|
|
52
|
+
[`create_explorer_link()`](https://time-split.readthedocs.io/en/stable/generated/time_split.support.html#time_split.support.create_explorer_link)
|
|
53
|
+
to build application URLs with preselected splitting parameters.
|
|
37
54
|
|
|
38
55
|
## Installation
|
|
39
56
|
The package is published through the [Python Package Index (PyPI)]. Source code
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "time-split"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "Time-based k-fold validation splits for heterogeneous data."
|
|
5
5
|
authors = ["Richard Sundqvist <richard.sundqvist@live.se>"]
|
|
6
6
|
|
|
@@ -18,7 +18,7 @@ classifiers = [
|
|
|
18
18
|
"License :: OSI Approved :: MIT License",
|
|
19
19
|
"Natural Language :: English",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
|
-
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
22
|
"Programming Language :: Python :: 3.11",
|
|
23
23
|
"Programming Language :: Python :: 3.12",
|
|
24
24
|
]
|
|
@@ -34,20 +34,26 @@ classifiers = [
|
|
|
34
34
|
python = ">=3.11"
|
|
35
35
|
|
|
36
36
|
pandas = ">=2.0.3"
|
|
37
|
-
rics = ">=
|
|
37
|
+
rics = ">=4.1.1"
|
|
38
38
|
|
|
39
39
|
# plotting
|
|
40
40
|
matplotlib = { version = "*", optional = true }
|
|
41
41
|
seaborn = { version = "*", optional = true }
|
|
42
42
|
|
|
43
|
+
# explorer
|
|
44
|
+
streamlit = { version = "*", optional = true }
|
|
45
|
+
croniter = { version = "*", optional = true }
|
|
46
|
+
|
|
43
47
|
[tool.poetry.extras]
|
|
44
48
|
plotting = ["matplotlib", "seaborn"]
|
|
49
|
+
streamlit = ["matplotlib", "seaborn", "streamlit", "croniter"]
|
|
45
50
|
|
|
46
51
|
[tool.poetry.group.manual-extras.dependencies]
|
|
47
52
|
# Extras that must be available for docs + tests go here. Doc/test-only deps go in their respective groups.
|
|
48
53
|
croniter = "^2.0.3"
|
|
49
54
|
pyarrow = ">=15.0.2,<17.0.0"
|
|
50
55
|
polars = { version = "^0.20.19", extras = ["pandas"] }
|
|
56
|
+
streamlit = "*"
|
|
51
57
|
|
|
52
58
|
[tool.poetry.group.docs]
|
|
53
59
|
[tool.poetry.group.docs.dependencies]
|
|
@@ -84,11 +90,11 @@ dask = { version = "==2024.5.0", extras = ["dataframe"] }
|
|
|
84
90
|
|
|
85
91
|
[tool.poetry.group.devops.dependencies]
|
|
86
92
|
invoke = "^2.2.0"
|
|
87
|
-
mypy = "^1.
|
|
88
|
-
ruff = "
|
|
93
|
+
mypy = "^1.10.1"
|
|
94
|
+
ruff = "^0.5.0"
|
|
89
95
|
|
|
90
|
-
safety = "^3.
|
|
91
|
-
codespell = "^2.
|
|
96
|
+
safety = "^3.2.3"
|
|
97
|
+
codespell = "^2.3.0"
|
|
92
98
|
bump2version = "^1.0.1"
|
|
93
99
|
|
|
94
100
|
[tool.ruff]
|
|
@@ -147,6 +153,7 @@ ignore = [
|
|
|
147
153
|
"SIM108", # Use ternary operator - coverage issue, see https://github.com/nedbat/coveragepy/issues/509
|
|
148
154
|
"TD003", # Missing issue link
|
|
149
155
|
]
|
|
156
|
+
allowed-confusables = ["ℹ"]
|
|
150
157
|
|
|
151
158
|
[tool.ruff.lint.per-file-ignores]
|
|
152
159
|
"tests/*" = [
|
|
@@ -4,8 +4,8 @@ from typing import NamedTuple
|
|
|
4
4
|
|
|
5
5
|
from pandas import Timedelta, Timestamp
|
|
6
6
|
|
|
7
|
-
from ..settings import
|
|
8
|
-
from ..types import
|
|
7
|
+
from ..settings import auto_expand_limits
|
|
8
|
+
from ..types import ExpandLimits, TimedeltaTypes
|
|
9
9
|
|
|
10
10
|
LimitsTuple = tuple[Timestamp, Timestamp]
|
|
11
11
|
LevelTuple = tuple[TimedeltaTypes, TimedeltaTypes, TimedeltaTypes]
|
|
@@ -17,17 +17,21 @@ class _TimedeltaTuple(NamedTuple):
|
|
|
17
17
|
tolerance: Timedelta
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def expand_limits(
|
|
20
|
+
def expand_limits(
|
|
21
|
+
limits: LimitsTuple,
|
|
22
|
+
spec: ExpandLimits | LevelTuple | Iterable[LevelTuple] = "auto",
|
|
23
|
+
) -> LimitsTuple:
|
|
21
24
|
"""Derive the `"real"` bounds of `limits`.
|
|
22
25
|
|
|
23
26
|
Args:
|
|
24
27
|
limits: A tuple ``(min, max)`` of timestamps.
|
|
25
|
-
|
|
26
|
-
``[(start_at, round_to, tolerance)...]``. Passing
|
|
27
|
-
|
|
28
|
+
spec: Expansion spec as described in the :ref:`User guide`. Also supports level-tuples
|
|
29
|
+
``[(start_at, round_to, tolerance)...]``. Passing
|
|
30
|
+
``expand_limits=[settings.auto_expand_limits.day, settings.auto_expand_limits.hour]``
|
|
31
|
+
is equivalent to ``expand_limits='auto'``.
|
|
28
32
|
|
|
29
33
|
Returns:
|
|
30
|
-
Limits rounded according to the
|
|
34
|
+
Limits rounded according to the given specification.
|
|
31
35
|
|
|
32
36
|
Raises:
|
|
33
37
|
ValueError: For invalid limits.
|
|
@@ -38,23 +42,23 @@ def expand_limits(limits: LimitsTuple, *, flex: Flex | LevelTuple | Iterable[Lev
|
|
|
38
42
|
|
|
39
43
|
Basic usage.
|
|
40
44
|
|
|
41
|
-
>>> expand_limits(limits,
|
|
45
|
+
>>> expand_limits(limits, "d")
|
|
42
46
|
(Timestamp('2019-05-11 00:00:00'), Timestamp('2019-05-12 00:00:00'))
|
|
43
47
|
|
|
44
48
|
You may specify a maximum "distance" that limits may be expanded.
|
|
45
49
|
|
|
46
|
-
>>> expand_limits(limits,
|
|
50
|
+
>>> expand_limits(limits, "d<1h")
|
|
47
51
|
(Timestamp('2019-05-11 00:00:00'), Timestamp('2019-05-11 22:05:30'))
|
|
48
52
|
|
|
49
53
|
Limits will never be rounded in the "wrong" direction...
|
|
50
54
|
|
|
51
55
|
>>> limits = Timestamp("2019-05-11"), Timestamp("2019-05-11 11:05:30")
|
|
52
|
-
>>> expand_limits(limits,
|
|
56
|
+
>>> expand_limits(limits, "d")
|
|
53
57
|
(Timestamp('2019-05-11 00:00:00'), Timestamp('2019-05-11 11:05:30'))
|
|
54
58
|
|
|
55
59
|
...even if you make the tolerance large enough.
|
|
56
60
|
|
|
57
|
-
>>> expand_limits(limits,
|
|
61
|
+
>>> expand_limits(limits, "d<14h")
|
|
58
62
|
(Timestamp('2019-05-11 00:00:00'), Timestamp('2019-05-11 11:05:30'))
|
|
59
63
|
|
|
60
64
|
"""
|
|
@@ -62,20 +66,20 @@ def expand_limits(limits: LimitsTuple, *, flex: Flex | LevelTuple | Iterable[Lev
|
|
|
62
66
|
msg = f"Bad limits. Expected limits[1] > limits[0], but got {limits=}."
|
|
63
67
|
raise ValueError(msg)
|
|
64
68
|
|
|
65
|
-
if
|
|
69
|
+
if spec is False:
|
|
66
70
|
return limits
|
|
67
71
|
|
|
68
|
-
if
|
|
72
|
+
if spec is True or spec == "auto":
|
|
69
73
|
return _from_levels(limits)
|
|
70
74
|
|
|
71
|
-
if isinstance(
|
|
72
|
-
round_to, _, tolerance =
|
|
75
|
+
if isinstance(spec, str):
|
|
76
|
+
round_to, _, tolerance = spec.partition("<")
|
|
73
77
|
level = _make_level(None, round_to=round_to.strip(), tolerance=tolerance.strip())
|
|
74
78
|
return _apply(limits, level=level)
|
|
75
|
-
if isinstance(
|
|
76
|
-
return _from_levels(limits, levels=[_make_level(*
|
|
79
|
+
if isinstance(spec, tuple):
|
|
80
|
+
return _from_levels(limits, levels=[_make_level(*spec)])
|
|
77
81
|
|
|
78
|
-
return _from_levels(limits, levels=starmap(_make_level,
|
|
82
|
+
return _from_levels(limits, levels=starmap(_make_level, spec))
|
|
79
83
|
|
|
80
84
|
|
|
81
85
|
def _from_levels(limits: LimitsTuple, *, levels: Iterable[_TimedeltaTuple] | None = None) -> LimitsTuple:
|
|
@@ -106,7 +110,7 @@ def _apply(limits: LimitsTuple, *, level: _TimedeltaTuple) -> LimitsTuple:
|
|
|
106
110
|
if abs(hi_ceil - hi) > level.tolerance:
|
|
107
111
|
return limits
|
|
108
112
|
|
|
109
|
-
if
|
|
113
|
+
if auto_expand_limits.SANITY_CHECK:
|
|
110
114
|
if lo.round(level.round_to) != lo_floor:
|
|
111
115
|
return limits
|
|
112
116
|
if hi.round(level.round_to) != hi_ceil:
|
|
@@ -116,12 +120,12 @@ def _apply(limits: LimitsTuple, *, level: _TimedeltaTuple) -> LimitsTuple:
|
|
|
116
120
|
|
|
117
121
|
|
|
118
122
|
def _levels_from_settings() -> list[_TimedeltaTuple]:
|
|
119
|
-
day, hour = _make_level(*
|
|
123
|
+
day, hour = _make_level(*auto_expand_limits.day), _make_level(*auto_expand_limits.hour)
|
|
120
124
|
if day.round_to != Timedelta(days=1):
|
|
121
|
-
msg = f"Invalid settings: {
|
|
125
|
+
msg = f"Invalid settings: {auto_expand_limits.day=} must have round_to=1 day."
|
|
122
126
|
raise ValueError(msg)
|
|
123
127
|
if hour.round_to != Timedelta(hours=1):
|
|
124
|
-
msg = f"Invalid settings: {
|
|
128
|
+
msg = f"Invalid settings: {auto_expand_limits.hour=} must have round_to=1 hour."
|
|
125
129
|
raise ValueError(msg)
|
|
126
130
|
return [day, hour]
|
|
127
131
|
|
|
@@ -3,9 +3,10 @@ from typing import NamedTuple
|
|
|
3
3
|
from pandas import DatetimeIndex, Timestamp, isna
|
|
4
4
|
|
|
5
5
|
from .._support import handle_dask
|
|
6
|
-
from ..types import DatetimeIterable,
|
|
6
|
+
from ..types import DatetimeIterable, ExpandLimits
|
|
7
7
|
from ._datetime_index_like import DatetimeIndexLike
|
|
8
|
-
from ._limits import LimitsTuple
|
|
8
|
+
from ._limits import LimitsTuple
|
|
9
|
+
from ._limits import expand_limits as expand
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class ProcessAvailableResult(NamedTuple):
|
|
@@ -16,12 +17,13 @@ class ProcessAvailableResult(NamedTuple):
|
|
|
16
17
|
expanded_limits: LimitsTuple
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def process_available(available: DatetimeIterable, *,
|
|
20
|
+
def process_available(available: DatetimeIterable, *, expand_limits: ExpandLimits) -> ProcessAvailableResult:
|
|
20
21
|
"""Process a user-given `available` argument.
|
|
21
22
|
|
|
22
23
|
Args:
|
|
23
24
|
available: Available data from user. May be ``None``
|
|
24
|
-
|
|
25
|
+
expand_limits: Expansion spec as described in the :ref:`User guide`. Determines how much (if at all) to expand
|
|
26
|
+
limits.
|
|
25
27
|
|
|
26
28
|
Returns:
|
|
27
29
|
A tuple ``(available, limits)``. Note that `available` will be ``None``, it has not been iterated over. This
|
|
@@ -49,7 +51,7 @@ def process_available(available: DatetimeIterable, *, flex: Flex) -> ProcessAvai
|
|
|
49
51
|
# (croniter 1.4.1 + numpy 1.25.2): croniter cannot handle numpy.datetime64
|
|
50
52
|
limits = Timestamp(min_dt), Timestamp(max_dt)
|
|
51
53
|
|
|
52
|
-
expanded_limits =
|
|
54
|
+
expanded_limits = expand(limits, spec=expand_limits)
|
|
53
55
|
return ProcessAvailableResult(available_retval, limits=limits, expanded_limits=expanded_limits)
|
|
54
56
|
|
|
55
57
|
|
|
@@ -2,7 +2,7 @@ from typing import NamedTuple, cast, get_args
|
|
|
2
2
|
|
|
3
3
|
from pandas import DatetimeIndex, NaT, Timedelta, Timestamp, date_range
|
|
4
4
|
|
|
5
|
-
from ..types import DatetimeIterable,
|
|
5
|
+
from ..types import DatetimeIterable, ExpandLimits, Schedule, TimedeltaTypes
|
|
6
6
|
from ._limits import LimitsTuple
|
|
7
7
|
from ._process_available import ProcessAvailableResult, process_available
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ class MaterializedSchedule(NamedTuple):
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def materialize_schedule(
|
|
20
|
-
schedule: Schedule,
|
|
20
|
+
schedule: Schedule, expand_limits: ExpandLimits, *, available: DatetimeIterable | None = None
|
|
21
21
|
) -> MaterializedSchedule:
|
|
22
22
|
"""Materialize user schedule based on available data."""
|
|
23
23
|
if available is None:
|
|
@@ -29,7 +29,7 @@ def materialize_schedule(
|
|
|
29
29
|
except TypeError as e:
|
|
30
30
|
raise ValueError("Schedule must be explicit when not bounded by an available range.") from e
|
|
31
31
|
|
|
32
|
-
available_metadata = process_available(available,
|
|
32
|
+
available_metadata = process_available(available, expand_limits=expand_limits)
|
|
33
33
|
min_dt, max_dt = available_metadata.expanded_limits
|
|
34
34
|
|
|
35
35
|
if isinstance(schedule, str) and _cron_like(schedule):
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from dataclasses import asdict, dataclass
|
|
3
|
+
from functools import lru_cache
|
|
3
4
|
from typing import cast, get_args
|
|
4
5
|
|
|
5
|
-
from pandas import Timedelta
|
|
6
|
+
from pandas import Timedelta
|
|
6
7
|
from rics.misc import format_kwargs
|
|
7
|
-
from rics.performance import format_seconds
|
|
8
8
|
|
|
9
|
-
from ..settings import misc
|
|
9
|
+
from ..settings import misc as settings
|
|
10
10
|
from ..types import (
|
|
11
11
|
DatetimeIndexSplitterKwargs,
|
|
12
12
|
DatetimeIterable,
|
|
13
13
|
DatetimeSplitBounds,
|
|
14
14
|
DatetimeSplits,
|
|
15
|
-
|
|
15
|
+
ExpandLimits,
|
|
16
16
|
Schedule,
|
|
17
17
|
Span,
|
|
18
18
|
TimedeltaTypes,
|
|
@@ -29,11 +29,12 @@ class DatetimeIndexSplitter:
|
|
|
29
29
|
"""Backend interface for splitting user data. See the :ref:`Parameter overview` page."""
|
|
30
30
|
|
|
31
31
|
schedule: Schedule
|
|
32
|
-
before: Span
|
|
33
|
-
after: Span
|
|
34
|
-
step: int
|
|
35
|
-
n_splits: int
|
|
36
|
-
|
|
32
|
+
before: Span = "7d"
|
|
33
|
+
after: Span = 1
|
|
34
|
+
step: int = 1
|
|
35
|
+
n_splits: int = 0
|
|
36
|
+
expand_limits: ExpandLimits = "auto"
|
|
37
|
+
ignore_filters: bool = False
|
|
37
38
|
|
|
38
39
|
def get_splits(self, available: DatetimeIterable | None = None) -> DatetimeSplits:
|
|
39
40
|
"""Compute a split of given user data."""
|
|
@@ -48,13 +49,13 @@ class DatetimeIndexSplitter:
|
|
|
48
49
|
return splits, ms
|
|
49
50
|
|
|
50
51
|
def _materialize_schedule(self, available: DatetimeIterable | None = None) -> MaterializedSchedule:
|
|
51
|
-
ms = materialize_schedule(self.schedule, self.
|
|
52
|
+
ms = materialize_schedule(self.schedule, self.expand_limits, available=available)
|
|
52
53
|
if not ms.schedule.sort_values().equals(ms.schedule):
|
|
53
54
|
raise ValueError(f"schedule must be sorted in ascending order; schedule={self.schedule!r} is not valid.")
|
|
54
55
|
|
|
55
56
|
types = get_args(TimedeltaTypes)
|
|
56
57
|
if (
|
|
57
|
-
|
|
58
|
+
settings.snap_to_end
|
|
58
59
|
and self.after != "all"
|
|
59
60
|
and isinstance(self.schedule, types)
|
|
60
61
|
and isinstance(self.after, types)
|
|
@@ -98,7 +99,7 @@ class DatetimeIndexSplitter:
|
|
|
98
99
|
msg = f"No valid splits with {limits_info}split params: ({format_kwargs(self.as_dict())})"
|
|
99
100
|
raise ValueError(msg)
|
|
100
101
|
|
|
101
|
-
return self._filter(retval)
|
|
102
|
+
return retval if self.ignore_filters else self._filter(retval)
|
|
102
103
|
|
|
103
104
|
def _filter(self, splits: DatetimeSplits) -> DatetimeSplits:
|
|
104
105
|
"""Apply splitting arguments.
|
|
@@ -121,29 +122,25 @@ class DatetimeIndexSplitter:
|
|
|
121
122
|
if self.step < 0: # Poorly documented - might not work as expected?
|
|
122
123
|
splits.reverse()
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
if settings.filter is None:
|
|
126
|
+
return splits
|
|
127
|
+
|
|
128
|
+
return [s for s in splits if settings.filter(*s)]
|
|
125
129
|
|
|
126
130
|
def _log_expansion(self, original: LimitsTuple, *, expanded: LimitsTuple) -> None:
|
|
127
|
-
|
|
128
|
-
return
|
|
131
|
+
from .._frontend import format_expanded_limits
|
|
129
132
|
|
|
130
|
-
if not LOGGER.isEnabledFor(logging.INFO):
|
|
133
|
+
if original == expanded or not LOGGER.isEnabledFor(logging.INFO):
|
|
131
134
|
return
|
|
132
135
|
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
message = format_expanded_limits(original, expanded=expanded, expand_limits=self.expand_limits)
|
|
137
|
+
self._emit_once(message)
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
LOGGER.info(
|
|
143
|
-
f"Available data limits have been expanded (since flex={self.flex!r}):\n"
|
|
144
|
-
f" start: {stringify(original[0], new=expanded[0])}\n"
|
|
145
|
-
f" end: {stringify(original[1], new=expanded[1])}"
|
|
146
|
-
)
|
|
139
|
+
@staticmethod
|
|
140
|
+
@lru_cache(8)
|
|
141
|
+
def _emit_once(message: str) -> None:
|
|
142
|
+
"""Emit if **not** in the eight most recent emission messages."""
|
|
143
|
+
LOGGER.info(message)
|
|
147
144
|
|
|
148
145
|
def __post_init__(self) -> None:
|
|
149
146
|
# Verify n_splits
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Vendored functions."""
|
|
2
|
+
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
5
|
+
from warnings import warn
|
|
6
|
+
|
|
7
|
+
from rics.misc import tname
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from rics import strings
|
|
11
|
+
|
|
12
|
+
fmt_perf = strings.format_perf_counter
|
|
13
|
+
fmt_sec = strings.format_seconds
|
|
14
|
+
except ImportError:
|
|
15
|
+
from rics import performance
|
|
16
|
+
|
|
17
|
+
# TODO(0.5.0): Drop this.
|
|
18
|
+
fmt_perf = performance.format_perf_counter
|
|
19
|
+
fmt_sec = performance.format_seconds
|
|
20
|
+
|
|
21
|
+
P = ParamSpec("P")
|
|
22
|
+
T = TypeVar("T")
|
|
23
|
+
|
|
24
|
+
PARAMS = (("expand_limits", "flex"),)
|
|
25
|
+
WARNED: set[str] = set() # Minimize the amount of warnings - once per param is enough.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def deprecated_params(__func: Callable[P, T], /) -> Callable[P, T]:
|
|
29
|
+
@wraps(__func)
|
|
30
|
+
def wrap(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
31
|
+
for new, old in PARAMS:
|
|
32
|
+
if old in kwargs:
|
|
33
|
+
if new in kwargs:
|
|
34
|
+
name = tname(__func, prefix_classname=True)
|
|
35
|
+
msg = f"Ambiguous call: {name}() does not accept both `{old}` and `{new}`."
|
|
36
|
+
raise TypeError(msg)
|
|
37
|
+
|
|
38
|
+
if old not in WARNED:
|
|
39
|
+
WARNED.add(old)
|
|
40
|
+
|
|
41
|
+
name = tname(__func, prefix_classname=True)
|
|
42
|
+
msg = f"{name}(): The `{old}` parameter is deprecated; use `{new}` instead."
|
|
43
|
+
warn(message=msg, category=DeprecationWarning, stacklevel=2)
|
|
44
|
+
|
|
45
|
+
kwargs[new] = kwargs.pop(old)
|
|
46
|
+
|
|
47
|
+
return __func(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
return wrap
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
from typing import TypeVar
|
|
1
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
from time_split._compat import deprecated_params
|
|
4
|
+
|
|
5
|
+
P = ParamSpec("P")
|
|
6
|
+
T = TypeVar("T")
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
def add_docstrings(**kwargs: str) -> None:
|
|
@@ -8,11 +11,17 @@ def add_docstrings(**kwargs: str) -> None:
|
|
|
8
11
|
_DOCSTRINGS.update(kwargs)
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
def docs(
|
|
14
|
+
def docs(__func: Callable[P, T], /) -> Callable[P, T]:
|
|
12
15
|
"""Add info the docstring of a function."""
|
|
13
|
-
assert
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
assert __func.__doc__ is not None # noqa: S101
|
|
17
|
+
__func.__doc__ = __func.__doc__.format(**_DOCSTRINGS)
|
|
18
|
+
|
|
19
|
+
if isinstance(__func, type):
|
|
20
|
+
__func.__init__ = deprecated_params(__func.__init__)
|
|
21
|
+
else:
|
|
22
|
+
__func = deprecated_params(__func)
|
|
23
|
+
|
|
24
|
+
return __func
|
|
16
25
|
|
|
17
26
|
|
|
18
27
|
_OFFSET = "pandas :ref:`offset alias <pandas:timeseries.offset_aliases>`"
|
|
@@ -27,10 +36,13 @@ _DOCSTRINGS = {
|
|
|
27
36
|
"step": "Select a subset of folds, preferring folds later in the schedule.",
|
|
28
37
|
"n_splits": "Maximum number of folds, preferring folds later in the schedule.",
|
|
29
38
|
"available": "Binds `schedule` to a range.",
|
|
30
|
-
"
|
|
39
|
+
"expand_limits": f'A {_OFFSET} used to expand `available` data to its likely `"true"` limits. Pass ``False`` to disable.',
|
|
40
|
+
"ignore_filters": "Set to ignore filtering parameters (e.g. `step`) and global configuration.",
|
|
31
41
|
"USER_GUIDE": (
|
|
32
|
-
"For more information about the `schedule`, `before/after` and `
|
|
42
|
+
"For more information about the `schedule`, `before/after` and `expand_limits`-arguments"
|
|
43
|
+
", see the :ref:`User guide`."
|
|
33
44
|
),
|
|
34
45
|
"OFFSET": _OFFSET,
|
|
35
46
|
"DatetimeIndexSplitterKwargs": "See :func:`~time_split.split`. The `available` keyword is managed by the integration.",
|
|
47
|
+
"show_removed": "If ``True``, splits removed by `n_splits` or `step` are included in the figure.",
|
|
36
48
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from ._plot import plot
|
|
2
2
|
from ._progress import default_metrics_formatter, log_split_progress
|
|
3
3
|
from ._split import split
|
|
4
|
-
from ._to_string import to_string
|
|
4
|
+
from ._to_string import format_expanded_limits, to_string
|
|
5
5
|
from ._weight import fold_weight
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
@@ -11,4 +11,5 @@ __all__ = [
|
|
|
11
11
|
"plot",
|
|
12
12
|
"split",
|
|
13
13
|
"to_string",
|
|
14
|
+
"format_expanded_limits",
|
|
14
15
|
]
|