Changelog¶
All notable changes to trustcv are documented in this file.
[1.3.0] - 2026-10-19¶
Changed (behaviour)¶
- The deep-learning runners no longer select models on the held-out fold.
TorchCVRunner,MONAICVRunner,JAXCVRunnerand the epoch loop ofUniversalCVRunnerused to compute per-epochval_*metrics on the held-out fold and pass them toEarlyStoppingandModelCheckpoint, which therefore chose the epoch on the data that then scored it. Per-epochval_*metrics now come only from an inner validation split of the training fold,run(..., inner_validation=0.1)(the same option asKerasCVRunnerin 1.2.1). Without it, noval_*metrics exist, models train for the full epochs, and selection callbacks warn once that they have nothing to monitor. The held-out fold is scored once, after training.metadata["model_selection"]records the strategy and sizes per fold. report_heldout_per_epoch=Truerecords held-out learning curves inmetadata["heldout_curves"]for plotting. Callbacks never see them, andEarlyStopping/ModelCheckpointrefuse amonitorstarting withheldout(ValueError). The 1.2.1 warning about selecting on the held-out fold is gone, as the case can no longer arise.ModelCheckpoint(save_best_only=False, save_freq=N)saves every N-th epoch counting from 1 (epochs 5, 10, ... forN=5), which also matchesMONAICVRunner'seval_every; it used to save at epochs 1, 6, 11, ...- Custom callbacks:
logsno longer carry held-outval_*values.logs["snapshot"]()returns a copy of the current weights, andlogs["checkpoints"]collects requested checkpoint paths.
Added¶
EarlyStopping(restore_best=True)works: it stores a copy of the best epoch's weights (torch/MONAIstate_dicton CPU, the JAXTrainState, Kerasget_weights()inUniversalCVRunner), and the runner restores them before scoring the held-out fold, both after an early stop and after the last epoch. Previously it kept a reference to the live model, so nothing was restored.ModelCheckpointfiles are written, through the framework adapter'ssave_model, with{fold}/{epoch}templating and parent directories created. Previously no runner saved anything.MONAICVRunner.run(eval_every=5)makes the evaluation and callback cadence explicit.MONAIAdapter.make_loaderbuilds one loader from row indices.UniversalCVRunnerwith PyTorch/MONAI acceptsoptimizeras a functionmodel -> optimizer, so each fold's new model gets its own (the default is Adam).- CI jobs for PyTorch + MONAI and for JAX on CPU, and smoke tests that run the heavy examples (
TRUSTCV_EXAMPLE_FAST=1).
Fixed¶
- Every runner calls all callbacks each epoch before acting on a stop request; a callback after an early stopper used to be skipped on the stopping epoch.
JAXCVRunnerignored the stop request fromEarlyStopping.UniversalCVRunnerdiscarded the JAX training state after each epoch and passed the loss function as the evaluation state.-
examples/monai_3d_imaging_cv.pyfailed on real MONAI (DenseNet121 cannot downsample a 32×32×16 volume five times) and only ran in its no-MONAI fallback. It now uses a small MONAI DenseNet and a faint, randomly placed lesion; the bright fixed cube made the task trivially separable (AUC 1.00). It splits withStratifiedGroupKFold, and a single-class fold reports AUC as undefined rather than 0.5. -
TrustCV(method="bootstrap", bootstrap_validation_estimator=".632")(and".632+", orcv=BootstrapValidation(estimator=...)) silently reported the plain out-of-bag mean.validate()now applies Efron & Tibshirani's (1997) estimator for per-observation losses (accuracy as 0-1 loss, mse, mae): leave-one-out bootstrap error, apparent error of a full-data refit and, for.632+, the no-information error with the capped correction of their equation 32. Corrected metrics report no confidence interval (none is valid without an outer bootstrap). Other metrics keep the out-of-bag mean with a warning; details are inresult.diagnostics["bootstrap"]. BootstrapValidation.compute_error(".632+")dropped the cap on the out-of-bag error in the final formula; it now follows equation 32.SpatialBlockCVtested onlyn_splitsrandomly chosen grid cells, so rows in the other cells were never tested (for example 4 of 9 cells withn_splits=5). Whole blocks are now assigned to folds, so every row is tested exactly once and no block is split.block_size(previously accepted but ignored) sets square cells on a fixed grid in coordinate units. With fewer occupied blocks than folds it now raisesValueErrorinstead of assigning rows at random (which put one location on both sides).RegulatoryReportHTML showed 0 for metrics and the confusion matrix whencalculate_clinical_metricswas not called, and labelled the cross-validation score "Mean Accuracy" whatever metric it was. Missing values now read "not computed", and the score is labelled with its metric and shown with its interval and method.
[1.2.2] - 2026-10-19¶
Fixed¶
-
TorchCVRunnerandMONAICVRunnercalled the splitter without labels, so stratified splitters (StratifiedGroupKFold,RepeatedKFold(stratify=True), ...) failed with "missing y". They now pass labels from a(X, y)tuple, a dataset'stargets/labelsattribute, or scalar MONAIlabelvalues, without loading items. -
TrustCV.validate()raisesValueErrorwhen the splitter produces no folds, instead of returning a result with no scores (withgroups=, the group check could report PASSED over zero folds). -
RegulatoryReportHTML no longer states that the report "complies with" the chosen standard; a scope section says it does not establish compliance, and the approval block is a review and sign-off section. Docstrings no longer call the reports "FDA/CE-compliant". -
Docstrings of
TorchCVRunner,KerasCVRunner,ProgressLogger,RegulatoryComplianceLoggerand thecomplianceparameter ofTrustCVValidatorno longer claim to ensure regulatory compliance; the logger now prints "Validation log saved to ...".
Added¶
NestedCV,NestedGroupedCVandNestedTemporalCVtaken_jobs(default-1, as before) for the inner grid search. It was hard-coded to all cores, which failed where worker processes cannot receive the estimator (for example code run withpython -c, some notebook and cluster setups).
Changed¶
- Documentation moves to MkDocs Material, with a generated API reference,
api-schema.jsonandllms.txt; documentation code snippets run in CI. Several docstrings that contradicted their signatures are fixed. trustcv.checks.OVERALL_STATUSESlists the overall status values.
[1.2.1] - 2026-10-17¶
Fixed¶
KerasCVRunnerselected models on the held-out fold by default. It addedEarlyStopping(restore_best_weights=True)and passed the CV test fold asvalidation_data, so each fold's best epoch was chosen on the data that then scored it. The held-out fold is now only scored. By default there is no validation data and no automatic early stopping (train forepochs). In a noise benchmark the old default's optimism was small (AUC 0.504 vs 0.491, within run-to-run noise), so expect small changes in reported scores.KerasCVRunnercopies Keras callbacks per fold, so state such asEarlyStopping.bestno longer carries from one fold to the next.KerasCVRunner.run(..., verbose=0)raised a duplicate-keyword error.- Examples no longer select on the held-out fold. The PyTorch example reported the best test-fold AUC over epochs, MONAI the best test-fold accuracy, and TensorFlow early-stopped on the test fold. They now train for fixed epochs and score once. The TensorFlow bootstrap section computes the real AUC instead of a constant 0.5. Bundled recipe 7 drops
EarlyStoppingand fixes its Keras summary, whose error was hidden by a broadexcept. - The test for duplicate column labels no longer depends on scikit-learn accepting duplicate column names (scikit-learn 1.9 rejects them).
Added¶
KerasCVRunner.run(inner_validation=0.1): early stopping on an inner split of each training fold. It is group-aware withgroups=, stratified when feasible, andinner_validation_strategy="last"handles chronological data. With noEarlyStoppingpassed, one is added on the inner split. Per-fold details are inresult.metadata["model_selection"].trustcv.core.inner_split.inner_validation_split, the framework-agnostic helper behind it.- trustcv's
EarlyStoppingandModelCheckpoint(save_best_only=True)warn once per CV run when they select on aval_*metric. The Torch, MONAI, JAX and Universal runners compute those on the held-out fold.
Changed¶
KerasCVRunner(validation_split>0)(Keras's last-fraction split) still works, but warns that it is not group-aware and can split repeated rows. It cannot be combined withinner_validation; passingvalidation_datais rejected.
[1.2.0] - 2026-10-17¶
Added¶
- New overall status
NEEDS_REVIEW: a leakage-relevant check found signs of possible leakage (WARNING) that cannot be proven automatically. It ranks betweenFAILEDandNOT_FULLY_VERIFIED. - Independence scan when no groups are supplied: rows that come in unusually tight clusters (repeated subjects, augmentation, oversampling) make
group_leakageaWARNING, also when independence was declared. Turn off withTrustCV(check_independence=False). - Identifier-column detection for DataFrames (
patient_id,PatientID,mrn, ...), with a hint to drop the column and pass it asgroups=. Repeated identifier values without groups makegroup_leakageaWARNING. DataLeakageChecker.check(folds=...)audits given(train_idx, test_idx)splits.- Strong single predictors (
target_strong_threshold, default 0.90) are named in the target-leakage message and recommendations without changing its status. TrustCV(max_audit_folds=10)caps how many scored folds the distance-based detector audits; overlap and exact-duplicate checks always cover every fold. Coverage is inresult.diagnostics["fold_audit"].- The test suite refuses to run against a trustcv other than the checkout under test.
Changed¶
validate()audits the folds that were actually scored. Group-overlap, legacy integrity keys and the external detector no longer re-run the splitter or use the detector's own splits. Customleakage_checkers without afoldsparameter still work and say they used their own splits.- Hints are actionable and use current API names, for example
TrustCV(method='stratified_group_kfold')for group overlap. Repeated per-fold hints are deduplicated. - Target-leakage messages name the features and their scores; inverse associations are shown as such.
- The Pipeline preprocessing message states that preprocessing applied before
validate()is not visible. - Covariate shift is
INFOwhen group separation was verified on the scored folds. - Confidence intervals of built-in metrics bounded to [0, 1] are clipped to that range (raw bounds kept in
diagnostics["confidence_intervals"][metric]["clipped_from"]). Custom scorers are not clipped. - Behaviour change: results that were
NOT_FULLY_VERIFIEDonly because of aWARNINGare nowNEEDS_REVIEW, and declared-independent data that looks clustered changes fromPASSEDtoNEEDS_REVIEW.
Fixed¶
- Multi-input (dict/list) features no longer make the detector and the target scan error, which turned
overall_statusintoFAILED; they areNOT_CHECKED. - The detector's exact-duplicate check hashed the DataFrame index along with the values, so identical rows with different indices were missed. It now hashes values only and counts test rows.
[1.1.2] - 2026-10-17¶
Added¶
ClinicalMetrics.calculate_all(..., groups=...): the AUC confidence interval resamples whole patients when group ids are given, so repeated measures no longer give too-narrow intervals.groupsmust be 1-D with one entry per observation; a mismatch raisesValueErrorinstead of silently resampling a different population.- Regression tests for the AUC interval (pandas input with a non-default index, cluster bootstrap, single-class input).
- CI job that runs the TensorFlow/Keras tests against
trustcv[tensorflow].
Fixed¶
- The AUC interval returns
(nan, nan)instead of raisingIndexErrorwhen no resample contains both classes. The_auc_cidocstring no longer claims DeLong; it is a bootstrap.
[1.1.1] - 2026-10-17¶
Added¶
trustcvcommand line tool withaboutandexamples. Seven runnable usage recipes now ship inside the package (trustcv examples,--run,--copy). Examples moved fromexamples/totrustcv/examples/; framework-specific scripts stay inexamples/.
Fixed¶
ClinicalMetrics.calculate_allno longer raisesKeyErrorwheny_trueis a pandas Series with a non-default index.
Changed¶
dashboard()no longer writestrustcv_dashboard.htmlinto the current directory by default. Passsave_path="<file>.html"to save the dashboard to disk.
[1.1.0] - 2026-09-21¶
Trust and validation semantics¶
- The default confidence interval method is now corrected_t; oof_bootstrap adds pooled OOF and group-cluster resampling. Legacy fold-score bootstrap remains available with a warning that correlated fold-score intervals are too narrow.
- A reported PASSED overall status now requires every applicable leakage-relevant check to be PASSED or NOT_APPLICABLE. Unknown and unverified conditions produce NOT_FULLY_VERIFIED.
- Class imbalance is now INFO or WARNING, never FAILED.
- Added structured CheckResult entries, overall_status, automatic target-feature scanning, an opt-in permutation CV-loop sanity check, and recommend_cv.
- Added external_leakage_detected. The inverted legacy has_leakage result key is deprecated for one release.
- KS tests now report Bonferroni-corrected covariate shift and never create leakage types.
- Near-duplicate detection now uses train-standardized Euclidean distance calibrated to within-training nearest-neighbour spacing instead of cosine similarity.
- Preprocessing is reported PASSED only for visible sklearn Pipeline steps; preprocessing applied before validate() cannot be detected.
- Legacy leakage keys no longer report a pass when the leakage detector fails or cannot run.
Differences from the version described in the paper (v1.0.7)¶
- Confidence intervals: v1.0.7 defaulted to bootstrapping a small set of correlated fold scores. v1.1.0 defaults to the Nadeau–Bengio corrected resampled t interval and offers pooled OOF bootstrap intervals.
- PASSED semantics: v1.0.7 could report leakage PASSED when evidence was missing or a detector failed. v1.1.0 reports PASSED only when every applicable leakage-relevant structured check passed; incomplete evidence is NOT_FULLY_VERIFIED.
- Near-duplicate detection: v1.0.7 used a fixed cosine-similarity threshold that produced false positives in low dimensions. v1.1.0 uses train-standardized Euclidean distance calibrated to within-training nearest-neighbour spacing.
[1.0.7] - 2026-03-16¶
Bug Fixes¶
-
Fixed severity ranking in DataLeakageChecker — The
check_cv_splits()method used Python'smax()on severity strings ("critical","high", etc.), which does alphabetical comparison. This causedmax("critical", "high")to incorrectly return"high". Replaced with explicit rank-based_worst_severity()helper using{"none": 0, "low": 1, "medium": 2, "high": 3, "critical": 4}. -
Fixed temporal leakage detection being too aggressive — Previously flagged leakage whenever
min_test < max_train(any date overlap), which triggers even for normal k-fold scenarios. Now only flags when test data appears before training starts (min_test < min_train) or when >50% of test samples fall within the training period. Addedoverlap_fractionandtest_before_train_fractionmetrics. -
Replaced bare
except:clauses — Bareexcept:in temporal timestamp parsing and feature correlation now catch specific exceptions (ValueError,TypeError,OverflowError) instead of silently swallowing all errors.
New Features¶
-
Near-duplicate detection (
check_near_duplicates()) — Detects near-duplicate samples between train/test sets using cosine similarity (sklearn.metrics.pairwise.cosine_similarity). Configurable similarity threshold (default 0.99). Automatically integrated intocheck_cv_splits(). -
Hierarchical leakage detection (
check_hierarchical_leakage()) — Checks if parent-level groups (e.g., hospitals) leak across train/test sets, even when child-level groups (e.g., patients) are properly separated. Useful for multi-level medical data (Hospital → Department → Patient). -
Auto-computed spatial threshold —
check()andcheck_cv_splits()now auto-compute a spatial proximity threshold (10th percentile of pairwise distances) when coordinates are provided but no explicit threshold is given. Previously spatial checks were silently skipped in the conveniencecheck()method. -
LeakageDetectionCallback — New callback for
UniversalCVRunnerthat automatically checks each fold for data leakage during cross-validation. Reports per-fold leakage findings and aggregates a summary at the end. Usage:LeakageDetectionCallback(data=(X, y), groups=patient_ids, verbose=1). -
Auto-running DataLeakageChecker in TrustCVValidator — When
check_leakage=True(the default),TrustCVValidator.validate()now automatically creates and runs aDataLeakageCheckerinternally, without requiring the user to pass one explicitly. The full suite of leakage checks (patient, duplicate, near-duplicate, temporal, feature statistics, spatial, label distribution) runs automatically. -
Truly comprehensive
comprehensive_check()— Previously only rancheck_feature_target_leakage(). Now also runs the full CV-basedcheck()suite and merges results, recommendations, and severity into a single report withoverall_severity.
Quality Improvements¶
-
KS-test for feature statistics —
_check_feature_statistics()now runs a Kolmogorov-Smirnov test (scipy.stats.ks_2samp) on each feature in addition to the existing mean/std comparison. Features with KS p-value < 0.001 are flagged as having significantly different distributions. Reportsks_suspicious_featurescount andks_pvalueslist. -
Chi-squared test for label distribution —
_check_label_distribution()now runs a chi-squared contingency test (scipy.stats.chi2_contingency) alongside the existing max-difference check. Reportschi2_pvaluefor statistical significance of distribution differences. -
Temporal overlap quantification —
_check_temporal_leakage()now computes and returnsoverlap_fraction(fraction of test samples with timestamps within the training period) andtest_before_train_fraction(fraction of test samples before training starts).
Documentation¶
- AI Agent documentation — Added machine-readable documentation files for AI coding agents:
website/llms.txt— Concise API reference following the llms.txt standardwebsite/llms-full.txt— Extended reference with all 29 CV methods and full signatureswebsite/api-schema.json— Structured JSON API schema for programmatic agents.github/copilot-instructions.md— GitHub Copilot repo instructions.cursorrules— Cursor IDE project rules-
website/ai-agents.html— Website page documenting AI agent support with setup guides -
All documentation updated for v1.0.7 leakage improvements (CLAUDE.md, llms.txt, llms-full.txt, api-schema.json, copilot-instructions.md, cursorrules).
Tests¶
- 47 new unit tests in
tests/test_leakage_improvements.pycovering: - Severity ranking correctness (5 tests)
- Temporal leakage fix (6 tests)
- Near-duplicate detection (6 tests)
- Hierarchical leakage (3 tests)
- Spatial auto-threshold (2 tests)
- KS-test feature statistics (3 tests)
- Chi-squared label distribution (2 tests)
- Comprehensive check integration (2 tests)
- LeakageDetectionCallback (4 tests)
- Validator auto-checker (2 tests)
- LeakageReport dataclass (3 tests)
- check() convenience method (4 tests)
- Preprocessing leakage (2 tests)
- Existing 9 tests continue to pass
[1.0.6] - Previous Release¶
Initial public release with 29 CV methods, DataLeakageChecker, BalanceChecker, ClinicalMetrics, UniversalCVRunner, and framework adapters for PyTorch, TensorFlow, MONAI, and JAX.