time-split 0.3.0__tar.gz → 0.4.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.4.0}/PKG-INFO +2 -2
- {time_split-0.3.0 → time_split-0.4.0}/README.md +1 -1
- {time_split-0.3.0 → time_split-0.4.0}/pyproject.toml +2 -1
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/__init__.py +1 -1
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_backend/_limits.py +26 -22
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_backend/_process_available.py +7 -5
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_backend/_schedule.py +3 -3
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_backend/_splitter.py +7 -6
- time_split-0.4.0/src/time_split/_compat.py +49 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_docstrings.py +18 -8
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_frontend/_plot.py +8 -7
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_frontend/_progress.py +3 -2
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_frontend/_split.py +4 -4
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_frontend/_to_string.py +1 -1
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/sklearn/_impl.py +4 -4
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/settings.py +7 -7
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/types.py +3 -3
- {time_split-0.3.0 → time_split-0.4.0}/LICENSE.md +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_backend/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_backend/_datetime_index_like.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_backend/_span.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_frontend/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_frontend/_weight.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/_support.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/_log_progress.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/base.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/pandas/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/pandas/_impl.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/polars/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/polars/_impl.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/integration/sklearn/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/py.typed +0 -0
- {time_split-0.3.0 → time_split-0.4.0}/src/time_split/support/__init__.py +0 -0
- {time_split-0.3.0 → time_split-0.4.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.4.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
|
|
@@ -58,7 +58,7 @@ strong non-stationary characteristics.
|
|
|
58
58
|
a CRON-expression, or a pre-defined list.
|
|
59
59
|
- [Data selection](https://time-split.readthedocs.io/en/stable/guide/spans.html) based on a timedelta, or the splitting
|
|
60
60
|
schedule itself.
|
|
61
|
-
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/
|
|
61
|
+
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/expand_limits.html) data limits for
|
|
62
62
|
supported data types.
|
|
63
63
|
- [Plotting function](https://time-split.readthedocs.io/en/stable/generated/time_split.html#time_split.plot) for
|
|
64
64
|
visualization of folds. Display fold sizes (hour/row count), or
|
|
@@ -27,7 +27,7 @@ strong non-stationary characteristics.
|
|
|
27
27
|
a CRON-expression, or a pre-defined list.
|
|
28
28
|
- [Data selection](https://time-split.readthedocs.io/en/stable/guide/spans.html) based on a timedelta, or the splitting
|
|
29
29
|
schedule itself.
|
|
30
|
-
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/
|
|
30
|
+
- Automatically extract and [normalize](https://time-split.readthedocs.io/en/stable/guide/expand_limits.html) data limits for
|
|
31
31
|
supported data types.
|
|
32
32
|
- [Plotting function](https://time-split.readthedocs.io/en/stable/generated/time_split.html#time_split.plot) for
|
|
33
33
|
visualization of folds. Display fold sizes (hour/row count), or
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "time-split"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Time-based k-fold validation splits for heterogeneous data."
|
|
5
5
|
authors = ["Richard Sundqvist <richard.sundqvist@live.se>"]
|
|
6
6
|
|
|
@@ -48,6 +48,7 @@ plotting = ["matplotlib", "seaborn"]
|
|
|
48
48
|
croniter = "^2.0.3"
|
|
49
49
|
pyarrow = ">=15.0.2,<17.0.0"
|
|
50
50
|
polars = { version = "^0.20.19", extras = ["pandas"] }
|
|
51
|
+
numpy = "<2" # TODO: numpy 2.0
|
|
51
52
|
|
|
52
53
|
[tool.poetry.group.docs]
|
|
53
54
|
[tool.poetry.group.docs.dependencies]
|
|
@@ -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):
|
|
@@ -4,7 +4,8 @@ from typing import cast, get_args
|
|
|
4
4
|
|
|
5
5
|
from pandas import Timedelta, Timestamp
|
|
6
6
|
from rics.misc import format_kwargs
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from time_split._compat import fmt_sec
|
|
8
9
|
|
|
9
10
|
from ..settings import misc
|
|
10
11
|
from ..types import (
|
|
@@ -12,7 +13,7 @@ from ..types import (
|
|
|
12
13
|
DatetimeIterable,
|
|
13
14
|
DatetimeSplitBounds,
|
|
14
15
|
DatetimeSplits,
|
|
15
|
-
|
|
16
|
+
ExpandLimits,
|
|
16
17
|
Schedule,
|
|
17
18
|
Span,
|
|
18
19
|
TimedeltaTypes,
|
|
@@ -33,7 +34,7 @@ class DatetimeIndexSplitter:
|
|
|
33
34
|
after: Span
|
|
34
35
|
step: int
|
|
35
36
|
n_splits: int
|
|
36
|
-
|
|
37
|
+
expand_limits: ExpandLimits
|
|
37
38
|
|
|
38
39
|
def get_splits(self, available: DatetimeIterable | None = None) -> DatetimeSplits:
|
|
39
40
|
"""Compute a split of given user data."""
|
|
@@ -48,7 +49,7 @@ 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
|
|
|
@@ -137,10 +138,10 @@ class DatetimeIndexSplitter:
|
|
|
137
138
|
if old == new:
|
|
138
139
|
return retval + "<no change>"
|
|
139
140
|
diff = (new - old).total_seconds()
|
|
140
|
-
return retval + f"{_PrettyTimestamp(new).auto} ({'+' if diff > 0 else '-'}{
|
|
141
|
+
return retval + f"{_PrettyTimestamp(new).auto} ({'+' if diff > 0 else '-'}{fmt_sec(abs(diff))})"
|
|
141
142
|
|
|
142
143
|
LOGGER.info(
|
|
143
|
-
f"Available data limits have been expanded (since
|
|
144
|
+
f"Available data limits have been expanded (since expand_limits={self.expand_limits!r}):\n"
|
|
144
145
|
f" start: {stringify(original[0], new=expanded[0])}\n"
|
|
145
146
|
f" end: {stringify(original[1], new=expanded[1])}"
|
|
146
147
|
)
|
|
@@ -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,9 +36,10 @@ _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.',
|
|
31
40
|
"USER_GUIDE": (
|
|
32
|
-
"For more information about the `schedule`, `before/after` and `
|
|
41
|
+
"For more information about the `schedule`, `before/after` and `expand_limits`-arguments"
|
|
42
|
+
", see the :ref:`User guide`."
|
|
33
43
|
),
|
|
34
44
|
"OFFSET": _OFFSET,
|
|
35
45
|
"DatetimeIndexSplitterKwargs": "See :func:`~time_split.split`. The `available` keyword is managed by the integration.",
|
|
@@ -4,7 +4,8 @@ from typing import TYPE_CHECKING, Any, Literal
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from pandas import Timestamp
|
|
6
6
|
from rics.misc import format_kwargs, get_public_module
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from time_split._compat import fmt_sec
|
|
8
9
|
|
|
9
10
|
from .._backend import DatetimeIndexSplitter
|
|
10
11
|
from .._backend._datetime_index_like import DatetimeIndexLike
|
|
@@ -16,7 +17,7 @@ from ..types import (
|
|
|
16
17
|
DatetimeIterable,
|
|
17
18
|
DatetimeSplitBounds,
|
|
18
19
|
DatetimeSplits,
|
|
19
|
-
|
|
20
|
+
ExpandLimits,
|
|
20
21
|
Schedule,
|
|
21
22
|
Span,
|
|
22
23
|
)
|
|
@@ -64,7 +65,7 @@ def plot(
|
|
|
64
65
|
step: int = 1,
|
|
65
66
|
n_splits: int = 0,
|
|
66
67
|
available: DatetimeIterable | None = None,
|
|
67
|
-
|
|
68
|
+
expand_limits: ExpandLimits = "auto",
|
|
68
69
|
# Split plot args
|
|
69
70
|
bar_labels: str | Rows | list[tuple[str, str]] | bool = True,
|
|
70
71
|
show_removed: bool = False,
|
|
@@ -84,7 +85,7 @@ def plot(
|
|
|
84
85
|
n_splits: {n_splits}
|
|
85
86
|
available: {available} If `bar_labels` is given but is not a ``list``,
|
|
86
87
|
this data will be used to compute fold sizes.
|
|
87
|
-
|
|
88
|
+
expand_limits: {expand_limits}
|
|
88
89
|
bar_labels: Labels to draw on the bars. If you pass a string, it will be interpreted as a time unit (see
|
|
89
90
|
:ref:`pandas:timeseries.offset_aliases` for valid frequency strings). Bars will show the number of units
|
|
90
91
|
contained. Pass `'rows'` to simply count the numbers of elements in `data` (if given). To write custom
|
|
@@ -114,7 +115,7 @@ def plot(
|
|
|
114
115
|
after=after,
|
|
115
116
|
step=step,
|
|
116
117
|
n_splits=n_splits,
|
|
117
|
-
|
|
118
|
+
expand_limits=expand_limits,
|
|
118
119
|
)
|
|
119
120
|
plot_data = _get_plot_data(available, splitter, row_count_bin=row_count_bin, show_removed=show_removed)
|
|
120
121
|
|
|
@@ -207,10 +208,10 @@ def _plot_row_counts(ax: "Axes", row_count_bin: str | pd.Series, row_counts: pd.
|
|
|
207
208
|
from numpy import diff, timedelta64
|
|
208
209
|
|
|
209
210
|
row_counts = row_count_bin.sort_index()
|
|
210
|
-
pretty =
|
|
211
|
+
pretty = fmt_sec(diff(row_counts.index).min() / timedelta64(1, "s"))
|
|
211
212
|
else:
|
|
212
213
|
row_counts = row_counts.sort_index()
|
|
213
|
-
pretty =
|
|
214
|
+
pretty = fmt_sec(pd.Timedelta(row_count_bin).total_seconds())
|
|
214
215
|
|
|
215
216
|
row_counts = row_counts * (max(ax.get_yticks()) / row_counts.max()) # Normalize to fold number yaxis
|
|
216
217
|
ax.fill_between(
|
|
@@ -6,7 +6,8 @@ from typing import Any, Generic
|
|
|
6
6
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
from rics.collections.dicts import flatten_dict
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
from time_split._compat import fmt_sec
|
|
10
11
|
|
|
11
12
|
from ..settings import log_split_progress as settings
|
|
12
13
|
from ..types import DatetimeSplitBounds, FormatMetrics, GetMetrics, LoggerArg, MetricsType, SplitProgressExtras
|
|
@@ -96,7 +97,7 @@ def log_split_progress(
|
|
|
96
97
|
start_message=settings.START_MESSAGE,
|
|
97
98
|
end_level=end_level,
|
|
98
99
|
end_message=settings.END_MESSAGE,
|
|
99
|
-
seconds_formatter=settings.SECONDS_FORMATTER or
|
|
100
|
+
seconds_formatter=settings.SECONDS_FORMATTER or fmt_sec,
|
|
100
101
|
user_extra={} if extra is None else extra.copy(), # Not actually immutable; deepcopy can be very expensive.
|
|
101
102
|
get_metrics=get_metrics,
|
|
102
103
|
format_metrics=settings.FORMAT_METRICS or default_metrics_formatter,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .._backend import DatetimeIndexSplitter
|
|
2
2
|
from .._docstrings import docs
|
|
3
|
-
from ..types import DatetimeIterable, DatetimeSplits,
|
|
3
|
+
from ..types import DatetimeIterable, DatetimeSplits, ExpandLimits, Schedule, Span
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
@docs
|
|
@@ -12,7 +12,7 @@ def split(
|
|
|
12
12
|
step: int = 1,
|
|
13
13
|
n_splits: int = 0,
|
|
14
14
|
available: DatetimeIterable | None = None,
|
|
15
|
-
|
|
15
|
+
expand_limits: ExpandLimits = "auto",
|
|
16
16
|
) -> DatetimeSplits:
|
|
17
17
|
"""Create time-based cross-validation splits.
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ def split(
|
|
|
25
25
|
step: {step}
|
|
26
26
|
n_splits: {n_splits}
|
|
27
27
|
available: {available} Passing a tuple ``(min, max)`` is enough.
|
|
28
|
-
|
|
28
|
+
expand_limits: {expand_limits}
|
|
29
29
|
|
|
30
30
|
{USER_GUIDE}
|
|
31
31
|
|
|
@@ -41,5 +41,5 @@ def split(
|
|
|
41
41
|
after=after,
|
|
42
42
|
step=step,
|
|
43
43
|
n_splits=n_splits,
|
|
44
|
-
|
|
44
|
+
expand_limits=expand_limits,
|
|
45
45
|
).get_splits(available)
|
|
@@ -29,7 +29,7 @@ def to_string(
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def to_string(
|
|
32
|
-
bounds:
|
|
32
|
+
bounds: DatetimeTypes | DatetimeSplitBounds | tuple[DatetimeTypes, DatetimeTypes, DatetimeTypes],
|
|
33
33
|
mid: DatetimeTypes | None = None,
|
|
34
34
|
end: DatetimeTypes | None = None,
|
|
35
35
|
/,
|
|
@@ -10,7 +10,7 @@ from ...types import (
|
|
|
10
10
|
DatetimeIterable,
|
|
11
11
|
DatetimeSplits,
|
|
12
12
|
DatetimeTypes,
|
|
13
|
-
|
|
13
|
+
ExpandLimits,
|
|
14
14
|
MetricsType,
|
|
15
15
|
Schedule,
|
|
16
16
|
Span,
|
|
@@ -44,7 +44,7 @@ class ScikitLearnSplitter(BaseCrossValidator): # type: ignore[misc]
|
|
|
44
44
|
after: {after}
|
|
45
45
|
step: {step}
|
|
46
46
|
n_splits: {n_splits}
|
|
47
|
-
|
|
47
|
+
expand_limits: {expand_limits}
|
|
48
48
|
log_progress: {log_progress}
|
|
49
49
|
verify_xy: If ``True``, split X and y independently and verify that they are equal.
|
|
50
50
|
|
|
@@ -59,7 +59,7 @@ class ScikitLearnSplitter(BaseCrossValidator): # type: ignore[misc]
|
|
|
59
59
|
before: Span = "7d",
|
|
60
60
|
after: Span = 1,
|
|
61
61
|
n_splits: int = 0,
|
|
62
|
-
|
|
62
|
+
expand_limits: ExpandLimits = "auto",
|
|
63
63
|
step: int = 1,
|
|
64
64
|
log_progress: LogProgressArg[MetricsType] = False,
|
|
65
65
|
verify_xy: bool = True,
|
|
@@ -71,7 +71,7 @@ class ScikitLearnSplitter(BaseCrossValidator): # type: ignore[misc]
|
|
|
71
71
|
after=after,
|
|
72
72
|
step=step,
|
|
73
73
|
n_splits=n_splits,
|
|
74
|
-
|
|
74
|
+
expand_limits=expand_limits,
|
|
75
75
|
)
|
|
76
76
|
self.log_progress = log_progress
|
|
77
77
|
self.verify_xy = verify_xy
|
|
@@ -5,14 +5,14 @@ import typing as _t
|
|
|
5
5
|
from . import types as _tst
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
9
|
-
"""Configuration for the `'auto'` :attr:`~time_split.types.
|
|
8
|
+
class auto_expand_limits: # noqa: N801
|
|
9
|
+
"""Configuration for the `'auto'` :attr:`~time_split.types.ExpandLimits` logic.
|
|
10
10
|
|
|
11
|
-
This class determines how ``(min, max)``-tuples are expanded when
|
|
11
|
+
This class determines how ``(min, max)``-tuples are expanded when expand_limitsible bounds are enabled.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
SANITY_CHECK: bool = True
|
|
15
|
-
"""If ``True``, use original limits if
|
|
15
|
+
"""If ``True``, use original limits if expand_limitsed limits do not pass sanity checks."""
|
|
16
16
|
|
|
17
17
|
@classmethod
|
|
18
18
|
def set_level(
|
|
@@ -23,7 +23,7 @@ class auto_flex: # noqa: N801
|
|
|
23
23
|
round_to: _tst.TimedeltaTypes,
|
|
24
24
|
tolerance: _tst.TimedeltaTypes,
|
|
25
25
|
) -> None:
|
|
26
|
-
"""Set a :class:`Level` used by the auto-
|
|
26
|
+
"""Set a :class:`Level` used by the auto-expand_limits logic.
|
|
27
27
|
|
|
28
28
|
Inputs are not verified until an actual split is made.
|
|
29
29
|
|
|
@@ -43,7 +43,7 @@ class auto_flex: # noqa: N801
|
|
|
43
43
|
setattr(cls, level, level_tuple)
|
|
44
44
|
|
|
45
45
|
class Level(_t.NamedTuple):
|
|
46
|
-
"""Level type used by :attr:`
|
|
46
|
+
"""Level type used by :attr:`auto_expand_limits`."""
|
|
47
47
|
|
|
48
48
|
start_at: _tst.TimedeltaTypes
|
|
49
49
|
"""Span size at which this level starts."""
|
|
@@ -114,7 +114,7 @@ class log_split_progress: # noqa: N801
|
|
|
114
114
|
"""
|
|
115
115
|
|
|
116
116
|
SECONDS_FORMATTER: _t.Callable[[float], str] | None = None
|
|
117
|
-
"""A callable ``(seconds) -> formatted_seconds``. Use :func:`rics.
|
|
117
|
+
"""A callable ``(seconds) -> formatted_seconds``. Use :func:`rics.strings.format_seconds` if ``None``.
|
|
118
118
|
|
|
119
119
|
Both ``seconds`` and ``formatted_seconds`` will be available to the :attr:`END_MESSAGE` message.
|
|
120
120
|
"""
|
|
@@ -18,8 +18,8 @@ Schedule: _t.TypeAlias = _pd.DatetimeIndex | DatetimeIterable | TimedeltaTypes
|
|
|
18
18
|
"""User schedule type."""
|
|
19
19
|
Span = int | _t.Literal["all"] | TimedeltaTypes
|
|
20
20
|
"""User span type. Used to determine limits from the timestamps given by a :attr:`Schedule`."""
|
|
21
|
-
|
|
22
|
-
"""
|
|
21
|
+
ExpandLimits = bool | _t.Literal["auto"] | str
|
|
22
|
+
"""Limits flexibility spec for ``floor/ceil``. Pass ``False`` to disable."""
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class DatetimeSplitBounds(_t.NamedTuple):
|
|
@@ -62,7 +62,7 @@ class DatetimeIndexSplitterKwargs(_t.TypedDict, total=False):
|
|
|
62
62
|
after: Span
|
|
63
63
|
step: int
|
|
64
64
|
n_splits: int
|
|
65
|
-
|
|
65
|
+
expand_limits: ExpandLimits
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
LoggerArg = _logging.Logger | _logging.LoggerAdapter[_t.Any] | str
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|