cleverly.learners.CrossFitPlan#

class cleverly.learners.CrossFitPlan(n_folds: int = 10, learner_folds: int = 5, scheme: str = 'stratified', stratify_by: tuple[str, ...] = (), random_state: int | None = None, repeats: int = 1)[source]#

The fold policy a fit declared, as distinct from the split it got.

Every field is a number or a string, so a plan is comparable, hashable and serialisable, and says nothing about any particular dataset. What a plan realises on one is a Folds, and the two can differ: resolve_n_folds() caps n_folds at the rarest stratum and again at the cluster count, both with a warning at fit time and no trace afterwards. Recording the plan beside the realised count is what makes “why did my 10-fold fit run 3 folds?” answerable from a saved result.

Built from the estimator’s own keyword arguments by crossfit_plan() and held on TMLEConfig, exactly as TargetingSpec is – so the settings appear once and cannot drift.

Parameters:
n_foldsint

Outer folds, the ones that make the nuisance predictions out of fold. 1 means cross_fit=False.

learner_foldsint

Inner folds, which score Super Learner’s candidates inside one outer training fold. A separate declaration rather than the same one: this is model selection, not what makes a prediction out of fold, and its stratum is the learner’s own target – the treatment for the mechanism, the outcome for the regression.

schemestr

Which family of split the fit used. "supplied" means a caller handed over exact assignments and nothing was generated. "none" means the fit drew no split. The other two are resolved from what the data declared rather than chosen: "grouped" whenever id= named clusters, and "vfold" otherwise.

stratify_bytuple of str

What the outer folds were checked against, as user-facing names. Empty on every fit this version runs, because it draws no split that balances the data it then conditions on. A copied or modified estimator can declare names here, and its fit then refuses.

random_stateint or None

Seed for generated outer splits and repeat-specific learner state. Under scheme="supplied", the assignments ignore it while learner and collaborative selection folds remain seeded. A seed is not enough to reproduce a generated split on its own; see cleverly.provenance.

repeatsint

How many independent draws of the whole split the fit combines by median. 1 is an ordinary fit. A count layered over whichever scheme the data resolved to, not a scheme of its own – repeating a grouped split gives grouped splits.

Methods#

CrossFitPlan.describe() → str[source]#

Return a readable description.

Returns:
str

One line naming the fold count, the repeats, and the stratification.

CrossFitPlan.seeds() → tuple[int | None, ...][source]#

One seed per repeat, for the fold draws to combine.

Spawned from random_state rather than derived by addition, so the draws are independent rather than merely different, and a repeated fit stays reproducible under a seed. random_state=None yields None per repeat, because the plan is a declaration and the caller declared no seed. The engine does not draw with None: _repeat_draws() replaces each None with a fresh seed from operating-system entropy, in the same [0, 2**31 - 2] range, before it draws the folds. That seed reaches the folds, the learners and the C-TMLE selection folds, and seed records it on the result. The plan keeps random_state=None, so the result still says that the caller fixed no seed.

One repeat passes random_state straight through rather than spawning from it, which is what makes repeats=1 bit-for-bit an ordinary fit rather than merely an equivalent one. tests/unit/test_repeated_crossfit.py enforces that.

Returns:
tuple of int or None

One seed per repeat, derived from random_state.

Attributes#

CrossFitPlan.cross_fit#

Whether the outer nuisance fits are cross-fitted at all.

CrossFitPlan.learner_folds: int = 5#
CrossFitPlan.n_folds: int = 10#
CrossFitPlan.random_state: int | None = None#
CrossFitPlan.repeated#

Whether more than one draw of the split was combined.

CrossFitPlan.repeats: int = 1#
CrossFitPlan.scheme: str = 'stratified'#
CrossFitPlan.stratify_by: tuple[str, ...] = ()#