cleverly.SplitPlan#

class cleverly.SplitPlan(assignments: Sequence[Sequence[int]] | Sequence[ndarray[tuple[Any, ...], dtype[int64]]] | ndarray[tuple[Any, ...], dtype[int64]], source_fingerprint: str | None = None, provenance: tuple[FoldOrigin, ...] | None = None)[source]#

Reusable cross-fitting assignments for every repeat.

A fit accepts a plan only when the plan records how random_partition() drew each repeat. split_plan and from_folds() write that record. Before any learner runs, verify() draws each repeat again and refuses labels that differ. A plan built from labels alone stays constructible, and every fit refuses it.

Parameters:
assignmentssequence of sequence of int, or ndarray

Fold labels in repeat-major order, one label per row in each repeat. Any nested sequence is accepted, and a two-dimensional integer array is read as one row per repeat. The value is copied into nested tuples, so the plan does not change when the source does.

source_fingerprintstr or None, default=None

The data fingerprint the labels were realised on, as data_fingerprint records it. split_plan fills it in, and validate() then refuses data that fingerprint differently. None is a plan bound to no data, which is what unbound() returns.

provenancetuple of FoldOrigin or None, default=None

One generator record per repeat, in repeat order. None for labels with no record: a hand-built plan or a stratified split. A fit refuses a plan whose provenance is None.

Attributes:
nint

Return the number of rows in each repeat.

n_foldsint

Return the number of folds in each repeat.

n_repeatsint

Return the number of repeats.

fingerprintstr

Return the provenance-compatible fingerprint of every repeat.

See also

cleverly.learners.CrossFitPlan

Policy that generates folds from data.

cleverly.learners.Folds

Mutable-array materialization of one repeat.

cleverly.learners.random_partition

The generator a plan must record.

Examples

>>> from cleverly import SplitPlan
>>> from cleverly.learners import random_partition
>>> plan = SplitPlan.from_folds([random_partition(6, 3, seed=0)])
>>> plan.assignments
((2, 1, 0, 1, 2, 0),)
>>> plan.provenance[0].seed
0
>>> plan.verify(n=6)

Methods#

classmethod SplitPlan.from_folds(folds: Iterable[Folds], *, source_fingerprint: str | None = None) → SplitPlan[source]#

Copy repeat assignments, and their generator records, from realized folds.

Parameters:
foldsiterable of Folds

Realized folds in repeat order.

source_fingerprintstr or None, default=None

The data fingerprint these folds were realised on. None leaves the plan bound to no data.

Returns:
SplitPlan

Immutable copies of the assignments. provenance holds each repeat’s Folds.origin when every repeat has one, and is None otherwise.

Examples

>>> from cleverly import SplitPlan
>>> from cleverly.learners import random_partition
>>> plan = SplitPlan.from_folds([random_partition(4, 2, seed=s) for s in (1, 2)])
>>> plan.n_repeats, [origin.seed for origin in plan.provenance]
(2, [1, 2])
SplitPlan.to_folds() → tuple[Folds, ...][source]#

Return fresh mutable-array fold objects for every repeat.

Each fold object carries its repeat’s FoldOrigin when the plan records one, so a fit on this plan returns a plan with the same record.

Returns:
tuple of Folds

Independent materializations in repeat order.

SplitPlan.unbound() → SplitPlan[source]#

Return this plan bound to no data, with its generator record kept.

A refit on the same rows with one column replaced changes the data fingerprint and moves no row. The unbound plan serves that refit, and a caller who means to reuse the labels on other rows asks for it by name. verify() still draws every repeat again, so an unbound plan cannot carry labels the record does not produce.

Returns:
SplitPlan

The same assignments and provenance, with source_fingerprint set to None.

Examples

>>> from cleverly import SplitPlan
>>> from cleverly.learners import random_partition
>>> bound = SplitPlan.from_folds([random_partition(4, 2, seed=3)], source_fingerprint="ab")
>>> free = bound.unbound()
>>> free.source_fingerprint is None, free.provenance == bound.provenance
(True, True)
SplitPlan.validate(*, n: int, cluster: ndarray[tuple[Any, ...], dtype[int64]] | None = None, treatment: ndarray[tuple[Any, ...], dtype[float64]] | None = None, stratify: ndarray[tuple[Any, ...], dtype[float64]] | None = None, source_fingerprint: str | None = None) → tuple[Folds, ...][source]#

Validate assignments against the data and return fresh folds.

Parameters:
nint

Number of data rows.

clusterndarray, optional

Cluster code for each row.

treatmentndarray, optional

Categorical treatment code for each row.

stratifyndarray, optional

Requested stratification code for each row. Named as in make_folds(), which balances on the same vector; strata is the survey design role strata carries.

source_fingerprintstr or None, default=None

Fingerprint of the data being validated. Compared with source_fingerprint when both are present.

Returns:
tuple of Folds

Fresh fold realizations in repeat order.

SplitPlan.verify(*, n: int, cluster: ndarray[tuple[Any, ...], dtype[int64]] | None = None) → None[source]#

Refuse labels that the recorded generator does not produce on these rows.

Each repeat is drawn again with random_partition(), from its recorded fold count and seed, and compared label for label. The recorded scheme must be "grouped" when the data declare clusters and "vfold" when they do not. The draw reads n and the cluster labels only, so a plan that passes holds labels the recorded fold count and seed produce.

What this rules out is an assignment no draw of this package made: a hand-built one, a stratified one, and one edited after the draw. What it cannot rule out is the declaration itself. The fold count and the seed come from the caller, so a caller who searched for a seed gets a plan this method accepts. A fit records that declaration and has nothing local to audit it against. result.split_plan of an earlier fit is the source this contract is written for.

Parameters:
nint

Number of data rows.

clusterndarray or None, default=None

Cluster code for each row, or None for data without clusters.

Raises:
DataError

If the plan has no generator record, if the row count differs, or if a repeat names another generator, the other scheme, or labels its record does not produce. The message names the repeat.

Examples

>>> from cleverly import SplitPlan
>>> from cleverly.learners import random_partition
>>> SplitPlan.from_folds([random_partition(6, 2, seed=5)]).verify(n=6)

Attributes#

SplitPlan.fingerprint#

Return the provenance-compatible fingerprint of every repeat.

SplitPlan.n#

Return the number of rows in each repeat.

SplitPlan.n_folds#

Return the number of folds in each repeat.

SplitPlan.n_repeats#

Return the number of repeats.

SplitPlan.provenance: tuple[FoldOrigin, ...] | None = None#
SplitPlan.source_fingerprint: str | None = None#
SplitPlan.assignments: Sequence[Sequence[int]] | Sequence[ndarray[tuple[Any, ...], dtype[int64]]] | ndarray[tuple[Any, ...], dtype[int64]]#