time-split 1.0.5__tar.gz → 1.1.1__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 (39) hide show
  1. {time_split-1.0.5 → time_split-1.1.1}/PKG-INFO +1 -1
  2. {time_split-1.0.5 → time_split-1.1.1}/pyproject.toml +1 -1
  3. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/__init__.py +1 -1
  4. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_backend/_schedule.py +17 -4
  5. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_backend/_splitter.py +8 -7
  6. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/app/_create_explorer_link.py +2 -2
  7. {time_split-1.0.5 → time_split-1.1.1}/LICENSE.md +0 -0
  8. {time_split-1.0.5 → time_split-1.1.1}/README.md +0 -0
  9. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/__main__.py +0 -0
  10. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_backend/__init__.py +0 -0
  11. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_backend/_datetime_index_like.py +0 -0
  12. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_backend/_limits.py +0 -0
  13. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_backend/_process_available.py +0 -0
  14. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_backend/_span.py +0 -0
  15. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_docstrings.py +0 -0
  16. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_frontend/__init__.py +0 -0
  17. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_frontend/_plot.py +0 -0
  18. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_frontend/_progress.py +0 -0
  19. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_frontend/_split.py +0 -0
  20. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_frontend/_to_string.py +0 -0
  21. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_frontend/_weight.py +0 -0
  22. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/_support.py +0 -0
  23. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/app/__init__.py +0 -0
  24. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/app/reexport/__init__.py +0 -0
  25. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/cli.py +0 -0
  26. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/__init__.py +0 -0
  27. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/_log_progress.py +0 -0
  28. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/base.py +0 -0
  29. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/pandas/__init__.py +0 -0
  30. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/pandas/_impl.py +0 -0
  31. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/polars/__init__.py +0 -0
  32. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/polars/_impl.py +0 -0
  33. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/sklearn/__init__.py +0 -0
  34. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/integration/sklearn/_impl.py +0 -0
  35. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/py.typed +0 -0
  36. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/settings.py +0 -0
  37. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/support/__init__.py +0 -0
  38. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/support/types.py +0 -0
  39. {time_split-1.0.5 → time_split-1.1.1}/src/time_split/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: time-split
3
- Version: 1.0.5
3
+ Version: 1.1.1
4
4
  Summary: Time-based k-fold validation splits for heterogeneous data.
5
5
  License: MIT
6
6
  License-File: LICENSE.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "time-split"
3
- version = "1.0.5"
3
+ version = "1.1.1"
4
4
  description = "Time-based k-fold validation splits for heterogeneous data."
