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.
Files changed (65) hide show
  1. {time_split-0.3.0 → time_split-0.5.0}/PKG-INFO +25 -5
  2. {time_split-0.3.0 → time_split-0.5.0}/README.md +18 -1
  3. {time_split-0.3.0 → time_split-0.5.0}/pyproject.toml +14 -7
  4. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/__init__.py +1 -1
  5. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_limits.py +26 -22
  6. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_process_available.py +7 -5
  7. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_schedule.py +3 -3
  8. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_splitter.py +26 -29
  9. time_split-0.5.0/src/time_split/_compat.py +49 -0
  10. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_docstrings.py +20 -8
  11. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/__init__.py +2 -1
  12. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_plot.py +35 -14
  13. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_progress.py +3 -2
  14. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_split.py +7 -4
  15. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_to_string.py +51 -2
  16. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/sklearn/_impl.py +5 -24
  17. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/settings.py +14 -9
  18. time_split-0.5.0/src/time_split/streamlit/README.md +82 -0
  19. time_split-0.5.0/src/time_split/streamlit/__init__.py +1 -0
  20. time_split-0.5.0/src/time_split/streamlit/_logging.py +75 -0
  21. time_split-0.5.0/src/time_split/streamlit/_support.py +67 -0
  22. time_split-0.5.0/src/time_split/streamlit/_views.py +125 -0
  23. time_split-0.5.0/src/time_split/streamlit/config.py +68 -0
  24. time_split-0.5.0/src/time_split/streamlit/widgets/__init__.py +1 -0
  25. time_split-0.5.0/src/time_split/streamlit/widgets/data/__init__.py +7 -0
  26. time_split-0.5.0/src/time_split/streamlit/widgets/data/_data.py +336 -0
  27. time_split-0.5.0/src/time_split/streamlit/widgets/data/_datasets.py +123 -0
  28. time_split-0.5.0/src/time_split/streamlit/widgets/data/_sample_data.py +147 -0
  29. time_split-0.5.0/src/time_split/streamlit/widgets/data/load.py +111 -0
  30. time_split-0.5.0/src/time_split/streamlit/widgets/data/sample.csv +397 -0
  31. time_split-0.5.0/src/time_split/streamlit/widgets/display/__init__.py +15 -0
  32. time_split-0.5.0/src/time_split/streamlit/widgets/display/_aggregate.py +227 -0
  33. time_split-0.5.0/src/time_split/streamlit/widgets/display/_code.py +175 -0
  34. time_split-0.5.0/src/time_split/streamlit/widgets/display/_overview.py +51 -0
  35. time_split-0.5.0/src/time_split/streamlit/widgets/display/_performance.py +176 -0
  36. time_split-0.5.0/src/time_split/streamlit/widgets/display/_plot_folds.py +92 -0
  37. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/__init__.py +21 -0
  38. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_datetime.py +30 -0
  39. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_duration.py +78 -0
  40. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_expand_limits.py +141 -0
  41. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_schedule.py +135 -0
  42. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_schedule_filter.py +43 -0
  43. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_span.py +122 -0
  44. time_split-0.5.0/src/time_split/streamlit/widgets/parameters/_splitter_kwargs.py +29 -0
  45. time_split-0.5.0/src/time_split/streamlit/widgets/ruff.toml +6 -0
  46. time_split-0.5.0/src/time_split/streamlit/widgets/types.py +351 -0
  47. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/support/__init__.py +4 -1
  48. time_split-0.5.0/src/time_split/support/_create_explorer_link.py +118 -0
  49. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/types.py +3 -3
  50. {time_split-0.3.0 → time_split-0.5.0}/LICENSE.md +0 -0
  51. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/__init__.py +0 -0
  52. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_datetime_index_like.py +0 -0
  53. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_backend/_span.py +0 -0
  54. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_frontend/_weight.py +0 -0
  55. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/_support.py +0 -0
  56. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/__init__.py +0 -0
  57. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/_log_progress.py +0 -0
  58. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/base.py +0 -0
  59. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/pandas/__init__.py +0 -0
  60. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/pandas/_impl.py +0 -0
  61. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/polars/__init__.py +0 -0
  62. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/polars/_impl.py +0 -0
  63. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/integration/sklearn/__init__.py +0 -0
  64. {time_split-0.3.0 → time_split-0.5.0}/src/time_split/py.typed +0 -0
  65. {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.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
- Requires-Dist: matplotlib ; extra == "plotting"
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 (>=3.1.0)
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
  [![Codecov](https://codecov.io/gh/rsundqvist/time-split/branch/master/graph/badge.svg)](https://codecov.io/gh/rsundqvist/time-split)
40
43
  [![Read the Docs](https://readthedocs.org/projects/time-split/badge/)](https://time-split.readthedocs.io/)
41
44
  [![PyPI - License](https://img.shields.io/pypi/l/time-split.svg)](https://pypi.python.org/pypi/time-split)
45
+ [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/rsundqvist/time-split/latest?logo=docker&label=time-split)](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/flex.html) data limits for
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
+ [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/rsundqvist/time-split/latest?logo=docker&label=time-split)](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
  [![Codecov](https://codecov.io/gh/rsundqvist/time-split/branch/master/graph/badge.svg)](https://codecov.io/gh/rsundqvist/time-split)
9
9
  [![Read the Docs](https://readthedocs.org/projects/time-split/badge/)](https://time-split.readthedocs.io/)
10
10
  [![PyPI - License](https://img.shields.io/pypi/l/time-split.svg)](https://pypi.python.org/pypi/time-split)
11
+ [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/rsundqvist/time-split/latest?logo=docker&label=time-split)](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/flex.html) data limits for
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
+ [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/rsundqvist/time-split/latest?logo=docker&label=time-split)](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.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
- 'Programming Language :: Python :: 3 :: Only',
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 = ">=3.1.0"
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.9.0"
88
- ruff = ">=0.3.7,<0.5.0"
93
+ mypy = "^1.10.1"
94
+ ruff = "^0.5.0"
89
95
 
90
- safety = "^3.1.0"
91
- codespell = "^2.2.6"
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/*" = [
@@ -11,6 +11,6 @@ __all__ = [
11
11
  "__version__", # Make MyPy happy
12
12
  ]
13
13
 
14
- __version__ = "0.3.0"
14
+ __version__ = "0.5.0"
15
15
 
16
16
  _logging.getLogger(__name__).addHandler(_logging.NullHandler())
@@ -4,8 +4,8 @@ from typing import NamedTuple
4
4
 
5
5
  from pandas import Timedelta, Timestamp
6
6
 
7
- from ..settings import auto_flex
8
- from ..types import Flex, TimedeltaTypes
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(limits: LimitsTuple, *, flex: Flex | LevelTuple | Iterable[LevelTuple] = "auto") -> LimitsTuple:
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
- flex: Flex arguments as described in the :ref:`User guide`. Also supports level-tuples
26
- ``[(start_at, round_to, tolerance)...]``. Passing ``flex=[settings.auto_flex.day, settings.auto_flex.hour]``
27
- is equivalent to ``flex='auto'``.
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 `flex`-argument.
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, flex="d")
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, flex="d<1h")
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, flex="d")
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, flex="d<14h")
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 flex is False:
69
+ if spec is False:
66
70
  return limits
67
71
 
68
- if flex is True or flex == "auto":
72
+ if spec is True or spec == "auto":
69
73
  return _from_levels(limits)
70
74
 
71
- if isinstance(flex, str):
72
- round_to, _, tolerance = flex.partition("<")
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(flex, tuple):
76
- return _from_levels(limits, levels=[_make_level(*flex)])
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, flex))
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 auto_flex.SANITY_CHECK:
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(*auto_flex.day), _make_level(*auto_flex.hour)
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: {auto_flex.day=} must have round_to=1 day."
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: {auto_flex.hour=} must have round_to=1 hour."
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, Flex
6
+ from ..types import DatetimeIterable, ExpandLimits
7
7
  from ._datetime_index_like import DatetimeIndexLike
8
- from ._limits import LimitsTuple, expand_limits
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, *, flex: Flex) -> ProcessAvailableResult:
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
- flex: Flex-argument. Determines how much (if at all) to expand limits.
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 = expand_limits(limits, flex=flex)
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, Flex, Schedule, TimedeltaTypes
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, flex: Flex, *, available: DatetimeIterable | None = None
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, flex=flex)
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, Timestamp
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
- Flex,
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
- flex: Flex
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.flex, available=available)
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
- misc.snap_to_end
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
- return splits
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
- if original == expanded:
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
- def stringify(old: Timestamp, *, new: Timestamp) -> str:
134
- from .._frontend._to_string import _PrettyTimestamp
136
+ message = format_expanded_limits(original, expanded=expanded, expand_limits=self.expand_limits)
137
+ self._emit_once(message)
135
138
 
136
- retval = f"{old} -> "
137
- if old == new:
138
- return retval + "<no change>"
139
- diff = (new - old).total_seconds()
140
- return retval + f"{_PrettyTimestamp(new).auto} ({'+' if diff > 0 else '-'}{format_seconds(abs(diff))})"
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
- FuncType = TypeVar("FuncType") # No proper way to type this?
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(function: FuncType) -> FuncType:
14
+ def docs(__func: Callable[P, T], /) -> Callable[P, T]:
12
15
  """Add info the docstring of a function."""
13
- assert function.__doc__ is not None # noqa: S101
14
- function.__doc__ = function.__doc__.format(**_DOCSTRINGS)
15
- return function
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
- "flex": f'A {_OFFSET} used to expand `available` data to its likely `"true"` limits. Pass ``False`` to disable.',
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 `flex`-arguments" ", see the :ref:`User guide`."
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
  ]