cleverly.longitudinal.DynamicRegimen#
- class cleverly.longitudinal.DynamicRegimen(label: str, plan: tuple[RuleNode, ...], rule_kind: FunctionKind | None = None)[source]#
A plan whose nodes may be rules rather than constants.
rule_kind="known"declares that every callable node is a fixed rowwise function of one unit’s available history, chosen independently of the analysis sample. A rule that estimates a threshold, or otherwise aggregates across the sample, defines a different, data-adaptive target, and inference for it needs conditions this path does not check. No code can inspect a closure, so the declaration is the check.refuse_regimen_rules()refusesNoneand"estimated"when the regimen is built, andLTMLE.fitrefuses them again before any learner.DynamicRegimen("treat once L2 rises", (0, lambda h: h["L2"] > 0), rule_kind="known")
A node can hold a known policy density instead, written as a
Stochasticnode. The unit then draws its arm at that node with probability \(q_t(a \mid H_t)\):DynamicRegimen("mix", (Stochastic(q1, "q1", density_kind="known"), "low"))
A rule node is handed the history frame, which holds no earlier treatment. A rule that must read an arm drawn at an earlier policy node, such as “continue the arm drawn at the first node”, is written as a one-hot
Stochasticnode. That node reads the policy frame, andresolve_plans()resolves a one-hot density as the rule it equals.- Parameters:
- label
str What the reported parameter is named by, exactly as for
Regimen.- plan
tuple One entry per time point. An entry is a categorical treatment label, meaning that label for everybody at that node, a callable \(d_t(H_t)\) handed that node’s history frame and returning one arm per row, or a
Stochasticnode declareddensity_kind="known", whose density function is handed that node’s policy frame and returns one probability per level. Mixing them is the ordinary case: “treat at the first node, then keep treating only while the biomarker stays high” is a constant followed by a rule. The regimen stores the plan as a tuple, so a list or an iterator is read once, when it is built. A single label, callable or policy is not a plan and raisesDataError.- rule_kind{“known”, “estimated”}
orNone The declaration that every callable node of
planis a known function. One declaration covers every node."known"is the one value a fit accepts for a plan with a callable node.None, the default, and"estimated"raiseCapabilityErrorfor such a plan, and a plan of labels and policy nodes alone is exempt. Each policy node carries its owndensity_kind. Any other value raisesDataError. It is the last field, soDynamicRegimen(label, plan)keeps its positional order.
- label
- Attributes:
n_timesintThe number of treatment nodes, one for each entry of
plan.has_policyboolWhether any node of
planis a stochastic policy or a modified treatment policy.
Methods#
- DynamicRegimen.assignment(data: LongitudinalData) Any[source]#
Evaluate every node’s rule and return the
(n, T)arm matrix.Called once per fit, in
cleverly.longitudinal.LTMLE.fit(), and the matrix is what every mask, mechanism design and clever covariate then reads. Evaluating it once rather than at each use is not only cheaper: a rule that is not a deterministic function of the frame would otherwise let the follower masks disagree with the designs the mechanism was evaluated at, and the fit would be answering for no single regimen at all. The callable receives a frame for ergonomic vectorization, but its supported contract is rowwise: it must not read sample summaries or learn a rule from those rows.A rule is asked for an arm on every row that is still in the study before the node – uncensored through
t - 1, and on a survival fit event-free throught - 1as well, since a unit that has had the event has no treatment decision attfor a rule to make. Off that set the history is the zero fillcovariate_history()puts there, so whatever the rule returns is meaningless; it is replaced by a sentinel rather than validated, because such a row is masked out of every regression and every influence curve, and the only way it could still matter is by putting ananinto a design matrix that a learner is called on.A policy node assigns no arm. Its column holds the label each unit was observed to receive, because the mechanism of a policy plan is evaluated at the observed history.
policy_density()evaluates the policy itself.It runs
refuse_regimen_rules()before it calls any rule, because a modified regimen can reach it directly with a declaration this version refuses.- Parameters:
- data
LongitudinalData The validated panel, which supplies each node’s history frame and the rows still in the study.
- data
- Returns:
ndarray(n, T)object array of treatment labels, one column per node. A row that has left the study before a node holdsNoneat that node.
- Raises:
CapabilityErrorIf a node is callable and
rule_kindisNoneor"estimated", or a policy node does not declaredensity_kind="known".DataErrorIf
rule_kindis not one of the three states, or a rule raises or returns other than one arm per row.
- DynamicRegimen.is_mtp(time: int) bool[source]#
Whether
time’s arm is a modified treatment policy of the unit’s own treatment.- Parameters:
- time
int The treatment node, counted from one.
- time
- Returns:
- bool
Truewhen the entry ofplanattimeis a policy object, such asShiftorModifiedPolicy.
- DynamicRegimen.is_policy(time: int) bool[source]#
Whether
time’s arm is drawn from a known policy density.- Parameters:
- time
int The treatment node, counted from one.
- time
- Returns:
- bool
Truewhen the entry ofplanattimeis aStochasticnode.
- DynamicRegimen.is_rule(time: int) bool[source]#
Whether
time’s arm is decided by a rule rather than declared.
- DynamicRegimen.is_tilt(time: int) bool[source]#
Whether
time’s arm is drawn from an odds tilt of the treatment mechanism.- Parameters:
- time
int The treatment node, counted from one.
- time
- Returns:
- bool
Truewhen the entry ofplanattimeis anIncrementalnode.
- DynamicRegimen.policy_density(data: LongitudinalData, time: int) FloatArray[source]#
Evaluate the policy node at
timeand check it before it is used.The density function is handed
policy_frame(). It returns an(n, K)array whose columns followdata.treatment_levels[time - 1], or a dataframe whose columns are exactly those levels, which this reorders by level. The label route exists because a permutation of the columns of an array is silent.The check reads the rows still in the study before the node: uncensored and event-free through
time - 1. Every other row is set to a zero row, which no regression and no influence curve reads.- Parameters:
- data
LongitudinalData The validated panel.
- time
int The policy node, counted from one.
- data
- Returns:
ndarray(n, K)policy probabilities, zero on the rows that left the study.
- Raises:
CapabilityErrorIf the node does not declare
density_kind="known".DataErrorIf
timeis not a policy node, if the density function raises, or if it returns other than a probability simplex per reachable row over the levels.
Attributes#
- DynamicRegimen.has_mtp#
Whether any node of
planis a modified treatment policy.
- DynamicRegimen.has_policy#
Whether any node of
planis a stochastic policy or a modified treatment policy.
- DynamicRegimen.has_rule#
Whether any node of
planis a rule \(d_t(H_t)\).
- DynamicRegimen.has_tilt#
Whether any node of
planis an odds tilt of the treatment mechanism.
- DynamicRegimen.n_times#
The number of treatment nodes, one for each entry of
plan.