5
5
  authors = [
6
6
  { name = "Richard Sundqvist", email = "richard.sundqvist@live.se" },
@@ -9,4 +9,4 @@ __all__ = [
9
9
  "split",
10
10
  ]
11
11
 
12
- __version__ = "1.0.5"
12
+ __version__ = "1.1.1"
@@ -1,12 +1,14 @@
1
- from typing import NamedTuple, get_args
1
+ from typing import Literal, NamedTuple, get_args
2
2
 
3
3
  from pandas import DatetimeIndex, NaT, Timedelta, Timestamp, date_range
4
4
 
5
+ from ..settings import misc as settings
5
6
  from ..types import DatetimeIterable, ExpandLimits, Schedule, TimedeltaTypes
6
7
  from ._limits import LimitsTuple
7
8
  from ._process_available import ProcessAvailableResult, process_available
8
9
 
9
10
  NO_LIMITS: LimitsTuple = NaT, NaT
11
+ ScheduleType = Literal["cron", "explicit", "timedelta"]
10
12
 
11
13
 
12
14
  class MaterializedSchedule(NamedTuple):
@@ -14,6 +16,7 @@ class MaterializedSchedule(NamedTuple):
14
16
 
15
17
  schedule: DatetimeIndex
16
18
  available_metadata: ProcessAvailableResult
19
+ schedule_type: ScheduleType
17
20
 
18
21
 
19
22
  def materialize_schedule(
@@ -25,6 +28,7 @@ def materialize_schedule(
25
28
  return MaterializedSchedule(
26
29
  DatetimeIndex(schedule),
27
30
  available_metadata=ProcessAvailableResult(None, NO_LIMITS, NO_LIMITS),
31
+ schedule_type="explicit",
28
32
  )
29
33
  except TypeError as e:
30
34
  raise ValueError("Schedule must be explicit when not bounded by an available range.") from e
@@ -32,21 +36,26 @@ def materialize_schedule(
32
36
  available_metadata = process_available(available, expand_limits=expand_limits)
33
37
  min_dt, max_dt = available_metadata.expanded_limits
34
38
 
39
+ schedule_type: ScheduleType
35
40
  if isinstance(schedule, str) and _cron_like(schedule):
36
41
  schedule = _handle_cron(schedule, min_dt, max_dt)
42
+ schedule_type = "cron"
37
43
  elif isinstance(schedule, get_args(TimedeltaTypes)):
38
44
  schedule = _from_timedelta(schedule, available_metadata.expanded_limits)
45
+ schedule_type = "timedelta"
39
46
  else:
40
47
  if not isinstance(schedule, DatetimeIndex):
41
48
  schedule = DatetimeIndex(schedule)
42
49
 
43
50
  schedule = schedule.tz_localize(min_dt.tz) if schedule.tz is None else schedule.tz_convert(min_dt.tz)
44
51
  schedule = schedule[(min_dt <= schedule) & (schedule <= max_dt)]
45
- return MaterializedSchedule(schedule, available_metadata=available_metadata)
52
+ schedule_type = "explicit"
53
+
54
+ return MaterializedSchedule(schedule, available_metadata, schedule_type)
46
55
 
47
56
 
48
57
  def _cron_like(schedule: str) -> bool:
49
- # CroniterBadCronError: Exactly 5 or 6 columns has to be specified for iterator expression.
58
+ # CroniterBadCronError: Exactly 5 or 6 columns have to be specified for iterator expression.
50
59
  return len(schedule.split()) > 4 or schedule[0] == "@" # noqa: PLR2004
51
60
 
52
61
 
@@ -54,7 +63,11 @@ def _from_timedelta(schedule: TimedeltaTypes, limits: LimitsTuple) -> DatetimeIn
54
63
  timedelta = Timedelta(schedule)
55
64
  if timedelta <= Timedelta(0):
56
65
  raise ValueError(f"unbounded {schedule=} must be greater than zero.")
57
- return date_range(*limits, freq=timedelta, inclusive="both")
66
+
67
+ if settings.snap_to_end:
68
+ return date_range(limits[1], limits[0], freq=-timedelta, inclusive="both")[::-1]
69
+ else:
70
+ return date_range(limits[0], limits[1], freq=timedelta, inclusive="both")
58
71
 
59
72
 
60
73
  def _handle_cron(expr: str, min_dt: Timestamp, max_dt: Timestamp) -> DatetimeIndex:
@@ -53,20 +53,21 @@ class DatetimeIndexSplitter:
53
53
  if (
54
54
  settings.snap_to_end
55
55
  and self.after != "all"
56
- and isinstance(self.schedule, types)
57
- and isinstance(self.after, types)
56
+ and ms.schedule_type == "timedelta"
57
+ and isinstance(self.after, (types, int))
58
58
  ):
59
59
  ms = self._snap_to_end(ms)
60
60
 
61
61
  return ms
62
62
 
63
63
  def _snap_to_end(self, ms: MaterializedSchedule) -> MaterializedSchedule:
64
- schedule_frequency = ms.schedule.freq
65
- if schedule_frequency is None:
66
- return ms
64
+ data_end = ms.available_metadata.expanded_limits[1]
65
+ schedule_end = ms.schedule[-1]
67
66
 
68
- from_end = ms.available_metadata.expanded_limits[1] - (ms.schedule[-1] + Timedelta(self.after))
69
- from_end = from_end.floor(schedule_frequency.base) # TODO(settings): should this depend on misc.round_limits?
67
+ from_end = data_end - schedule_end
68
+ if not isinstance(self.after, int):
69
+ after = Timedelta(self.after)
70
+ from_end -= after
70
71
 
71
72
  return ms._replace(schedule=ms.schedule + from_end)
72
73
 
@@ -99,8 +99,8 @@ def _create_query(
99
99
  query = {"data": data, **kwargs}
100
100
  else:
101
101
  splitter = DatetimeIndexSplitter(**kwargs)
102
- _, ms = splitter.get_plot_data(data)
103
- query["data"] = _get_data_param(*ms.available_metadata.limits)
102
+ start, end = splitter._materialize_schedule(data).available_metadata.limits
103
+ query["data"] = _get_data_param(start, end)
104
104
 
105
105
  if skip_default:
106
106
  schedule = kwargs["schedule"]
File without changes
File without changes