tpcp 3.2.0__tar.gz → 3.3.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 (41) hide show
  1. {tpcp-3.2.0 → tpcp-3.3.0}/PKG-INFO +1 -1
  2. {tpcp-3.2.0 → tpcp-3.3.0}/pyproject.toml +1 -1
  3. {tpcp-3.2.0 → tpcp-3.3.0}/pyproject.toml.orig +1 -1
  4. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/__init__.py +1 -1
  5. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/validate/__init__.py +2 -1
  6. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/validate/_composed_splitters.py +86 -33
  7. {tpcp-3.2.0 → tpcp-3.3.0}/README.md +0 -0
  8. {tpcp-3.2.0 → tpcp-3.3.0}/skills/tpcp/tpcp-basics/SKILL.md +0 -0
  9. {tpcp-3.2.0 → tpcp-3.3.0}/skills/tpcp/tpcp-builder/SKILL.md +0 -0
  10. {tpcp-3.2.0 → tpcp-3.3.0}/skills/tpcp/tpcp-datasets/SKILL.md +0 -0
  11. {tpcp-3.2.0 → tpcp-3.3.0}/skills/tpcp/tpcp-multiprocessing/SKILL.md +0 -0
  12. {tpcp-3.2.0 → tpcp-3.3.0}/skills/tpcp/tpcp-optimization/SKILL.md +0 -0
  13. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_algorithm.py +0 -0
  14. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_algorithm_utils.py +0 -0
  15. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_base.py +0 -0
  16. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_cli.py +0 -0
  17. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_dataset.py +0 -0
  18. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_hash.py +0 -0
  19. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_optimize.py +0 -0
  20. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_parameters.py +0 -0
  21. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_pipeline.py +0 -0
  22. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_utils/__init__.py +0 -0
  23. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_utils/_general.py +0 -0
  24. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/_utils/_score.py +0 -0
  25. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/caching.py +0 -0
  26. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/exceptions.py +0 -0
  27. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/misc/__init__.py +0 -0
  28. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/misc/_class_utils.py +0 -0
  29. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/misc/_typed_iterator.py +0 -0
  30. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/misc/_warning_error_context.py +0 -0
  31. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/optimize/__init__.py +0 -0
  32. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/optimize/_optimize.py +0 -0
  33. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/optimize/optuna.py +0 -0
  34. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/parallel.py +0 -0
  35. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/testing/__init__.py +0 -0
  36. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/testing/_algorithm_test_mixin.py +0 -0
  37. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/testing/_regression_utils.py +0 -0
  38. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/types.py +0 -0
  39. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/validate/_cross_val_helper.py +0 -0
  40. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/validate/_scorer.py +0 -0
  41. {tpcp-3.2.0 → tpcp-3.3.0}/src/tpcp/validate/_validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tpcp
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Pipeline and Dataset helpers for complex algorithm evaluation.
5
5
  Author: Arne Küderle, Robert Richer, Raul C. Sîmpetru, Björn Eskofier
6
6
  Author-email: Arne Küderle <arne.kuederle@fau.de>, Robert Richer <robert.richer@fau.de>, Raul C. Sîmpetru <raul.simpetru@fau.de>, Björn Eskofier <bjoern.eskofier@fau.de>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tpcp"
3
- version = "3.2.0"
3
+ version = "3.3.0"
4
4
  description = "Pipeline and Dataset helpers for complex algorithm evaluation."
5
5
  requires-python = ">=3.11,<4.0"
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tpcp"
3
- version = "3.2.0"
3
+ version = "3.3.0"
4
4
  description = "Pipeline and Dataset helpers for complex algorithm evaluation."
