prefit 0.1.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.
- prefit-0.1.0/.gitignore +31 -0
- prefit-0.1.0/ARCHITECTURE.md +98 -0
- prefit-0.1.0/CHANGELOG.md +111 -0
- prefit-0.1.0/CONTRIBUTING.md +53 -0
- prefit-0.1.0/LICENSE +21 -0
- prefit-0.1.0/PKG-INFO +351 -0
- prefit-0.1.0/README.md +297 -0
- prefit-0.1.0/benchmarks/fixtures.py +203 -0
- prefit-0.1.0/benchmarks/run.py +127 -0
- prefit-0.1.0/prefit/__init__.py +23 -0
- prefit-0.1.0/prefit/_version.py +1 -0
- prefit-0.1.0/prefit/api.py +448 -0
- prefit-0.1.0/prefit/assemble.py +91 -0
- prefit-0.1.0/prefit/cli.py +61 -0
- prefit-0.1.0/prefit/config.py +39 -0
- prefit-0.1.0/prefit/data/track_record.json +57 -0
- prefit-0.1.0/prefit/enums.py +99 -0
- prefit-0.1.0/prefit/export.py +328 -0
- prefit-0.1.0/prefit/figures.py +305 -0
- prefit-0.1.0/prefit/fingerprint.py +92 -0
- prefit-0.1.0/prefit/frozen.py +70 -0
- prefit-0.1.0/prefit/guard.py +188 -0
- prefit-0.1.0/prefit/llm/__init__.py +4 -0
- prefit-0.1.0/prefit/llm/anthropic.py +39 -0
- prefit-0.1.0/prefit/llm/config.py +36 -0
- prefit-0.1.0/prefit/llm/gemini.py +37 -0
- prefit-0.1.0/prefit/llm/interpreter.py +162 -0
- prefit-0.1.0/prefit/llm/local.py +38 -0
- prefit-0.1.0/prefit/llm/payload.py +80 -0
- prefit-0.1.0/prefit/llm/provider.py +21 -0
- prefit-0.1.0/prefit/posttrain.py +394 -0
- prefit-0.1.0/prefit/profiling/__init__.py +0 -0
- prefit-0.1.0/prefit/profiling/kinds.py +82 -0
- prefit-0.1.0/prefit/profiling/profile.py +94 -0
- prefit-0.1.0/prefit/profiling/stats.py +236 -0
- prefit-0.1.0/prefit/profiling/tabular.py +227 -0
- prefit-0.1.0/prefit/profiling/task_inference.py +45 -0
- prefit-0.1.0/prefit/py.typed +0 -0
- prefit-0.1.0/prefit/render/__init__.py +0 -0
- prefit-0.1.0/prefit/render/common.py +172 -0
- prefit-0.1.0/prefit/render/html.py +484 -0
- prefit-0.1.0/prefit/render/tables.py +23 -0
- prefit-0.1.0/prefit/render/text.py +94 -0
- prefit-0.1.0/prefit/report/__init__.py +20 -0
- prefit-0.1.0/prefit/report/model.py +422 -0
- prefit-0.1.0/prefit/rules/__init__.py +0 -0
- prefit-0.1.0/prefit/rules/catalog.py +659 -0
- prefit-0.1.0/prefit/rules/engine.py +50 -0
- prefit-0.1.0/prefit/rules/metrics.py +140 -0
- prefit-0.1.0/prefit/rules/registry.py +165 -0
- prefit-0.1.0/prefit/verify.py +242 -0
- prefit-0.1.0/pyproject.toml +104 -0
- prefit-0.1.0/tests/conftest.py +43 -0
- prefit-0.1.0/tests/golden/datetime_column.json +102 -0
- prefit-0.1.0/tests/golden/high_missing.json +97 -0
- prefit-0.1.0/tests/golden/imbalanced.json +90 -0
- prefit-0.1.0/tests/golden/leaky_categorical_classification.json +90 -0
- prefit-0.1.0/tests/golden/skewed_target.json +95 -0
- prefit-0.1.0/tests/test_answers.py +61 -0
- prefit-0.1.0/tests/test_cli.py +12 -0
- prefit-0.1.0/tests/test_export.py +87 -0
- prefit-0.1.0/tests/test_guard.py +67 -0
- prefit-0.1.0/tests/test_llm.py +149 -0
- prefit-0.1.0/tests/test_partition.py +127 -0
- prefit-0.1.0/tests/test_profiling.py +63 -0
- prefit-0.1.0/tests/test_render.py +146 -0
- prefit-0.1.0/tests/test_review.py +64 -0
- prefit-0.1.0/tests/test_rules.py +167 -0
- prefit-0.1.0/tests/test_snapshot.py +36 -0
- prefit-0.1.0/tests/test_surface.py +112 -0
- prefit-0.1.0/tests/test_verify.py +111 -0
prefit-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# secrets / local env
|
|
2
|
+
.env
|
|
3
|
+
.venv/
|
|
4
|
+
venv/
|
|
5
|
+
|
|
6
|
+
# notebooks (scratch work; examples live in examples/*.py)
|
|
7
|
+
*.ipynb
|
|
8
|
+
.ipynb_checkpoints/
|
|
9
|
+
|
|
10
|
+
# python / tooling caches
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[cod]
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# build artefacts
|
|
20
|
+
dist/
|
|
21
|
+
build/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
|
|
27
|
+
# reports generated in the repo root (examples/output/ is tracked on purpose)
|
|
28
|
+
/*.html
|
|
29
|
+
|
|
30
|
+
# private v0 design notes (kept locally, not published)
|
|
31
|
+
ARCHITECTURE.v0.md
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
prefit shortens the pre-training analysis of a tabular supervised-learning
|
|
4
|
+
project. It measures the data, derives classified decisions from the
|
|
5
|
+
measurements, tests the decisions that can be tested, asks the user about the
|
|
6
|
+
ones that cannot, and exports only what is safe, confirmed or verified.
|
|
7
|
+
|
|
8
|
+
## Pipeline
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
pf.split(df, ...) -> dev_df, test_df (explicit; same splitter as holdout=)
|
|
12
|
+
dev_df, target, [estimator, groups, time_col, holdout, context, answers]
|
|
13
|
+
│
|
|
14
|
+
▼ guard.py ─ validate inputs (misspelled target, single-class target, groups+time…);
|
|
15
|
+
│ optional internal holdout (only with holdout=…), set aside before profiling
|
|
16
|
+
▼ fingerprint.py ─ SHA-256 digests of the input frame -> manifest (no data)
|
|
17
|
+
▼ profiling/ ─ dev rows only, seeded sample above ProfileOptions.max_rows
|
|
18
|
+
│ kinds.py column kind + reason (numeric, categorical, boolean,
|
|
19
|
+
│ datetime, text, id_like, constant)
|
|
20
|
+
│ stats.py per-kind stats, bootstrap / Wilson intervals,
|
|
21
|
+
│ single-feature held-out association score
|
|
22
|
+
│ tabular.py → DataProfile (aggregates only, JSON-serializable)
|
|
23
|
+
▼ rules/ ─ deterministic: DataProfile + RuleContext → RuleOutput
|
|
24
|
+
│ catalog.py versioned rules with thresholds; emit Evidence → Finding → Decision (+ Question)
|
|
25
|
+
│ metrics.py metric policy: one contextual decision driven by the cost of errors
|
|
26
|
+
▼ figures.py ─ decision-bound plotly specs (aggregates + color tokens)
|
|
27
|
+
▼ report/model.py ─ Report (decisions, questions, metric plan, roadmap,
|
|
28
|
+
│ experiments, baseline) + details (findings, evidence,
|
|
29
|
+
│ profile, figures, manifest, context, limitations, llm);
|
|
30
|
+
│ deeply immutable (frozen.py)
|
|
31
|
+
│
|
|
32
|
+
├─ Report.answer(...) re-runs the rules with new answers (no data needed)
|
|
33
|
+
├─ Report.verify(df) fingerprint check, then verify.py on dev rows
|
|
34
|
+
├─ Report.holdout(df) fingerprint check, re-derives the internal holdout
|
|
35
|
+
├─ Report.register_external_holdout() metadata + exact-overlap count, never profiled
|
|
36
|
+
├─ Report.export() export.py: model recipe + preprocessor + CV + scoring
|
|
37
|
+
├─ Report.interpret() llm/: optional, schema-constrained interpretation
|
|
38
|
+
├─ Report.to_html() render/: single-file report
|
|
39
|
+
└─ pf.review(...) posttrain.py: assumptions vs. actual errors
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Contracts
|
|
43
|
+
|
|
44
|
+
- **Evidence** — `id`, `label`, `value`, optional 95% `ci`, `method`, `columns`,
|
|
45
|
+
linked `figure_ids`.
|
|
46
|
+
- **Finding** — produced by one rule; `rule_id`, `rule_version`, `severity`,
|
|
47
|
+
`trigger` (`fired` | `uncertain`), `thresholds`, and at least one evidence id
|
|
48
|
+
(enforced by the model).
|
|
49
|
+
- **Decision** — `cls` (procedural / conditional / contextual / review),
|
|
50
|
+
`status`, `stage`, `action`, `rationale`, `risk`, optional `step` (the concrete
|
|
51
|
+
pipeline change), links to findings, evidence, figures, experiments, question.
|
|
52
|
+
- **Decision.eligible()** — the single rule for what enters the pipeline:
|
|
53
|
+
procedural+applied, contextual+confirmed, conditional+verified (or
|
|
54
|
+
supported_by_proxy when explicitly requested).
|
|
55
|
+
|
|
56
|
+
## Why these boundaries
|
|
57
|
+
|
|
58
|
+
1. **Thresholds are triggers.** A rule compares a statistic *and its interval*
|
|
59
|
+
to a threshold. Interval straddles the threshold → `uncertain`.
|
|
60
|
+
2. **Data-modifying actions are never procedural.** Dropping duplicates, rows
|
|
61
|
+
or columns can be a collection artefact or legitimate data; only the user or
|
|
62
|
+
an experiment can decide. The one exception is constant columns, which carry
|
|
63
|
+
no information. Raw datetime/text/string-id columns are left out of the
|
|
64
|
+
export because v1 cannot encode them — a capability limit reported as a
|
|
65
|
+
review item, not a judgement that they are uninformative.
|
|
66
|
+
3. **The data cannot choose the metric.** It can say which metrics mislead
|
|
67
|
+
(evidence-backed `avoid`); what to optimize depends on what errors cost.
|
|
68
|
+
4. **Experiments are bounded, and their inference matches the split.** One
|
|
69
|
+
change at a time against a fixed base, identical folds, hard budget. The
|
|
70
|
+
test follows the protocol: corrected repeated k-fold t-test (repeated or
|
|
71
|
+
grouped k-fold), no p-value for time-ordered folds. "Improved" needs
|
|
72
|
+
direction, Holm-adjusted significance and a practical effect threshold.
|
|
73
|
+
Proxy models give sensitivity analysis; only the user's estimator verifies.
|
|
74
|
+
5. **Split precedence is explicit.** time_col > groups > stratification;
|
|
75
|
+
time_col disables stratification (stated in the decision); groups and
|
|
76
|
+
time_col together raise instead of silently dropping one.
|
|
77
|
+
6. **The EDA must not leak into the final estimate.** The holdout is reserved
|
|
78
|
+
before profiling and is never used by rules or experiments.
|
|
79
|
+
7. **Reports are immutable, data is fingerprinted.** answer / verify /
|
|
80
|
+
interpret / register_external_holdout return a new report with an
|
|
81
|
+
incremented manifest revision and a history entry; every model is frozen
|
|
82
|
+
with tuples and read-only dicts. Steps that need data again check the
|
|
83
|
+
frame's fingerprint and refuse a different one instead of re-splitting.
|
|
84
|
+
8. **The LLM is above the trust boundary.** It sees an allowlisted payload
|
|
85
|
+
(inspectable with `llm_payload()`), must cite existing ids, and can only
|
|
86
|
+
suggest; `answer()` is the only way an answer is applied.
|
|
87
|
+
|
|
88
|
+
## Adding a rule
|
|
89
|
+
|
|
90
|
+
See CONTRIBUTING.md. In short: write `fn(profile, ctx, out)` in
|
|
91
|
+
`rules/catalog.py` with `@rule(id, version, thresholds)`, use `trigger()` with
|
|
92
|
+
the statistic's interval, pick the decision class honestly, add benchmark cases
|
|
93
|
+
(must fire / must not fire / near threshold), and bump the manifest version.
|
|
94
|
+
|
|
95
|
+
## Not in v1
|
|
96
|
+
|
|
97
|
+
Other modalities, time-series forecasting, clustering, model selection,
|
|
98
|
+
multi-feature leakage detection, interaction effects between decisions.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/); before 1.0, minor versions may
|
|
5
|
+
change the public API.
|
|
6
|
+
|
|
7
|
+
## 0.1.0 — 2026-11-01
|
|
8
|
+
|
|
9
|
+
First release of **prefit** (`pip install prefit`, `import prefit as pf`), the
|
|
10
|
+
decision-driven design. Replaces the v0 `Roadmap` API.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `pf.analyze()` returns a `Report`: Evidence → Finding → Decision chain with
|
|
14
|
+
decision classes (procedural / conditional / contextual / review) and statuses.
|
|
15
|
+
- Input guard: misspelled targets and unknown keywords raise; holdout reserved
|
|
16
|
+
before profiling (stratified, group-aware with `groups=`, time-ordered with
|
|
17
|
+
`time_col=`).
|
|
18
|
+
- Column-kind inference (numeric, categorical, boolean, datetime, text,
|
|
19
|
+
id_like, constant) with reasons; bootstrap / Wilson intervals on
|
|
20
|
+
threshold-relevant statistics; near-threshold findings marked `uncertain`.
|
|
21
|
+
- Task-agnostic leakage hint via a single-feature held-out score; leakage is
|
|
22
|
+
reported as "manual review required" with a question, never as a verdict.
|
|
23
|
+
- Rules: task confirmation, validation split, holdout policy, small data,
|
|
24
|
+
temporal order, target missing, duplicates, conflicting duplicates, high
|
|
25
|
+
missingness, near-constant, high correlation, constant columns, unsupported
|
|
26
|
+
columns, id-like columns, leakage hint, fit-in-fold, skewed features, high
|
|
27
|
+
cardinality, class imbalance, target skew, heavy tails, zero inflation;
|
|
28
|
+
metric policy driven by the cost of errors.
|
|
29
|
+
- `Report.answer()`, `Report.verify()` (bounded experiments, corrected paired
|
|
30
|
+
t-test, Holm correction, budget; user estimator verifies, proxies give
|
|
31
|
+
sensitivity analysis), `Report.export()` (sklearn Pipeline + CV + scoring from
|
|
32
|
+
eligible decisions only, with an explicit excluded list).
|
|
33
|
+
- Single-file HTML report with decision cards, linked evidence, rule versions
|
|
34
|
+
and thresholds, experiments, decision-bound plotly figures with data tables,
|
|
35
|
+
light/dark themes; text, markdown and JSON renders; reproducibility manifest.
|
|
36
|
+
- `pf.review()`: reference-based hypothesis checks, baselines, residual /
|
|
37
|
+
confusion / PR / segment analysis, list of never-verified assumptions;
|
|
38
|
+
standalone or embedded HTML.
|
|
39
|
+
- Optional LLM layer: allowlisted payload with `llm_payload()` preview, column
|
|
40
|
+
aliasing, schema-constrained output, unknown-id claims dropped, suggestions
|
|
41
|
+
never applied; Anthropic, Gemini and local OpenAI-compatible providers.
|
|
42
|
+
- Versioned benchmark (`benchmarks/`), track record cited in reports.
|
|
43
|
+
- CLI: `prefit report data.csv --target y`.
|
|
44
|
+
- CI (ruff, mypy, pytest with coverage, HTML smoke test).
|
|
45
|
+
|
|
46
|
+
### Changed (pre-release API round)
|
|
47
|
+
- Data partitions are explicit: `pf.split(df, target, test_size, strategy)` is the
|
|
48
|
+
primary path; `analyze` defaults to `holdout=None` and only sets a holdout aside
|
|
49
|
+
when asked (`holdout=0.2`). `report.split()` became `report.holdout(df)`, which
|
|
50
|
+
re-derives that partition and never splits anew.
|
|
51
|
+
- Fingerprints: `verify`, `holdout` and `register_external_holdout(dev_df=...)`
|
|
52
|
+
check the frame against SHA-256 digests (column schema, dtypes, index, target,
|
|
53
|
+
row-order-sensitive content) of the frame passed to `analyze`, and explain
|
|
54
|
+
any mismatch (reordered, changed, shorter, different columns).
|
|
55
|
+
- `report.register_external_holdout(test_df, dev_df=...)` records an evaluation
|
|
56
|
+
partition (metadata only, never profiled) and counts exact row overlap with
|
|
57
|
+
the development data, reported as a review item without a leakage verdict.
|
|
58
|
+
- Deep immutability: all report models are frozen, lists are tuples and dicts
|
|
59
|
+
read-only; every change is a new revision. `pf.load_report()` reloads JSON.
|
|
60
|
+
- API surface: reading / flow / automation on `Report`, internals under
|
|
61
|
+
`report.details` (findings, evidence, profile, figures, manifest, context,
|
|
62
|
+
limitations, rule_track_record, llm). Short reprs for all report objects,
|
|
63
|
+
a notebook summary card, and `report.decisions_table()`. v0 aliases removed.
|
|
64
|
+
- Split columns (`groups`, `time_col`) stay out of the features unless
|
|
65
|
+
`split_columns_as_features=True`, which raises a warning.
|
|
66
|
+
- HTML report rebuilt: Summary / Actions / Roadmap / Details navigation, short
|
|
67
|
+
decision cards (why with the decisive number, do, experiment summary, at most
|
|
68
|
+
one figure) with evidence, experiment, figure and rule/benchmark details in
|
|
69
|
+
collapsed sections, consecutive roadmap numbering, collapsed Details section.
|
|
70
|
+
- Verification and baselines use the export splitter (repeated for power).
|
|
71
|
+
- Feature scaling is an export option (`scale=True`), not part of the
|
|
72
|
+
procedural fit-in-fold decision.
|
|
73
|
+
- Bootstrap intervals computed on subsampled resamples are rescaled to the full
|
|
74
|
+
sample size (they were ~sqrt(n/m) times too wide on large data).
|
|
75
|
+
- Single-feature association is a 2-fold cross-validated score.
|
|
76
|
+
- Single-class targets are rejected up front; +/-inf values produce an
|
|
77
|
+
`infinite_values` review item instead of numpy warnings and are treated as
|
|
78
|
+
missing by the exported preprocessing; duplicate detection hashes rows
|
|
79
|
+
directly (much faster on wide data).
|
|
80
|
+
- `benchmarks/run.py` keeps the committed experiment section when run without
|
|
81
|
+
`--verify`.
|
|
82
|
+
|
|
83
|
+
### Changed (pre-release review)
|
|
84
|
+
- Raw datetime / text / string-id columns are a review item ("unsupported in
|
|
85
|
+
v1, may carry signal"), no longer a procedural drop.
|
|
86
|
+
- `groups=` together with `time_col=` raises instead of silently ignoring
|
|
87
|
+
`groups`; the time split states that class stratification is off; grouped
|
|
88
|
+
classification notes that class balance is approximate.
|
|
89
|
+
- Verification: the test now follows the split protocol (corrected repeated
|
|
90
|
+
k-fold t-test; single-run grouped k-fold; no p-value for time-series folds),
|
|
91
|
+
and "improved" requires a practical effect threshold
|
|
92
|
+
(`Budget.min_relative_effect` / `min_effect`). Per-fold differences and the
|
|
93
|
+
protocol are shown in the report.
|
|
94
|
+
- `export()` returns `model` (full estimator recipe, possibly a
|
|
95
|
+
`TransformedTargetRegressor`) and `preprocessor` instead of `pipeline`;
|
|
96
|
+
estimator-level steps without an estimator go to `needs_estimator`, and an
|
|
97
|
+
estimator without `class_weight` moves that step to `excluded` — nothing
|
|
98
|
+
listed as included is dropped.
|
|
99
|
+
- Reports record `manifest.revision` and `manifest.history` for every
|
|
100
|
+
analyze / answer / verify / interpret step.
|
|
101
|
+
- Wording: "single-feature predictive leakage hint"; benchmark records are a
|
|
102
|
+
"rule detection pass rate"; the HTML report opens with a data-partition note.
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
- `GroupKFold` is no longer suggested from duplicates or id-like columns.
|
|
106
|
+
- Integer targets with more than 10 distinct values are inferred as regression.
|
|
107
|
+
- `use_llm=`, `provider=`, `api_key=` replaced by `llm=LLMConfig(...)`.
|
|
108
|
+
|
|
109
|
+
### Removed
|
|
110
|
+
- `Roadmap.make_sklearn()` / `validation_recipe()` (use `Report.export()`).
|
|
111
|
+
- Reserved non-tabular modalities from the public enums.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
python -m venv .venv && source .venv/bin/activate
|
|
7
|
+
pip install -e ".[dev]"
|
|
8
|
+
pytest -q # tests (includes the benchmark cases)
|
|
9
|
+
ruff check . && mypy prefit
|
|
10
|
+
python benchmarks/run.py # refreshes benchmarks/results and the track record
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Adding or changing a rule
|
|
14
|
+
|
|
15
|
+
1. Write the rule in `prefit/rules/catalog.py` with
|
|
16
|
+
`@rule("id", "1", thresholds={...})`. Compare statistics with `trigger()` and
|
|
17
|
+
pass the statistic's interval so near-threshold cases become `uncertain`.
|
|
18
|
+
2. Choose the decision class honestly:
|
|
19
|
+
- procedural only if it is about *how to evaluate* and is right for any data;
|
|
20
|
+
- anything that changes the data's content is review, conditional or contextual;
|
|
21
|
+
- anything that needs knowledge outside the data is contextual (ask a question).
|
|
22
|
+
3. Never phrase a hint as a verdict ("leakage hint — manual review required",
|
|
23
|
+
not "leakage detected").
|
|
24
|
+
4. If the decision has a pipeline step, make sure `export.build_model` supports
|
|
25
|
+
its `Step.kind`.
|
|
26
|
+
5. Add benchmark cases in `benchmarks/fixtures.py`: one where it must fire, one
|
|
27
|
+
where it must not, and a near-threshold one where that is meaningful.
|
|
28
|
+
6. Bump the rule's version when its thresholds or logic change, and bump
|
|
29
|
+
`MANIFEST_VERSION` when cases or expectations change. Re-run
|
|
30
|
+
`python benchmarks/run.py` and commit the results, including failures.
|
|
31
|
+
7. If decision structure changes, refresh the golden files:
|
|
32
|
+
`UPDATE_GOLDEN=1 pytest tests/test_snapshot.py`, and review the diff.
|
|
33
|
+
|
|
34
|
+
## Versioning
|
|
35
|
+
|
|
36
|
+
Semantic versioning; before 1.0, minor versions may change the API. Every
|
|
37
|
+
release gets a CHANGELOG entry. Rule versions are independent and appear in
|
|
38
|
+
every report's manifest.
|
|
39
|
+
|
|
40
|
+
## Pull requests
|
|
41
|
+
|
|
42
|
+
Keep the deterministic core free of new hard dependencies. Tests, ruff and mypy
|
|
43
|
+
must pass; coverage must stay above the configured floor.
|
|
44
|
+
|
|
45
|
+
## Releasing
|
|
46
|
+
|
|
47
|
+
Publishing runs in GitHub Actions with PyPI Trusted Publishing (no tokens).
|
|
48
|
+
|
|
49
|
+
1. Bump `version` in `pyproject.toml` and `prefit/_version.py`, add a CHANGELOG entry, merge to `main`.
|
|
50
|
+
2. Optional dry run: Actions → Release → Run workflow → `target=testpypi`, then
|
|
51
|
+
`pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ prefit`.
|
|
52
|
+
3. Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z`. The workflow checks that the tag matches the
|
|
53
|
+
package version, builds, runs `twine check` and a wheel smoke test, then publishes to PyPI.
|
prefit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hkln4
|
|
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.
|
prefit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: prefit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Decision-driven EDA for tabular supervised learning: evidence-backed, verifiable pre-training decisions.
|
|
5
|
+
Project-URL: Homepage, https://hkln4.github.io/prefit/
|
|
6
|
+
Project-URL: Example report, https://hkln4.github.io/prefit/report.html
|
|
7
|
+
Project-URL: Repository, https://github.com/hkln4/prefit
|
|
8
|
+
Project-URL: Issues, https://github.com/hkln4/prefit/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/hkln4/prefit/blob/main/CHANGELOG.md
|
|
10
|
+
Author: hkln4
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: cross-validation,data-profiling,eda,evaluation,leakage,machine-learning,model-evaluation,scikit-learn
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: numpy>=1.23
|
|
28
|
+
Requires-Dist: pandas>=1.5
|
|
29
|
+
Requires-Dist: pydantic>=2
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Requires-Dist: anthropic>=0.40; extra == 'all'
|
|
32
|
+
Requires-Dist: plotly>=5; extra == 'all'
|
|
33
|
+
Requires-Dist: scikit-learn>=1.4; extra == 'all'
|
|
34
|
+
Requires-Dist: scipy>=1.9; extra == 'all'
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
37
|
+
Requires-Dist: pandas-stubs; extra == 'dev'
|
|
38
|
+
Requires-Dist: plotly>=5; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-cov>=4; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
42
|
+
Requires-Dist: scikit-learn>=1.4; extra == 'dev'
|
|
43
|
+
Requires-Dist: scipy>=1.9; extra == 'dev'
|
|
44
|
+
Provides-Extra: gemini
|
|
45
|
+
Requires-Dist: google-genai>=1.0; extra == 'gemini'
|
|
46
|
+
Provides-Extra: llm
|
|
47
|
+
Requires-Dist: anthropic>=0.40; extra == 'llm'
|
|
48
|
+
Provides-Extra: report
|
|
49
|
+
Requires-Dist: plotly>=5; extra == 'report'
|
|
50
|
+
Provides-Extra: verify
|
|
51
|
+
Requires-Dist: scikit-learn>=1.4; extra == 'verify'
|
|
52
|
+
Requires-Dist: scipy>=1.9; extra == 'verify'
|
|
53
|
+
Description-Content-Type: text/markdown
|
|
54
|
+
|
|
55
|
+
# prefit
|
|
56
|
+
|
|
57
|
+
**Decision-driven EDA for tabular supervised learning.**
|
|
58
|
+
|
|
59
|
+
Point `prefit` at your development data before you train. It profiles the
|
|
60
|
+
data, turns what it measures into a short list of *decisions* — how to split,
|
|
61
|
+
which columns to question, what to preprocess, which metric to trust — and shows
|
|
62
|
+
the evidence, threshold and uncertainty behind every one. Decisions that depend
|
|
63
|
+
on your data and model are tested with small, bounded experiments instead of
|
|
64
|
+
being assumed. At the end you get a scikit-learn model recipe that contains only
|
|
65
|
+
steps that are safe, that you confirmed, or that an experiment supported.
|
|
66
|
+
|
|
67
|
+
It is not AutoML: it does not choose a model or tune hyperparameters, and it
|
|
68
|
+
never applies an unconfirmed assumption silently.
|
|
69
|
+
|
|
70
|
+
**[Example report](https://hkln4.github.io/prefit/report.html)** ·
|
|
71
|
+
[Project page](https://hkln4.github.io/prefit/) · [PyPI](https://pypi.org/project/prefit/)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install "prefit[verify]" # core + experiments/export (scikit-learn, scipy)
|
|
75
|
+
pip install "prefit[verify,report]" # + plotly, for fully offline HTML reports
|
|
76
|
+
pip install "prefit[llm]" # optional LLM interpretation (Anthropic)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The core (`pandas`, `numpy`, `pydantic`) profiles, decides and renders reports
|
|
80
|
+
with no scikit-learn, no network and no API key.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Quickstart
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import prefit as pf
|
|
88
|
+
from sklearn.ensemble import HistGradientBoostingRegressor
|
|
89
|
+
|
|
90
|
+
model = HistGradientBoostingRegressor(random_state=0)
|
|
91
|
+
|
|
92
|
+
# 1. set the test data aside before anything is computed
|
|
93
|
+
dev_df, test_df = pf.split(df, target="price", test_size=0.2, strategy="random", random_state=42)
|
|
94
|
+
|
|
95
|
+
# 2. analyze the development data only
|
|
96
|
+
report = pf.analyze(dev_df, target="price", estimator=model)
|
|
97
|
+
report # notebook: a short summary card with the next step
|
|
98
|
+
print(report) # roadmap: one line of why / do per decision
|
|
99
|
+
report.to_html("report.html") # the full report
|
|
100
|
+
|
|
101
|
+
# 3. every step returns a new report revision
|
|
102
|
+
report = report.answer({"metric_priority": "typical_error"})
|
|
103
|
+
report = report.verify(dev_df) # bounded experiments with your model
|
|
104
|
+
report = report.register_external_holdout(test_df, dev_df=dev_df, name="test set")
|
|
105
|
+
|
|
106
|
+
# 4. export only what is applied, confirmed or verified
|
|
107
|
+
built = report.export()
|
|
108
|
+
print(built.describe()) # what is in, what is out, and why
|
|
109
|
+
fitted = built.model.fit(dev_df[built.feature_columns], dev_df["price"])
|
|
110
|
+
|
|
111
|
+
# 5. once, at the end, on the untouched test data
|
|
112
|
+
review = pf.review(test_df["price"], fitted.predict(test_df[built.feature_columns]), report)
|
|
113
|
+
report.to_html("report.html", review=review)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Shortcut: `pf.analyze(df, target, holdout=0.2)` sets the holdout aside itself,
|
|
117
|
+
before profiling; `dev_df, test_df = report.holdout(df)` gives it back. Without
|
|
118
|
+
`holdout=`, `analyze` never splits — the default is `holdout=None`.
|
|
119
|
+
|
|
120
|
+
From the shell:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
prefit report dev.csv --target price --verify --answer metric_priority=typical_error --out report.html
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
An example report produced by `examples/quickstart.py` is live at
|
|
127
|
+
[hkln4.github.io/prefit/report.html](https://hkln4.github.io/prefit/report.html) (source:
|
|
128
|
+
[`examples/output/california_report.html`](https://github.com/hkln4/prefit/blob/main/examples/output/california_report.html)).
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## The report object
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# reading
|
|
136
|
+
print(report); report.to_html(path); report.to_markdown()
|
|
137
|
+
report.roadmap; report.pending_questions(); report.decisions_table()
|
|
138
|
+
report.target; report.task
|
|
139
|
+
|
|
140
|
+
# flow (each returns a new revision; the old report is unchanged)
|
|
141
|
+
report.answer({...}); report.verify(df); report.interpret(llm)
|
|
142
|
+
report.register_external_holdout(test_df, dev_df=dev_df)
|
|
143
|
+
report.export(estimator); report.holdout(df); pf.review(...)
|
|
144
|
+
|
|
145
|
+
# automation
|
|
146
|
+
report.decisions; report.decision(id); report.questions; report.experiments
|
|
147
|
+
report.metric_plan; report.baseline; report.to_json(); report.llm_payload()
|
|
148
|
+
|
|
149
|
+
# internals: the traceability chain and reproducibility record
|
|
150
|
+
report.details.findings / .finding(id) / .evidence / .profile / .figures
|
|
151
|
+
report.details.manifest / .context / .limitations / .rule_track_record / .llm
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`report.decisions_table()` is the best place to start in a notebook: one row per
|
|
155
|
+
decision with its status, a one-line why (with the decisive number), what to do,
|
|
156
|
+
and any experiment result.
|
|
157
|
+
|
|
158
|
+
**Reports are immutable.** Every model is frozen, lists are tuples and dicts are
|
|
159
|
+
read-only, all the way down. `answer`, `verify`, `interpret` and
|
|
160
|
+
`register_external_holdout` return a new report whose
|
|
161
|
+
`details.manifest.revision` is incremented and whose `history` records the step.
|
|
162
|
+
`pf.load_report(path)` reloads a report saved with `to_json`.
|
|
163
|
+
|
|
164
|
+
**The data is checked, never trusted.** A report stores no rows, so `verify`
|
|
165
|
+
and `holdout` take the DataFrame again. Its fingerprint — SHA-256 digests of the
|
|
166
|
+
column schema, dtypes, index, target and row-order-sensitive content — must
|
|
167
|
+
match the frame passed to `analyze`. A shuffled, edited, shortened or different
|
|
168
|
+
frame (the test set, say) raises an error that says what differs; nothing is
|
|
169
|
+
ever re-split.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## How decisions are made
|
|
174
|
+
|
|
175
|
+
Every rule turns *evidence* (a measured value, its 95% interval, the method)
|
|
176
|
+
into a *finding*, and the finding into one or more *decisions*. Each decision has
|
|
177
|
+
a **class**, which says how much the data alone can settle it:
|
|
178
|
+
|
|
179
|
+
| class | meaning | enters the model recipe when |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| **procedural** | about *how to evaluate*; correct regardless of the data (fit preprocessing inside folds, stratify, keep split columns and the holdout out). The only column it removes are constant ones. | always |
|
|
182
|
+
| **conditional** | may help or hurt depending on data and model (log target, power transform, target encoding, class weights, dropping mostly-missing columns) | an experiment with **your** estimator verifies it |
|
|
183
|
+
| **contextual** | cannot be decided from data (metric = cost of errors, is a column known at prediction time, is it an identifier, does time order matter) | you answer the question |
|
|
184
|
+
| **review** | needs a human look (duplicates, label conflicts, informative missingness, infinite values, collinearity, raw datetime/text columns v1 cannot encode) | never; it is a to-do |
|
|
185
|
+
|
|
186
|
+
Ground rules the catalog follows:
|
|
187
|
+
|
|
188
|
+
- **Thresholds are triggers, not verdicts.** Threshold-relevant statistics carry
|
|
189
|
+
a bootstrap or Wilson interval (bootstrap intervals are computed for the full
|
|
190
|
+
sample size even when resamples are subsampled); when the interval straddles
|
|
191
|
+
the threshold the finding is *near threshold* and its decision *uncertain*.
|
|
192
|
+
- **Leakage is only ever a hint.** A feature that alone predicts the target in a
|
|
193
|
+
2-fold cross-validated single-feature score raises a "single-feature
|
|
194
|
+
predictive leakage hint — manual review required" and a question ("is this
|
|
195
|
+
known at prediction time?"). Genuinely strong features look the same, so
|
|
196
|
+
nothing is dropped until you answer.
|
|
197
|
+
- **Unsupported is not uninformative.** Raw datetimes, free text and string
|
|
198
|
+
identifiers are left out of the exported features because v1 has no encoder
|
|
199
|
+
for them; they are reported as a review item, never as "carries no signal".
|
|
200
|
+
- **No group splitting without real groups.** ID-like columns and duplicates
|
|
201
|
+
produce review items. Group-aware splitters are used only with `groups=`;
|
|
202
|
+
`TimeSeriesSplit` only with `time_col=`. Both columns stay out of the features
|
|
203
|
+
unless `split_columns_as_features=True` (which adds a warning).
|
|
204
|
+
- **Split precedence is explicit.** `time_col` turns off class stratification
|
|
205
|
+
(the report says so); `groups` with classification uses
|
|
206
|
+
`StratifiedGroupKFold`, which balances classes only approximately; `groups`
|
|
207
|
+
and `time_col` together raise an error in v1.
|
|
208
|
+
- **The data you analyze is the data the report describes.** The report opens
|
|
209
|
+
with a data-partition note: which rows were analyzed, whether an internal or
|
|
210
|
+
registered external holdout exists, and that it was never read.
|
|
211
|
+
|
|
212
|
+
## Experiments, and what they can claim
|
|
213
|
+
|
|
214
|
+
`verify` tests each conditional decision once per model family as "current
|
|
215
|
+
setup" vs. "current setup + this one change", on identical folds. The folds come
|
|
216
|
+
from the same splitter as the export CV decision (repeated when it is a plain
|
|
217
|
+
k-fold, for power); baselines use the same folds. A `Budget` caps the number of
|
|
218
|
+
experiments, wall-clock time and rows.
|
|
219
|
+
|
|
220
|
+
| protocol | when | test |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `repeated_kfold` | default | corrected repeated k-fold t-test (Nadeau & Bengio 2003; Bouckaert & Frank 2004), Holm across the run |
|
|
223
|
+
| `grouped_kfold` | `groups=` | same correction on a single grouped k-fold run (low power, said in the report) |
|
|
224
|
+
| `time_series` | `time_col=` | none — expanding windows are not exchangeable; per-fold differences only, outcome left to you |
|
|
225
|
+
|
|
226
|
+
An experiment counts as **improved** only with the right direction, a
|
|
227
|
+
significant Holm-adjusted p-value, *and* a mean difference of at least the
|
|
228
|
+
practical threshold (`Budget(min_relative_effect=0.01)` = 1% of the baseline
|
|
229
|
+
score, or an absolute `min_effect`). Significant but smaller differences are
|
|
230
|
+
**negligible**. Per-fold differences are in the report.
|
|
231
|
+
|
|
232
|
+
- With `estimator=`, an improvement makes the decision **verified** (and
|
|
233
|
+
exportable); a practical-size loss makes it **refuted**.
|
|
234
|
+
- Without it, a linear and a tree proxy are used. Their results are a
|
|
235
|
+
**sensitivity analysis** (`supported_by_proxy`), not a verdict on your model,
|
|
236
|
+
and are excluded from export unless you pass
|
|
237
|
+
`export(include_proxy_supported=True)`. When one family gains and the other
|
|
238
|
+
loses, the decision is marked model-family dependent.
|
|
239
|
+
- All of it is **development evidence**. The generalization claim comes from the
|
|
240
|
+
untouched test data (or nested CV when you have none).
|
|
241
|
+
|
|
242
|
+
## Export
|
|
243
|
+
|
|
244
|
+
`report.export(estimator, scale=True)` returns:
|
|
245
|
+
|
|
246
|
+
- `model` — the full recipe for `fit` / `cross_validate`: `Pipeline(preprocess,
|
|
247
|
+
estimator)`, wrapped in a `TransformedTargetRegressor` when a target transform
|
|
248
|
+
was verified (`built.model_kind` says which). `None` without an estimator.
|
|
249
|
+
- `preprocessor` — the feature preprocessing alone. Feature scaling is an
|
|
250
|
+
estimator-dependent default (`scale=False` turns it off), not a decision.
|
|
251
|
+
- `cv`, `scoring`, `feature_columns`, `groups`, `sort_by`.
|
|
252
|
+
- `included`, `excluded` (with status and reason), `needs_estimator` (eligible
|
|
253
|
+
estimator-level steps that need an estimator), `unsupported` (columns left
|
|
254
|
+
out), `assumptions` still open.
|
|
255
|
+
|
|
256
|
+
## The HTML report
|
|
257
|
+
|
|
258
|
+
`report.to_html(path)` writes one file with four sections:
|
|
259
|
+
|
|
260
|
+
- **Summary** — target, task, development rows; how many questions await you and
|
|
261
|
+
how many decisions are critical or need review; one next step; the data
|
|
262
|
+
partition note.
|
|
263
|
+
- **Actions** — what needs your attention (open questions, review items, and
|
|
264
|
+
conditional decisions an experiment verified or refuted), the questions with
|
|
265
|
+
their options (and an LLM suggestion awaiting your confirmation, if you ran
|
|
266
|
+
one), and the metric plan.
|
|
267
|
+
- **Roadmap** — one short card per decision: status, title, one-line why with
|
|
268
|
+
the decisive number, one-line do, experiment summary, and at most one figure.
|
|
269
|
+
Evidence, experiment details, related figures and rule/benchmark details are
|
|
270
|
+
one click away inside the card.
|
|
271
|
+
- **Details** — limitations, then collapsed: all diagnostics, findings,
|
|
272
|
+
evidence, dataset profile, manifest and history.
|
|
273
|
+
|
|
274
|
+
No raw rows are embedded; category labels are shown only for low-cardinality
|
|
275
|
+
columns (`ProfileOptions(redact_values=True)` hides them all). Charts use
|
|
276
|
+
plotly.js: with `plotlyjs="cdn"` (default) the report is a single file but needs
|
|
277
|
+
internet for the charts; `plotlyjs="inline"` embeds plotly.js (~5 MB, needs the
|
|
278
|
+
`report` extra) for fully offline use; every chart also has a data table, which
|
|
279
|
+
works offline either way.
|
|
280
|
+
|
|
281
|
+
## Optional LLM interpretation
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
report.llm_payload(pf.LLMConfig(share_column_names=False)) # inspect first
|
|
285
|
+
report = report.interpret(pf.LLMConfig(provider="anthropic"))
|
|
286
|
+
report = report.interpret(pf.LLMConfig(provider="local", model="llama3.1")) # Ollama etc.
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The LLM receives only the computed report — never rows, never category labels —
|
|
290
|
+
and must answer in a fixed JSON schema. It can summarize, prioritize existing
|
|
291
|
+
decisions, suggest answers to open questions (shown as awaiting your
|
|
292
|
+
confirmation, never applied) and raise *semantic* flags on column names, which
|
|
293
|
+
are marked manual-review. Claims that cite unknown evidence or decision ids are
|
|
294
|
+
dropped and counted. With `share_column_names=False` columns are aliased and
|
|
295
|
+
free-text messages are withheld; the report lists exactly what was sent. The
|
|
296
|
+
free-text `context=` never changes a decision by itself. Without an LLM the
|
|
297
|
+
report is complete.
|
|
298
|
+
|
|
299
|
+
Provider status: the interpretation logic and the local OpenAI-compatible
|
|
300
|
+
provider are tested (with a fake provider and a loopback server). The Anthropic
|
|
301
|
+
and Gemini providers are thin wrappers that have **not** been exercised against
|
|
302
|
+
the live APIs in this repository's tests.
|
|
303
|
+
|
|
304
|
+
## After training
|
|
305
|
+
|
|
306
|
+
`pf.review(y_true, y_pred, report, y_score=..., segments=...)` checks the
|
|
307
|
+
roadmap's hypotheses against real errors, each against an explicit reference
|
|
308
|
+
(e.g. "tails dominate" vs. what Gaussian errors would give), adds residual /
|
|
309
|
+
confusion / precision–recall / segment analysis, and lists every assumption
|
|
310
|
+
that was never verified.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Benchmark
|
|
315
|
+
|
|
316
|
+
`python benchmarks/run.py` runs versioned fixture cases (synthetic data with one
|
|
317
|
+
injected hazard each, plus datasets bundled with scikit-learn) and checks that
|
|
318
|
+
each rule fires, stays silent, or reports *near threshold* as expected. Results,
|
|
319
|
+
including failures, are written to `benchmarks/results/<manifest>.json|md`;
|
|
320
|
+
`--verify` adds experiment outcomes on real datasets (a plain run keeps the
|
|
321
|
+
committed experiment section). Reports cite a rule's "detection pass rate on
|
|
322
|
+
benchmark <manifest>: k/n checks".
|
|
323
|
+
|
|
324
|
+
Current manifest `2026.11-2`: 20/20 cases pass. This is a detection pass rate on
|
|
325
|
+
known-hazard fixtures — not a claim about real-world accuracy, nor about how
|
|
326
|
+
often a decision improves a model.
|
|
327
|
+
|
|
328
|
+
## Case studies
|
|
329
|
+
|
|
330
|
+
`python examples/case_studies.py` (numbers below are from that script; fixed seeds):
|
|
331
|
+
|
|
332
|
+
| study | rule | before | after |
|
|
333
|
+
|---|---|---|---|
|
|
334
|
+
| California Housing, skewed target, log-transform (HGB) | `target_skew` | MAE 0.3125 | MAE 0.3074 (−1.6%) |
|
|
335
|
+
| Breast Cancer + 20% duplicated rows, de-duplicate (RF) | `duplicate_rows` | CV accuracy 0.9736 | 0.9579 (1.57 pts of inflation) |
|
|
336
|
+
| Diabetes + injected leaked feature, remove it (linear) | `leakage_hint` (critical) | CV R² 0.9999 | 0.4892 |
|
|
337
|
+
|
|
338
|
+
## Scope and limitations
|
|
339
|
+
|
|
340
|
+
- v1 covers tabular classification and regression. No images, text, time-series
|
|
341
|
+
forecasting, clustering or model selection.
|
|
342
|
+
- Leakage screening sees single-feature association and numeric correlation
|
|
343
|
+
only; leakage through feature combinations, time, preprocessing or repeated
|
|
344
|
+
entities is not detected.
|
|
345
|
+
- Column kinds and the task are inferred heuristically; check the schema table.
|
|
346
|
+
- Experiments test one change at a time; interactions are not measured.
|
|
347
|
+
- `groups` and `time_col` cannot be combined in v1.
|
|
348
|
+
- Raw datetime, free-text and string-identifier columns are not encoded.
|
|
349
|
+
|
|
350
|
+
Design notes: [ARCHITECTURE.md](https://github.com/hkln4/prefit/blob/main/ARCHITECTURE.md) · changes: [CHANGELOG.md](https://github.com/hkln4/prefit/blob/main/CHANGELOG.md) ·
|
|
351
|
+
contributing: [CONTRIBUTING.md](https://github.com/hkln4/prefit/blob/main/CONTRIBUTING.md)
|