foldaudit 0.2.5__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.
- foldaudit-0.2.5/LICENSE +21 -0
- foldaudit-0.2.5/MANIFEST.in +3 -0
- foldaudit-0.2.5/PKG-INFO +437 -0
- foldaudit-0.2.5/README.md +422 -0
- foldaudit-0.2.5/examples/demo.py +31 -0
- foldaudit-0.2.5/pyproject.toml +30 -0
- foldaudit-0.2.5/setup.cfg +4 -0
- foldaudit-0.2.5/src/foldaudit/__init__.py +23 -0
- foldaudit-0.2.5/src/foldaudit/core.py +1036 -0
- foldaudit-0.2.5/src/foldaudit/py.typed +0 -0
- foldaudit-0.2.5/src/foldaudit/report.py +249 -0
- foldaudit-0.2.5/src/foldaudit.egg-info/PKG-INFO +437 -0
- foldaudit-0.2.5/src/foldaudit.egg-info/SOURCES.txt +14 -0
- foldaudit-0.2.5/src/foldaudit.egg-info/dependency_links.txt +1 -0
- foldaudit-0.2.5/src/foldaudit.egg-info/top_level.txt +1 -0
- foldaudit-0.2.5/tests/test_foldaudit.py +997 -0
foldaudit-0.2.5/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anas Riaz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
foldaudit-0.2.5/PKG-INFO
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foldaudit
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Check that your cross-validation folds are fair and usable before you trust the score.
|
|
5
|
+
Author: Anas Riaz
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: cross-validation,machine-learning,evaluation,scikit-learn
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# foldaudit
|
|
17
|
+
|
|
18
|
+
**Name:** foldaudit
|
|
19
|
+
**One sentence:** It checks that your cross-validation folds are fair
|
|
20
|
+
and usable before you trust the score.
|
|
21
|
+
|
|
22
|
+
## The problem
|
|
23
|
+
|
|
24
|
+
When you run cross-validation, you get scores. You do not get any
|
|
25
|
+
information about the folds those scores came from. A fold can be
|
|
26
|
+
broken in ways that make its score meaningless: it contains only one
|
|
27
|
+
class, it is far too small, its class balance is nothing like your
|
|
28
|
+
full dataset, or, in a time-series split, training data comes from
|
|
29
|
+
after the test data. Scikit-learn's own issue tracker documents
|
|
30
|
+
degenerate folds of exactly this kind, for example StratifiedGroupKFold
|
|
31
|
+
producing empty or unusable splits, while `cross_val_score` only
|
|
32
|
+
returns scores and never inspects the folds themselves. Almost nobody
|
|
33
|
+
inspects every fold's indices by hand. foldaudit does that inspection
|
|
34
|
+
for you, before you train anything.
|
|
35
|
+
|
|
36
|
+
## What it is
|
|
37
|
+
|
|
38
|
+
A small, pure-Python library, version 0.2.5. No third-party
|
|
39
|
+
dependencies, no model training, no plots, no GPU, no paid APIs.
|
|
40
|
+
It works with scikit-learn splitters and without them: give it a
|
|
41
|
+
splitter with a `.split()` method, or a plain list of
|
|
42
|
+
`(train_idx, test_idx)` pairs.
|
|
43
|
+
|
|
44
|
+
## Installation (once published)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install foldaudit
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Local use from this folder, without installing:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
PYTHONPATH=src python -c "from foldaudit import audit; print('ok')"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from foldaudit import audit
|
|
61
|
+
|
|
62
|
+
# With a splitter (KFold, StratifiedKFold, GroupKFold,
|
|
63
|
+
# TimeSeriesSplit, or any object with .split())
|
|
64
|
+
report = audit(cv, X, y, groups=groups, time=dates)
|
|
65
|
+
|
|
66
|
+
# Without scikit-learn: plain index pairs
|
|
67
|
+
report = audit([(train_idx, test_idx), ...], y=y)
|
|
68
|
+
|
|
69
|
+
print(report.text) # pasteable plain-text report
|
|
70
|
+
print(report.to_json()) # JSON, for saving or for CI
|
|
71
|
+
report.raise_if_fail() # raises ValueError on a FAIL verdict
|
|
72
|
+
# report.raise_if_fail(fail_on_warning=True) # also fails on WARNING
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What it checks (version 0.2.5)
|
|
76
|
+
|
|
77
|
+
1. **Per-fold table.** Train/test sizes, class counts and rates,
|
|
78
|
+
group counts, and time min/max per fold.
|
|
79
|
+
2. **Validity flags.** Empty train/test, single-class train/test,
|
|
80
|
+
a class missing from a test fold, a test fold smaller than
|
|
81
|
+
the minimum size, a row appearing in both train and test of the
|
|
82
|
+
same fold (FAIL, since 0.2.1), a duplicated index inside
|
|
83
|
+
one side of a fold (WARNING, since 0.2.1), and a group
|
|
84
|
+
appearing on both sides of a fold (FAIL, since 0.2.2).
|
|
85
|
+
3. **Stratification and balance.** A fold's class rate compared
|
|
86
|
+
with the overall rate, and a fold's test size compared with the
|
|
87
|
+
median test size. Thresholds are configurable; the report always
|
|
88
|
+
quotes the actual numbers.
|
|
89
|
+
4. **Coverage.** How many times each row is tested, rows never
|
|
90
|
+
tested, and overlapping test folds (reported, not failed, since
|
|
91
|
+
repeated or shuffled splitters overlap by design).
|
|
92
|
+
5. **Report export.** Plain text and JSON, with an overall verdict
|
|
93
|
+
of OK, WARNING, or FAIL, and an optional fail-on-warning hook
|
|
94
|
+
for CI.
|
|
95
|
+
|
|
96
|
+
Additional checks: time order (train times must precede test
|
|
97
|
+
times, otherwise FAIL), group counts per fold, and group
|
|
98
|
+
overlap (a group on both sides of a fold is group leakage, FAIL,
|
|
99
|
+
since 0.2.2). Version 0.2.0
|
|
100
|
+
adds the rare-class ratio check: a fold's class rate more than 2x
|
|
101
|
+
(or less than 1/2x) the overall rate warns, even when the absolute
|
|
102
|
+
gap is under 20 points, provided the gap is at least 5 points and
|
|
103
|
+
the class appears in the test fold.
|
|
104
|
+
|
|
105
|
+
## Thresholds (defaults, all configurable)
|
|
106
|
+
|
|
107
|
+
| Setting | Default | Meaning |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| `class_rate_deviation` | 0.20 | Warn when a fold's class rate differs from the overall rate by more than 20 percentage points. |
|
|
110
|
+
| `class_rate_ratio` | 2.0 | Warn when a fold's class rate is more than this many times the overall rate, or less than 1/this of it (rare-class check; 5-point absolute floor applies). |
|
|
111
|
+
| `min_test_size` | 10 | Warn when a test fold has fewer rows than this. |
|
|
112
|
+
| `fold_size_deviation` | 0.50 | Warn when a test fold's size differs from the median test size by more than 50%. |
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from foldaudit import Thresholds, audit
|
|
116
|
+
|
|
117
|
+
report = audit(cv, X, y, thresholds=Thresholds(
|
|
118
|
+
class_rate_deviation=0.10,
|
|
119
|
+
min_test_size=20,
|
|
120
|
+
))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
These are documented heuristics, not universal truths. Change them
|
|
124
|
+
to fit your problem.
|
|
125
|
+
|
|
126
|
+
## Example output
|
|
127
|
+
|
|
128
|
+
A degenerate split, where each test fold contains only one class:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
foldaudit verdict: FAIL
|
|
132
|
+
|
|
133
|
+
Fold Train Test Test class rates Verdict
|
|
134
|
+
0 50 50 0=100% FAIL
|
|
135
|
+
1 50 50 1=100% FAIL
|
|
136
|
+
|
|
137
|
+
Flags:
|
|
138
|
+
[FAIL] fold 0: Train set has a single class: 1.
|
|
139
|
+
[FAIL] fold 0: Test set has a single class: 0.
|
|
140
|
+
...
|
|
141
|
+
|
|
142
|
+
Since 0.2.0 the missing-class and rate-deviation warnings for a
|
|
143
|
+
single-class fold are suppressed: they were the same fact reported
|
|
144
|
+
three times. One bad fold now reports two flags, not five.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A healthy balanced split reports `foldaudit verdict: OK` with no flags.
|
|
148
|
+
|
|
149
|
+
## What it deliberately will NOT do
|
|
150
|
+
|
|
151
|
+
- No model training or scores.
|
|
152
|
+
- No new splitters, and no purged or embargoed splits.
|
|
153
|
+
- No semantic train/test leakage detection beyond group labels
|
|
154
|
+
(duplicated rows, feature or target leakage — that is a
|
|
155
|
+
separate tool's job). A literal index appearing in both train
|
|
156
|
+
and test of one fold IS flagged, since 0.2.1, and a group label
|
|
157
|
+
appearing on both sides IS flagged, since 0.2.2: both are basic
|
|
158
|
+
fold validity, not semantic leakage analysis.
|
|
159
|
+
- No plots, drift detection, slice or fairness metrics, and no
|
|
160
|
+
experiment tracking.
|
|
161
|
+
- No automatic "fixing" of folds. It tells you what is wrong so
|
|
162
|
+
you can choose a better splitter yourself.
|
|
163
|
+
|
|
164
|
+
## Project layout
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
fold-audit/
|
|
168
|
+
pyproject.toml
|
|
169
|
+
README.md
|
|
170
|
+
src/
|
|
171
|
+
foldaudit/
|
|
172
|
+
__init__.py # public API: audit, Thresholds, Report, ...
|
|
173
|
+
core.py # audit logic, flags, coverage, time, groups
|
|
174
|
+
report.py # FoldResult, Flag, Report, text/JSON rendering
|
|
175
|
+
tests/
|
|
176
|
+
test_foldaudit.py # 72 tests, standard library only
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Tests and demo
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
python -m pytest tests -q
|
|
183
|
+
PYTHONPATH=src python examples/demo.py
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
72 tests pass. They use fake splitters made of plain index lists,
|
|
187
|
+
so they need no scikit-learn, pandas, or any other dependency.
|
|
188
|
+
Ruff check and ruff format check are clean. A wheel builds as
|
|
189
|
+
`foldaudit-0.2.5-py3-none-any.whl` and installs and imports as
|
|
190
|
+
version 0.2.5.
|
|
191
|
+
|
|
192
|
+
## What changed in 0.2.0 (fixes for the 2 November 2026 problems report)
|
|
193
|
+
|
|
194
|
+
All five reported problems are fixed, with a regression test each:
|
|
195
|
+
|
|
196
|
+
1. **Rare-class blind spot:** new `class_rate_ratio_deviation` check
|
|
197
|
+
(see above). The report's own example — 2% overall, 15% in a fold —
|
|
198
|
+
now warns at 7.5x.
|
|
199
|
+
2. **Out-of-range indices:** a bad index now raises
|
|
200
|
+
`ValueError: fold 0 train set contains index 3, but only 3 samples
|
|
201
|
+
were provided`, instead of a bare `IndexError` from inside the
|
|
202
|
+
counting loop. Negative and non-integer indices are rejected too.
|
|
203
|
+
3. **Report noise:** a single-class test fold now reports only the
|
|
204
|
+
single-class FAILs. The missing-class and rate-deviation warnings
|
|
205
|
+
for that fold were consequences of the same fact, so they are
|
|
206
|
+
suppressed. The degenerate demo fold went from 5 flags to 2.
|
|
207
|
+
4. **Small-fold check without labels:** `small_test` no longer lives
|
|
208
|
+
inside the labels block, so auditing without `y` still warns on a
|
|
209
|
+
1-row test fold. An empty test fold gets `empty_test` only.
|
|
210
|
+
5. **TimeSeriesSplit verdict:** with a `time` column supplied,
|
|
211
|
+
`never_tested` is INFO, not WARNING, because the earliest rows
|
|
212
|
+
are train-only by design. Without `time`, it stays WARNING.
|
|
213
|
+
|
|
214
|
+
## What changed in 0.2.1 (fix for the 0.2.0 problems report)
|
|
215
|
+
|
|
216
|
+
Problem 6, the only open item from that report, is fixed:
|
|
217
|
+
|
|
218
|
+
- **Train/test overlap within a fold (FAIL):** a row in both sides
|
|
219
|
+
of one fold now raises `train_test_overlap`, quoting the count,
|
|
220
|
+
e.g. 10 rows in the report's example. Previously that split
|
|
221
|
+
reported only an unrelated `never_tested` warning.
|
|
222
|
+
- **Duplicate index inside one side (WARNING):** a test fold like
|
|
223
|
+
`[2, 2, 3]` is flagged as `duplicate_indices` instead of being
|
|
224
|
+
silently counted twice. Indices are counted as given, not
|
|
225
|
+
silently deduplicated.
|
|
226
|
+
- **Polish:** the sdist now includes `examples/` via
|
|
227
|
+
`MANIFEST.in`. No `[project.urls]` Homepage/Repository was
|
|
228
|
+
added, because no GitHub repo exists yet — adding a guessed
|
|
229
|
+
URL would be worse than none.
|
|
230
|
+
|
|
231
|
+
## What changed in 0.2.2 (complete fix list, F1-F8)
|
|
232
|
+
|
|
233
|
+
Only the items in that list were changed:
|
|
234
|
+
|
|
235
|
+
- **F1 group overlap (FAIL):** a group label on both sides of a
|
|
236
|
+
fold now raises `group_overlap`. Counts are unchanged. No other
|
|
237
|
+
leakage detection was added.
|
|
238
|
+
- **F2 Thresholds validation:** invalid values now raise
|
|
239
|
+
`ValueError` naming the field (`class_rate_deviation` outside
|
|
240
|
+
[0, 1], `class_rate_ratio` below 1.0, `min_test_size` below 0,
|
|
241
|
+
`fold_size_deviation` below 0). Defaults and field names are
|
|
242
|
+
unchanged.
|
|
243
|
+
- **F3 boolean index:** a boolean index now raises the same
|
|
244
|
+
non-integer `ValueError` as 2.5. Ordinary ints still pass.
|
|
245
|
+
- **F4 ratio formatting:** ratios now print with two significant
|
|
246
|
+
digits, so 0.25 prints as "(0.25x)" and 7.5 still as "(7.5x)".
|
|
247
|
+
- **F5 text table:** the rates column is now as wide as the
|
|
248
|
+
longest rates string (minimum 40, plus one gap character), so a
|
|
249
|
+
12-class report no longer collides with its verdict. The demo
|
|
250
|
+
output is byte-identical to 0.2.1.
|
|
251
|
+
- **F6 README:** the PyPI-name block was removed; the dated check
|
|
252
|
+
is recorded once, in Blunt status below.
|
|
253
|
+
- **F7 sdist:** verified to list `examples/demo.py`; the
|
|
254
|
+
`MANIFEST.in` that already provided it is unchanged.
|
|
255
|
+
- **F8 [project.urls]:** skipped, because no GitHub repo exists
|
|
256
|
+
yet. No URL was invented or guessed.
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
## What changed in 0.2.4 (audit FA-001 to FA-030)
|
|
260
|
+
|
|
261
|
+
All 30 issues from the 0.2.2 audit are fixed in this version, with
|
|
262
|
+
a regression test each (30 new tests, 66 in total). This is a fresh
|
|
263
|
+
build on 0.2.2; it is not 0.2.3, and no 0.2.3 code was reused.
|
|
264
|
+
|
|
265
|
+
- **FA-001/002/003 splitter routing:** splitter arguments are now
|
|
266
|
+
dispatched by parameter name. A parameter named `groups` gets
|
|
267
|
+
groups, `time`/`times` gets time, keyword-only parameters are
|
|
268
|
+
passed by keyword, and a `*args` wrapper or an unavailable
|
|
269
|
+
signature falls back to `split(X, y, groups)`. An unknown
|
|
270
|
+
required parameter raises a clear error instead of receiving
|
|
271
|
+
the wrong object.
|
|
272
|
+
- **FA-004 missing/infinite labels:** rejected with ValueError
|
|
273
|
+
naming the position, as scikit-learn does. NaN labels no longer
|
|
274
|
+
become one phantom class per row.
|
|
275
|
+
- **FA-005 missing groups:** normalised to one missing group and
|
|
276
|
+
warned about as `missing_group_ids`, with the count. All-missing
|
|
277
|
+
groups therefore also trigger `group_overlap`, instead of
|
|
278
|
+
silently passing.
|
|
279
|
+
- **FA-006 missing times:** warned about as `missing_time` and
|
|
280
|
+
excluded from min/max, so the time-order verdict no longer
|
|
281
|
+
depends on index order.
|
|
282
|
+
- **FA-007 Thresholds:** frozen dataclass; NaN, infinite and
|
|
283
|
+
non-numeric values are rejected naming the field. `audit()`
|
|
284
|
+
also rejects a non-Thresholds thresholds object.
|
|
285
|
+
- **FA-008 input validation:** 2-D, mapping, string and unhashable
|
|
286
|
+
y/groups/time values raise ValueError naming the argument and
|
|
287
|
+
the required 1-D hashable shape.
|
|
288
|
+
- **FA-009/010 sample sizes:** sample count uses `shape[0]`
|
|
289
|
+
first, so sparse X works, and X length is checked against
|
|
290
|
+
y/groups/time and raises ValueError on mismatch.
|
|
291
|
+
- **FA-011 fold iterables:** any iterable of pairs is accepted,
|
|
292
|
+
a single `(train, test)` pair is one fold, strings raise
|
|
293
|
+
TypeError, and malformed folds raise ValueError naming the fold.
|
|
294
|
+
- **FA-012/013 JSON:** class tables in JSON are now records
|
|
295
|
+
(`label`, `repr`, `count`/`rate`), so labels `1` and `"1"` or
|
|
296
|
+
`None` and `"null"` cannot collide, numpy scalars, tuples,
|
|
297
|
+
datetimes, bytes and Decimal labels cannot crash export, and
|
|
298
|
+
`to_json()` uses `allow_nan=False` so no bare NaN/Infinity
|
|
299
|
+
token is ever emitted. This changes `to_dict()`/`to_json()`
|
|
300
|
+
class-table shape from dicts to lists; the Python
|
|
301
|
+
`FoldResult` attributes are unchanged dicts.
|
|
302
|
+
- **FA-014 datetime64 times:** numpy datetime64 ints are
|
|
303
|
+
converted to ISO strings (NaT becomes missing). Month/year
|
|
304
|
+
datetime64 units are left as-is.
|
|
305
|
+
- **FA-015 continuous targets:** a float target with more than
|
|
306
|
+
half its values unique warns as `continuous_target` and skips
|
|
307
|
+
class checks, instead of flooding the report. This detection
|
|
308
|
+
is float-only, as specified in the registry.
|
|
309
|
+
- **FA-016 text table:** every column width is computed from
|
|
310
|
+
the data, so large sizes and fold numbers cannot run together.
|
|
311
|
+
- **FA-017 median:** fold-size imbalance now uses the true
|
|
312
|
+
median (`statistics.median`), not the upper middle element.
|
|
313
|
+
- **FA-018 boundary ties (author decision):** equal train-max
|
|
314
|
+
and test-min timestamps warn as `time_tie_at_boundary`
|
|
315
|
+
(WARNING), not FAIL and not a silent pass. A tie may be one
|
|
316
|
+
batch, but it may also hide leakage, so it must be visible.
|
|
317
|
+
- **FA-019 missing train classes:** mirrored as
|
|
318
|
+
`missing_class_train`, suppressed only when the train side
|
|
319
|
+
itself is single-class (the same P3 rule as for test).
|
|
320
|
+
- **FA-020 single hold-out:** `never_tested` is INFO, not
|
|
321
|
+
WARNING, for a single fold, as it already was with time.
|
|
322
|
+
- **FA-021 group examples:** distinct overlapping groups are
|
|
323
|
+
shown, deduplicated before slicing to three.
|
|
324
|
+
- **FA-022 message formatting:** rates under 1% print with
|
|
325
|
+
two decimal places (never "0%"), and ratios never print in
|
|
326
|
+
scientific notation.
|
|
327
|
+
- **FA-023 exact thresholds:** class-rate decisions use exact
|
|
328
|
+
Fraction arithmetic from the decimal threshold, so exactly
|
|
329
|
+
20 points does not warn and exactly 5 points at 2.5x does.
|
|
330
|
+
- **FA-024 performance:** folds are streamed when sample data
|
|
331
|
+
is given (materialisation only remains when the sample count
|
|
332
|
+
must itself be inferred from the folds), and pairwise test
|
|
333
|
+
overlap uses bitmasks for up to 250,000 rows instead of
|
|
334
|
+
Python set intersections. Pairwise checking is still O(k^2)
|
|
335
|
+
in the number of folds, in C bit operations rather than
|
|
336
|
+
Python set operations; it is not claimed to be linear.
|
|
337
|
+
- **FA-025 per-row test counts:** `Report.test_counts` now
|
|
338
|
+
exposes the count for every row, including 0 for never-tested
|
|
339
|
+
rows, matching what the README coverage section promised.
|
|
340
|
+
- **FA-026 pooled schemes:** 1-row test folds, and test folds
|
|
341
|
+
that are exactly one group absent from train, downgrade
|
|
342
|
+
`single_class_test` and `small_test` to INFO instead of FAIL.
|
|
343
|
+
- **FA-027 string times:** rejected with ValueError; string
|
|
344
|
+
times are never compared lexicographically.
|
|
345
|
+
- **FA-028 packaging:** SPDX `license = "MIT"` with
|
|
346
|
+
license-files, and a `py.typed` marker is shipped. The old
|
|
347
|
+
`License :: OSI Approved :: MIT License` classifier was
|
|
348
|
+
removed because modern setuptools (PEP 639) forbids it
|
|
349
|
+
alongside an SPDX license expression; this is metadata
|
|
350
|
+
only, no code change.
|
|
351
|
+
- **FA-029 scope:** every text report ends with a Scope
|
|
352
|
+
footer stating what is checked and what is not checked,
|
|
353
|
+
and `to_dict()`/`to_json()` carry the same `scope` field.
|
|
354
|
+
- **FA-030 test suite:** 30 new regression tests, one per
|
|
355
|
+
bug, all standard-library only. The auditor's 28
|
|
356
|
+
sklearn/numpy/pandas regression tests are not copied into
|
|
357
|
+
the shipped suite (it must run without dependencies), but
|
|
358
|
+
equivalent stdlib tests cover the same behaviours; the
|
|
359
|
+
mutation score was not re-measured in this build.
|
|
360
|
+
|
|
361
|
+
Not fixed / still limited: no project is claimed
|
|
362
|
+
production-ready or leakage-free. Semantic leakage (duplicate
|
|
363
|
+
rows, feature/target leakage, preprocessing leakage) is still
|
|
364
|
+
out of scope by design and is now stated in every report.
|
|
365
|
+
|
|
366
|
+
## What changed in 0.2.5 (problems report P-001 to P-006)
|
|
367
|
+
|
|
368
|
+
All six problems from the 0.2.4 problems report are fixed, with a
|
|
369
|
+
regression test each (6 new tests, 72 in total). No new
|
|
370
|
+
dependencies, no API or parameter renames, no removed features.
|
|
371
|
+
|
|
372
|
+
- **P-001 tuple labels:** hashable tuple labels are now accepted
|
|
373
|
+
as atomic class labels. Unhashable nested values (lists, dicts,
|
|
374
|
+
sets, or tuples containing unhashable values) are still rejected.
|
|
375
|
+
- **P-002 generator time:** a generator `time` input is
|
|
376
|
+
materialised once at the start of `audit()` and reused for
|
|
377
|
+
validation and auditing, instead of being consumed by the
|
|
378
|
+
string-time validation pass.
|
|
379
|
+
- **P-003 small rates:** the text table now uses the adaptive
|
|
380
|
+
percentage formatter, so a 0.5% test class rate prints as
|
|
381
|
+
"0.50%", never as "0%".
|
|
382
|
+
- **P-004 fold verdict:** the per-fold table verdict is derived
|
|
383
|
+
from severity, FAIL > WARNING > INFO > OK, so an INFO-only
|
|
384
|
+
fold (e.g. a pooled leave-one-out fold) displays INFO, not
|
|
385
|
+
WARNING. The overall verdict rule is unchanged.
|
|
386
|
+
- **P-005 splitter routing:** positional slots already consumed
|
|
387
|
+
are tracked, and recognised named arguments after a skipped
|
|
388
|
+
defaulted slot are passed by keyword, so e.g.
|
|
389
|
+
`split(X, unused=None, groups=None)` no longer receives
|
|
390
|
+
groups in `unused`.
|
|
391
|
+
- **P-006 numpy pair:** a single `(train, test)` pair given as
|
|
392
|
+
two 1-D numpy integer arrays is now recognised as one fold,
|
|
393
|
+
like the list/tuple pair form. The shipped tests use a
|
|
394
|
+
stdlib fake array with the same shape/tolist interface, so
|
|
395
|
+
the suite still runs without numpy.
|
|
396
|
+
|
|
397
|
+
## Blunt status: done vs not done
|
|
398
|
+
|
|
399
|
+
Done: the core audit, the version 0.1 checks plus the 0.2.0
|
|
400
|
+
rare-class ratio check, the 0.2.1 overlap/duplicate checks, the
|
|
401
|
+
0.2.2 group-overlap check, and the 0.2.4 FA-001 to FA-030 fixes, the 0.2.5 P-001 to P-006 fixes,
|
|
402
|
+
text and JSON reports, configurable thresholds, 72 tests, a
|
|
403
|
+
runnable demo, a clean ruff check and ruff format check, and a
|
|
404
|
+
buildable wheel and sdist that pass twine check.
|
|
405
|
+
Not done: not published on PyPI, not proven to be wanted by
|
|
406
|
+
anyone, and no GitHub repo exists yet, so no Homepage link is
|
|
407
|
+
set. The PyPI name "foldaudit" was checked free on PyPI on
|
|
408
|
+
2 November 2026 and remains unpublished. Real scikit-learn
|
|
409
|
+
integration has now been tested successfully: by the auditor's
|
|
410
|
+
regression suite (StratifiedKFold OK, degenerate KFold FAIL,
|
|
411
|
+
GroupKFold counts correct), and in a real-data test on NYC
|
|
412
|
+
Yellow Taxi, January 2024 (first 1,500,000 rows of 2,964,624
|
|
413
|
+
trips; label payment_type, groups 252 pickup zones, time
|
|
414
|
+
pickup timestamp) — StratifiedKFold(5) OK in 8.9s, KFold(5)
|
|
415
|
+
with zones as groups FAIL in 12.6s (233–237 of 252 zones
|
|
416
|
+
leaking into train and test in every fold), GroupKFold(5)
|
|
417
|
+
by zone OK in 12.2s with no group or class-balance false
|
|
418
|
+
alarms, and KFold (no shuffle) on time-sorted data with
|
|
419
|
+
time FAIL in 12.2s, including a 2002-12-31 timestamp flagged
|
|
420
|
+
in January 2024 data. That real-data test was reported by
|
|
421
|
+
Anas on 2 November 2026 and was not run in this environment,
|
|
422
|
+
where scikit-learn is still not installed; the shipped test
|
|
423
|
+
suite itself remains standard-library only by design.
|
|
424
|
+
|
|
425
|
+
## Risks and honest limits
|
|
426
|
+
|
|
427
|
+
- Demand is indirect: scikit-learn issues document degenerate
|
|
428
|
+
folds, but nobody was found asking for a "fold audit library"
|
|
429
|
+
by name, so popularity is unproven.
|
|
430
|
+
- The library must earn its existence through the full checklist,
|
|
431
|
+
severity levels, and report, not just a counts loop.
|
|
432
|
+
- Scikit-learn could one day add fold warnings itself; foldaudit
|
|
433
|
+
stays splitter-agnostic to survive that.
|
|
434
|
+
|
|
435
|
+
## License
|
|
436
|
+
|
|
437
|
+
MIT.
|