5
5
  authors = [
6
6
  { name = "Arne Küderle", email = "arne.kuederle@fau.de" },
@@ -24,7 +24,7 @@ from tpcp._parameters import (
24
24
  )
25
25
  from tpcp._pipeline import OptimizablePipeline, Pipeline
26
26
 
27
- __version__ = "3.2.0"
27
+ __version__ = "3.3.0"
28
28
 
29
29
 
30
30
  __all__ = [
@@ -1,6 +1,6 @@
1
1
  """Module for all helper methods to evaluate algorithms."""
2
2
 
3
- from tpcp.validate._composed_splitters import CombinedSplitter, NoSplit
3
+ from tpcp.validate._composed_splitters import CombinedSplitter, NoSplit, SubsetSplitter
4
4
  from tpcp.validate._cross_val_helper import BaseDatasetSplitter, DatasetSplitter
5
5
  from tpcp.validate._scorer import (
6
6
  Aggregator,
@@ -23,6 +23,7 @@ __all__ = [
23
23
  "NoSplit",
24
24
  "Scorer",
25
25
  "ScorerTypes",
26
+ "SubsetSplitter",
26
27
  "cross_validate",
27
28
  "mean_agg",
28
29
  "no_agg",
@@ -71,61 +71,115 @@ class NoSplit(BaseDatasetSplitter):
71
71
  yield train.copy(), test.copy()
72
72
 
73
73
 
74
+ class SubsetSplitter(BaseDatasetSplitter):
75
+ """Select a subset of the current dataset, then split it into train/test group labels.
76
+
77
+ This is a dataset splitter itself, usable directly as ``cv`` or as a named child of
78
+ :class:`CombinedSplitter`. The selector is evaluated for each ``get_n_splits`` or ``split``
79
+ call, so it should return the same subset for the same input when the calls are paired.
80
+
81
+ Parameters
82
+ ----------
83
+ selector
84
+ Callable receiving the current dataset and returning a subset whose group labels belong to it.
85
+ splitter
86
+ A native tpcp splitter or any input accepted by :class:`DatasetSplitter`, including raw sklearn
87
+ splitters and explicit lists of positional folds. Raw inputs are adapted to group labels
88
+ when used. Pass :class:`DatasetSplitter` explicitly when grouping or stratification by
89
+ dataset index columns is needed.
90
+ """
91
+
92
+ def __init__(self, selector: DatasetSelector, splitter: object) -> None:
93
+ self.selector = selector
94
+ self.splitter = splitter
95
+
96
+ def _prepare(self, dataset: Dataset) -> tuple[Dataset, BaseDatasetSplitter]:
97
+ selected = self.selector(dataset)
98
+ _validate_subset(dataset, selected)
99
+ return selected, _normalize_splitter(self.splitter)
100
+
101
+ def get_n_splits(self, dataset: Dataset) -> int | None:
102
+ """Return the child splitter's fold count for the selected subset."""
103
+ selected, splitter = self._prepare(dataset)
104
+ return splitter.get_n_splits(selected)
105
+
106
+ def split(
107
+ self, dataset: Dataset, n_splits: int | None = None
108
+ ) -> Iterator[tuple[list[GroupLabelT], list[GroupLabelT]]]:
109
+ """Select the subset and yield its train/test group labels."""
110
+ selected, splitter = self._prepare(dataset)
111
+ if n_splits is None:
112
+ yield from splitter.split(selected)
113
+ else:
114
+ yield from splitter.split(selected, n_splits=n_splits)
115
+
116
+
74
117
  class CombinedSplitter(BaseDatasetSplitter):
75
- """Combine corresponding folds of splitters applied to selected dataset parts.
118
+ """Combine corresponding folds of named dataset splitters.
76
119
 
77
- Pass a list of one or more ``(selector, splitter)`` pairs as ``parts``. Each selector receives the current
78
- dataset and returns a subset. A child splitter can be a native tpcp splitter or any input
79
- accepted by :class:`DatasetSplitter`, including raw sklearn splitters.
80
- Positional fold assignments must be supplied as lists, including for raw child inputs.
120
+ Pass a list of one or more ``(name, splitter)`` pairs as ``parts``, where each child implements
121
+ :class:`BaseDatasetSplitter`. Use :class:`SubsetSplitter` to apply a child to a selected subset.
122
+ Names are unique strings without ``__``, following tpcp's composite parameter convention.
123
+ All children receive the current input dataset. Raw sklearn splitters and positional fold lists
124
+ can be used inside :class:`SubsetSplitter` or :class:`DatasetSplitter`.
81
125
 
82
126
  All specified fold counts must match, and at least one child must specify a count.
83
127
  Children that report ``None`` receive that count through their ``split`` method.
84
128
  All children must yield exactly that many folds. Train and test labels are
85
129
  deduplicated separately in first-occurrence order, and overlapping assignments
86
- raise ``ValueError``. Selectors receive the current input dataset, including
87
- within nested splits, and may return any dataset whose group labels belong to
88
- that input.
130
+ raise ``ValueError``. Nested compositions operate on the dataset supplied by their parent.
89
131
 
90
132
  For example, cross-validate real recordings and always train on artificial recordings::
91
133
 
92
- from tpcp.validate import CombinedSplitter, DatasetSplitter, NoSplit
134
+ from tpcp.validate import (
135
+ CombinedSplitter,
136
+ DatasetSplitter,
137
+ NoSplit,
138
+ SubsetSplitter,
139
+ )
93
140
 
94
141
  cv = CombinedSplitter(
95
142
  parts=[
96
143
  (
97
- lambda ds: ds.get_subset(recording_type="real"),
98
- DatasetSplitter(5, groupby="participant"),
144
+ "real",
145
+ SubsetSplitter(
146
+ lambda ds: ds.get_subset(recording_type="real"),
147
+ DatasetSplitter(5, groupby="participant"),
148
+ ),
99
149
  ),
100
150
  (
101
- lambda ds: ds.get_subset(recording_type="artificial"),
102
- NoSplit(None, train=lambda ds: ds),
151
+ "artificial",
152
+ NoSplit(
153
+ None,
154
+ train=lambda ds: ds.get_subset(recording_type="artificial"),
155
+ ),
103
156
  ),
104
157
  ],
105
158
  )
106
159
 
160
+ cv.set_params(parts__real__splitter__base_splitter=3)
161
+
162
+ Replace a whole child with ``set_params(parts__real=new_splitter)``, or update its nested parameters
163
+ with paths such as ``parts__real__selector`` or ``parts__artificial__train``. Adding or removing parts
164
+ requires replacing the entire ``parts`` list.
165
+
107
166
  Parameters
108
167
  ----------
109
168
  parts
110
- A list of selector and splitter pairs, in the order their fold contributions are combined.
169
+ A composite parameter list of named :class:`BaseDatasetSplitter` objects, in the order their fold
170
+ contributions are combined. Migrate old ``(selector, splitter)`` entries to
171
+ ``(name, SubsetSplitter(selector, splitter))``.
111
172
  """
112
173
 
113
- def __init__(self, parts: list[tuple[DatasetSelector, object]]) -> None:
174
+ _composite_params = ("parts",)
175
+
176
+ def __init__(self, parts: list[tuple[str, BaseDatasetSplitter]]) -> None:
114
177
  self.parts = parts
115
178
 
116
- def _prepare(self, dataset: Dataset) -> list[tuple[Dataset, BaseDatasetSplitter]]:
179
+ def _fold_count(self, dataset: Dataset) -> tuple[int, list[int | None]]:
117
180
  if not self.parts:
118
- raise ValueError("CombinedSplitter requires at least one selector and splitter pair.")
119
- prepared = []
120
- for selector, splitter in self.parts:
121
- selected = selector(dataset)
122
- _validate_subset(dataset, selected)
123
- prepared.append((selected, _normalize_splitter(splitter)))
124
- return prepared
125
-
126
- @staticmethod
127
- def _fold_count(prepared: list[tuple[Dataset, BaseDatasetSplitter]]) -> tuple[int, list[int | None]]:
128
- reported = [splitter.get_n_splits(dataset) for dataset, splitter in prepared]
181
+ raise ValueError("CombinedSplitter requires at least one named dataset splitter.")
182
+ reported = [splitter.get_n_splits(dataset) for _, splitter in self.parts]
129
183
  counts = [count for count in reported if count is not None]
130
184
  if not counts:
131
185
  raise ValueError("CombinedSplitter requires at least one child to report an integer number of folds.")
@@ -134,19 +188,18 @@ class CombinedSplitter(BaseDatasetSplitter):
134
188
  return counts[0], reported
135
189
 
136
190
  def get_n_splits(self, dataset: Dataset) -> int:
137
- """Return the shared number of folds after selecting each part."""
138
- return self._fold_count(self._prepare(dataset))[0]
191
+ """Return the shared number of folds reported by the children."""
192
+ return self._fold_count(dataset)[0]
139
193
 
140
194
  def split(
141
195
  self, dataset: Dataset, n_splits: int | None = None
142
196
  ) -> Iterator[tuple[list[GroupLabelT], list[GroupLabelT]]]:
143
197
  """Yield deduplicated, disjoint train and test labels for corresponding child folds."""
144
- prepared = self._prepare(dataset)
145
- available, reported = self._fold_count(prepared)
198
+ available, reported = self._fold_count(dataset)
146
199
  count = _requested_fold_count(n_splits, available)
147
200
  iterators = [
148
- iter(splitter.split(selected, n_splits=count)) if child_count is None else iter(splitter.split(selected))
149
- for (selected, splitter), child_count in zip(prepared, reported, strict=True)
201
+ iter(splitter.split(dataset, n_splits=count)) if child_count is None else iter(splitter.split(dataset))
202
+ for (_, splitter), child_count in zip(self.parts, reported, strict=True)
150
203
  ]
151
204
  for _ in range(count):
152
205
  folds = []
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
File without changes
File without changes
File without changes
File without changes
File without changes