Diagnostics and exceptions¶
Inspect what a splitter removed and check temporal, group, and embargo constraints.
Start with audit_splitter for a fold report. The assertion
helpers below raise the documented exceptions when a constraint is violated.
Diagnostics¶
purgedcv.audit_splitter ¶
Return a non-raising, per-fold report for a temporal splitter.
The report consumes the same candidate → purge → embargo → final pipeline
as BaseTemporalSplitter.split; purge and embargo counts therefore
come from the actual intermediate index arrays rather than being inferred
from the final split. Sliding walk-forward truncation is implemented by
the finalization stage and therefore appears in
rows_removed_by_finalization. If a custom finalization hook introduces
indices, rows_added_by_finalization reports them separately.
candidate_overlap_fraction is the fraction of candidate training rows
whose label horizons overlap the test horizons after applying the
splitter's purge_horizon padding. For non-empty candidates it equals
rows_removed_by_purge / candidate_train_size; empty candidates report
zero. compute_overlap_fraction reproduces it only when given the
same pre-purge candidate indices and purge horizon — indices returned by
cv.split() are already final. final_overlap_fraction repeats the
measure on final training rows and should be zero for a clean splitter.
For the built-in splitters, temporal_leakage_free is structurally
expected to be True: purge removes overlaps and later stages only
remove rows. The column is primarily a regression guard for custom
BaseTemporalSplitter subclasses whose finalization hook could
accidentally reintroduce indices; it is not an independent validation
algorithm. For an empty final train set it is vacuously True; inspect
train_nonempty before treating a fold as usable. Leakage and group
overlap are reported as values instead of raising; malformed inputs and
splitter configuration errors still raise.
Time columns are outer envelopes, not continuous windows. CPCV and
interleaved group folds can contain several disjoint positional blocks, so
train and test envelopes may overlap even when
final_overlap_fraction == 0. train_block_count and
test_block_count expose that layout; the counts refer to contiguous
runs of positional indices.
The audit requires the inherited BaseTemporalSplitter.split
implementation. A subclass overriding split() is rejected because its
returned folds may diverge from the auditable candidate → purge → embargo
→ finalization pipeline. Custom subclasses should use
_iter_test_indices, _candidate_train_idx, and
_finalize_train_idx instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cv
|
BaseTemporalSplitter
|
A |
required |
X
|
NDArrayAny | DataFrame
|
Feature matrix or other sized sample container. Its length must
match the times bound to |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
A DataFrame with one row per fold. Columns contain the zero-based fold |
DataFrame
|
number; candidate, final, and test sizes; train non-emptiness; rows |
DataFrame
|
removed at purge, embargo, and finalization; indices added by custom |
DataFrame
|
finalization; candidate and final temporal-overlap fractions; |
DataFrame
|
contiguous block counts; outer train/test time envelopes; and |
DataFrame
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
if |
ValueError
|
if |
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> from purgedcv import PurgedKFold, audit_splitter
>>> pred = pd.date_range("2024-01-01", periods=20, freq="D")
>>> evalu = pred + pd.Timedelta(days=1)
>>> cv = PurgedKFold(
... 4, prediction_times=pred, evaluation_times=evalu,
... purge_horizon="1D", embargo_observations=2,
... )
>>> report = audit_splitter(cv, np.zeros((20, 1)))
>>> report.shape[0]
4
>>> bool(report["final_overlap_fraction"].eq(0.0).all())
True
purgedcv.diagnostics.compute_overlap_fraction ¶
compute_overlap_fraction(train_idx: NDArrayAny, test_idx: NDArrayAny, prediction_times: TimesLike, evaluation_times: TimesLike, *, purge_horizon: HorizonLike | None = None) -> float
Return the fraction of training rows whose half-open label horizon
overlaps any test horizon, optionally padded by purge_horizon.
Unlike assert_no_temporal_leakage, this does not raise when it
finds leakage: it returns 0.0 for clean splits and 1.0 when every
training row leaks. Malformed indices, times, and horizons still raise
validation errors. Useful for logging splitter health metrics or for
debugging a splitter that produces unexpected behavior.
purge_horizon defaults to no padding. Reproducing an
audit_splitter report's candidate_overlap_fraction also requires
its pre-purge candidate indices; the indices yielded by cv.split() are
final and instead reproduce final_overlap_fraction.
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> from purgedcv.diagnostics import compute_overlap_fraction
>>> pred = pd.Series(pd.date_range("2024-01-01", periods=20, freq="D"))
>>> evalu = pred + pd.Timedelta(days=1)
>>> compute_overlap_fraction(np.arange(5), np.arange(10, 15), pred, evalu)
0.0
>>> compute_overlap_fraction(
... np.arange(10, 15), np.arange(10, 15), pred, evalu
... )
1.0
purgedcv.diagnostics.assert_no_temporal_leakage ¶
assert_no_temporal_leakage(train_idx: NDArrayAny, test_idx: NDArrayAny, prediction_times: TimesLike, evaluation_times: TimesLike, *, purge_horizon: HorizonLike | None = None) -> None
Raise TemporalLeakageError if any training row's label horizon
overlaps any test label horizon, optionally padded on both sides by
purge_horizon.
Test horizons are checked as a union of half-open intervals, not as one convex hull. This matters for CPCV folds whose test groups are intentionally non-contiguous.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
train_idx
|
NDArrayAny
|
positional indices of training rows. |
required |
test_idx
|
NDArrayAny
|
positional indices of test rows. |
required |
prediction_times
|
TimesLike
|
prediction times for all rows. |
required |
evaluation_times
|
TimesLike
|
evaluation times for all rows. |
required |
purge_horizon
|
HorizonLike | None
|
optional padding (default: |
None
|
Raises:
| Type | Description |
|---|---|
TemporalLeakageError
|
with the offending training row index and the two overlapping intervals in the message. |
Examples:
purgedcv.diagnostics.assert_groups_disjoint ¶
Raise GroupLeakageError if any group identifier appears in
both train_idx and test_idx.
Used by group-aware splitters to verify that no entity (patient, asset, user, etc.) is represented in both training and test of the same fold. The error message names a representative overlapping group plus the total count of overlapping groups, so the caller can scope follow-up.
Examples:
purgedcv.diagnostics.assert_embargo_respected ¶
assert_embargo_respected(train_idx: NDArrayAny, test_idx: NDArrayAny, prediction_times: TimesLike, evaluation_times: TimesLike, embargo: HorizonLike | None = None, *, embargo_observations: int | None = None, embargo_fraction: float | None = None) -> None
Raise EmbargoViolationError when an embargo is violated.
The duration, observation-count, and fractional modes have the same
mutually-exclusive semantics as purgedcv.apply_embargo. Exactly
one mode must be supplied; omitting all three is treated as a configuration
error rather than a successful audit.
Embargo is asymmetric: rows before test boundaries are never flagged. Zero-width modes are identities.
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> from purgedcv.diagnostics import assert_embargo_respected
>>> pred = pd.Series(pd.date_range("2024-01-01", periods=20, freq="D"))
>>> evalu = pred + pd.Timedelta(days=1)
>>> assert_embargo_respected(
... np.array([18]), np.arange(5, 10), pred, evalu, embargo="2D"
... )
Exceptions¶
purgedcv.TemporalCVError ¶
Bases: ValueError
Base class for all purged-cross-validation errors.
purgedcv.TemporalLeakageError ¶
Bases: TemporalCVError
Raised when a training row's label horizon overlaps the test horizon.
purgedcv.EmbargoViolationError ¶
Bases: TemporalCVError
Raised when a training row falls inside the post-test embargo window.
purgedcv.GroupLeakageError ¶
Bases: TemporalCVError
Raised when a group_id appears in both training and test of the same fold.