cleverly.sensitivity.PositivityReport#
- class cleverly.sensitivity.PositivityReport(propensity_quantiles: dict[str, dict[float, float]], tail_mass: dict[float, dict[str, float]], effective_sample_size: dict[str, dict[str, float]], weight_share: dict[str, dict[str, float]], truncated: dict[str, float], clever_covariate_max: dict[str, float], bounds: tuple[float, float], n: int, mechanisms: dict[str, dict[str, float]] = <factory>, composed_excluded: tuple[str, ...] = (), nuisance_bound: float = 0.0, simplex_deviation: float = 0.0, n_repeats: int = 1, backend: str | None = None, group_score_load: dict[str, ~cleverly.sensitivity.positivity.GroupScoreLoadRow] = <factory>, group_score_load_omissions: dict[str, str] = <factory>, treatment_mechanism: str = 'estimated')[source]#
Overlap diagnostics for a fitted TMLE.
- Parameters:
- propensity_quantiles
dictofstrtodictoffloattofloat Quantiles of
g(W), overall and within each treatment arm.- tail_mass
dictoffloattodictofstrtofloat Fraction of units with
g(W)below each threshold, and above its mirror.- effective_sample_size
dictofstrtodictofstrtofloat Kish ESS of the inverse-probability weights per arm, with the nominal arm size alongside for comparison.
- weight_share
dictofstrtodictofstrtofloat Share of the total weight held by the largest 1% and 5% of weights.
- truncated
dictofstrtofloat Count and fraction of propensity scores clipped by the truncation bounds, and the most extreme untruncated value.
- clever_covariate_max
dictofstrtofloat Largest reconstructed absolute clever-covariate value per targeted estimand family. This is a covariate-scale diagnostic, not a score contribution or an observation’s influence on the estimate.
- group_score_load
dictofstrtoGroupScoreLoadRow Absolute residual-multiplier concentration for each targeted family. A row holds fourteen keys:
equation,n_total,n_targeted,effective,targeted_ratio,total_ratio,top_1pct,top_5pct,max_load,zero_load,lower_bound,upper_bound,clipped_countandclipped_fraction. It carries no draw count, whichPositivityReport.n_repeatsrecords once for the whole fit. The fitted fluctuation retainsabs(w_i * h_ij)on the exact rows and with the exact weights used to evaluate each score equation. This report computes one Kish concentration ratio per column, then retains the most concentrated column and names it underequation. Every metric in a group row comes from that one column. It never sums columns with different units.effectiveis the Kish-equivalent row count of that absolute load. It is a descriptive concentration index. It is not the estimator’s information, actual influence, or the sample size used for its standard error.targeted_ratiodivides it by the rows in the fitted score mask.total_ratiodivides it by the full data row count, so outcome missingness and intermediate selection remain visible. The two top shares,max_load, andzero_loaddescribe the same selected column.n_targetedcounts rows in the fitted score mask, including a row whose selected equation has a structural zero. It does not count nonzero residual contributions.lower_bound,upper_bound,clipped_count, andclipped_fractiondescribe the treatment-mechanism bound used by that group. ATT and ATC therefore reportg_bounds_conditionalrather than the marginal bound.The groups that reach this table through
support()aremean,att,atcandmsm. A regime, shift or incremental fit gets its own support report from that method instead and so gets no row here;regimeandipsiappear only whenpositivity_report()is called directly.mtpnever appears at all, becausepolicies=needs a continuous treatment and this report refuses one.- group_score_load_omissions
dictofstrtostr Machine-readable reasons an exact group diagnostic or one of its components could not be reported. Exact score-weight loads are never reconstructed from a generic submodel, and clipping for a targeted treatment mechanism is unavailable unless its fitted clipping mask was retained.
- bounds
tupleoffloat Truncation bounds the fit applied to the treatment mechanism.
- n
int Number of observations the diagnostics were computed over.
- mechanisms
dictofstrtodictofstrtofloat Overlap for the other denominators in the clever covariate:
P(Delta = 1 | A, W)when outcomes are missing, andP(Z = z | A, W)for a controlled direct effect. Every row carries the same nine keys:min,q01,q05andmedianof the mechanism as fitted;clippedandclipped_fractionfor the cells the bound moved; andess_ratio,top_1pctandtop_5pctfor the load the weights leave behind. Empty when neither applies.The three load measures are taken over the weights the estimating equation actually forms: the bounded mechanism at each unit’s realised arm, times that unit’s observation weight, over the rows whose residual it multiplies. They are on the same scale as the propensity’s
effective_sample_sizeandweight_share, and fold in the design for the same reason those do, so the two tables can be read side by side.These deserve reporting for exactly the reason
gdoes: they enter the estimating equation as a denominator, so a value near zero can give one observation a large residual multiplier. Unlikegthey are one-sided – only the approach to zero matters – and they are easy to overlook, because a fit can have perfectly healthy propensity overlap and still be resting on a handful of rows that were very unlikely to be observed at all.A derived row reports the complete denominator for a missing-outcome fit (
P(A=a,Delta=1|W)), a controlled direct effect (P(A=a,Z=z|W)), or both at once (P(A=a,Delta=1,Z=z|W)). Thezin those names is the level the fit targets, which the factor row beside them states outright. It is not a further fitted mechanism and it was never held to a bound of its own: the estimator truncates each factor separately and multiplies. So itsminand quantiles stay on the untruncated product, as the fitted rows’ do, while itsclippedcounts the cells where any factor moved – which is the truncation this denominator underwent, and which a comparison of the two products would miss wherever a zero in one factor hides a clip in another. A per-arm outcome-adaptive fit regresses each factor onQbar(a, W), so its row names that design as the condition:P(A=a,Delta=1|Qbar(a,W)), with,Sadded under baseline strata.The derived row is the reason this table exists. Each factor can look comfortable on its own while their product concentrates the weight, because the denominator factors multiply. That does not imply their Kish ratios multiply. Only the product is what the clever covariate divides by. It describes the fit’s marginal-mean estimands – the
eyfamily, its contrasts, a regime, and a marginal structural model. A fit that targets none of those gets its factor rows and no product row, because an ATT or ATC covariate divides byP(A = a)rather thang_a(W)and an incremental one drops the propensity entirely.- composed_excluded
tupleofstr Targeted groups the derived row does not describe, on a fit that has the factors to build one. Empty when every targeted group forms the product, and empty when no factor stands beside
gat all, because then there is no derived row for any group to be outside of.This is here so the omission explains itself. A silently absent row reads exactly like a fit with nothing to report, and those are the two readings that must never be confused: one says the denominator is healthy, the other says this diagnostic does not cover the estimand asked for.
summary()names these groups and what each divides by instead.- nuisance_bound
float The lower bound applied to the fitted mechanisms above. A derived row has no single such bound – see
_bound_label().- simplex_deviation
float Largest
|sum_a g(a | W) - 1|across rows after truncation, and0for a two-armed fit, where the complement form preserves the sum exactly.Non-zero is expected rather than alarming: with more than two arms the bounds are applied arm by arm and deliberately not renormalised, because rescaling a row back onto the simplex can push a column below the floor and so undo the only thing truncation is for. The number is reported because it is the size of that deliberate inconsistency, and a large value says the bounds are binding hard – which is a positivity finding, not a bookkeeping one. It does not bias the plug-in: the plug-in averages targeted predictions and contains no mechanism at all.
- n_repeats
int Cross-fitting draws the fit combined. Everything else here describes the first of them, because overlap is a property of one fitted mechanism and a combination of draws would describe one no estimate came from.
- backend
strorNone Dataframe backend
to_frame()returns whendatais omitted.- treatment_mechanism{“estimated”, “known”}
Whether
g(W)is an estimate or the mechanism the data declares known. A known mechanism never moves under the fitted bounds, because the fit refuses a bound pair that would move it.
- propensity_quantiles
Methods#
- PositivityReport.summary() str[source]#
A printable overlap report.
- Returns:
strA printable report, one line per reported quantity.
Attributes#
- PositivityReport.backend: str | None = None#
Name of the dataframe backend the fit’s data arrived in, so that
to_frame()honours “results come back in the backend you passed in” without a caller having to thread the container back in by hand.
- PositivityReport.n_repeats: int = 1#
How many cross-fitting draws the fit combined. Everything above describes the first of them, and this is here so a reader knows that. Overlap is a property of one fitted mechanism, and combining
Rpropensity vectors would produce a perfectly good estimate ofgthat is nonetheless not the object any reportedpsiwas computed from – a different aggregation from the one the estimates use, under the same heading. The draws share the data and differ only in the split, so their overlap is near identical in practice; when it is not, that is itself worth seeing rather than averaging away.
- PositivityReport.severity#
Which tier this report’s own thresholds place the fit in.
- Returns:
- {“adequate”, “strain”, “serious”}
The tier behind
verdict(), for a caller that must branch on it.