Results Classes#

Dataclass containers for estimation results from various estimators.

DiDResults#

Results from basic DifferenceInDifferences estimation.

class diff_diff.DiDResults[source]

Bases: BaseResults

Results from a Difference-in-Differences estimation.

Provides easy access to coefficients, standard errors, confidence intervals, and summary statistics in a Pythonic way.

att

Average Treatment effect on the Treated (ATT).

Type:

float

se

Standard error of the ATT estimate.

Type:

float

t_stat

T-statistic for the ATT estimate.

Type:

float

p_value

P-value for the null hypothesis that ATT = 0.

Type:

float

conf_int

Confidence interval for the ATT.

Type:

tuple[float, float]

n_obs

Number of observations used in estimation.

Type:

int

n_treated

Number of treated units/observations.

Type:

int

n_control

Number of control units/observations.

Type:

int

Attributes

att

se

t_stat

p_value

conf_int

n_obs

is_significant

Check if the ATT is statistically significant at the alpha level.

significance_stars

Return significance stars based on p-value.

Methods

summary([alpha])

Generate a formatted summary of the estimation results.

to_dict()

Convert results to a dictionary.

to_dataframe()

Convert results to a pandas DataFrame.

att: float
se: float
t_stat: float
p_value: float
conf_int: Tuple[float, float]
n_obs: int
n_treated: int
n_control: int
alpha: float = 0.05
coefficients: Dict[str, float] | None = None
vcov: ndarray | None = None
residuals: ndarray | None = None
fitted_values: ndarray | None = None
r_squared: float | None = None
inference_method: str = 'analytical'
n_bootstrap: int | None = None
n_clusters: int | None = None
p_val_type: str | None = None
bootstrap_distribution: ndarray | None = None
survey_metadata: Any | None = None
vcov_type: str | None = None
cluster_name: str | None = None
conley_lag_cutoff: int | None = None
df_convention: str | None = None
inference_df: float | None = None
__repr__()[source]

Concise string representation.

Return type:

str

property coef_var: float

SE / abs(ATT). NaN when ATT is 0 or SE non-finite.

Type:

Coefficient of variation

summary(alpha=None)[source]

Generate a formatted summary of the estimation results.

Parameters:

alpha (float, optional) – Accepted for signature uniformity. The stored intervals were computed at fit time; a value different from the stored alpha raises ValueError rather than silently recomputing or relabeling. Re-fit at the desired alpha instead.

Returns:

Formatted summary table.

Return type:

str

print_summary(alpha=None)[source]

Print the summary to stdout.

Parameters:

alpha (float | None)

Return type:

None

to_dict()[source]

Convert results to a dictionary.

Returns:

Dictionary containing all estimation results.

Return type:

Dict[str, Any]

to_dataframe()[source]

Convert results to a pandas DataFrame.

Returns:

DataFrame with estimation results.

Return type:

pd.DataFrame

property is_significant: bool

Check if the ATT is statistically significant at the alpha level.

property significance_stars: str

Return significance stars based on p-value.

__init__(att, se, t_stat, p_value, conf_int, n_obs, n_treated, n_control, alpha=0.05, coefficients=None, vcov=None, residuals=None, fitted_values=None, r_squared=None, inference_method='analytical', n_bootstrap=None, n_clusters=None, p_val_type=None, bootstrap_distribution=None, survey_metadata=None, vcov_type=None, cluster_name=None, conley_lag_cutoff=None, df_convention=None, inference_df=None)
Parameters:
Return type:

None

MultiPeriodDiDResults#

Results from MultiPeriodDiD event study estimation.

class diff_diff.MultiPeriodDiDResults[source]

Bases: BaseResults

Results from a Multi-Period Difference-in-Differences estimation.

Provides access to period-specific treatment effects as well as an aggregate average treatment effect.

period_effects

Dictionary mapping period identifiers to their PeriodEffect objects. Contains all estimated period effects (pre and post, excluding the reference period which is normalized to zero).

Type:

dict[any, PeriodEffect]

avg_att

Average Treatment effect on the Treated across post-periods only.

Type:

float

avg_se

Standard error of the average ATT.

Type:

float

avg_t_stat

T-statistic for the average ATT.

Type:

float

avg_p_value

P-value for the null hypothesis that average ATT = 0.

Type:

float

avg_conf_int

Confidence interval for the average ATT.

Type:

tuple[float, float]

n_obs

Number of observations used in estimation.

Type:

int

n_treated

Number of treated units/observations.

Type:

int

n_control

Number of control units/observations.

Type:

int

pre_periods

List of pre-treatment period identifiers.

Type:

list

post_periods

List of post-treatment period identifiers.

Type:

list

reference_period

The reference (omitted) period. Its coefficient is zero by construction and it is excluded from period_effects.

Type:

any, optional

interaction_indices

Mapping from period identifier to column index in the full variance-covariance matrix. Used internally for sub-VCV extraction (e.g., by HonestDiD and PreTrendsPower).

Type:

dict, optional

Attributes

period_effects

att

pre_periods

post_periods

reference_period

interaction_indices

pre_period_effects

Pre-period effects only (for parallel trends assessment).

post_period_effects

Post-period effects only.

period_effects: Dict[Any, PeriodEffect]
avg_att: float
avg_se: float
avg_t_stat: float
avg_p_value: float
avg_conf_int: Tuple[float, float]
n_obs: int
n_treated: int
n_control: int
pre_periods: List[Any]
post_periods: List[Any]
alpha: float = 0.05
coefficients: Dict[str, float] | None = None
vcov: ndarray | None = None
residuals: ndarray | None = None
fitted_values: ndarray | None = None
r_squared: float | None = None
reference_period: Any | None = None
interaction_indices: Dict[Any, int] | None = None
survey_metadata: Any | None = None
inference_method: str = 'analytical'
n_bootstrap: int | None = None
n_clusters: int | None = None
vcov_type: str | None = None
cluster_name: str | None = None
conley_lag_cutoff: int | None = None
df_convention: str | None = None
inference_df: float | None = None
event_study_df: Dict[Any, float] | None = None
property att: float
property se: float
property conf_int: Tuple[float, float]
property p_value: float
property t_stat: float
__repr__()[source]

Concise string representation.

Return type:

str

property pre_period_effects: Dict[Any, PeriodEffect]

Pre-period effects only (for parallel trends assessment).

property post_period_effects: Dict[Any, PeriodEffect]

Post-period effects only.

property coef_var: float

SE / abs(overall ATT). NaN when ATT is 0 or SE non-finite.

Type:

Coefficient of variation

summary(alpha=None)[source]

Generate a formatted summary of the estimation results.

Parameters:

alpha (float, optional) – Accepted for signature uniformity. The stored intervals were computed at fit time; a value different from the stored alpha raises ValueError rather than silently recomputing or relabeling. Re-fit at the desired alpha instead.

Returns:

Formatted summary table.

Return type:

str

print_summary(alpha=None)[source]

Print the summary to stdout.

Parameters:

alpha (float | None)

Return type:

None

get_effect(period)[source]

Get the treatment effect for a specific period.

Parameters:

period (any) – The period identifier.

Returns:

The treatment effect for the specified period.

Return type:

PeriodEffect

Raises:

KeyError – If the period is not found in post-treatment periods.

to_dict()[source]

Convert results to a dictionary.

Returns:

Dictionary containing all estimation results.

Return type:

Dict[str, Any]

to_dataframe()[source]

Convert period-specific effects to a pandas DataFrame.

Returns:

DataFrame with one row per estimated period (pre and post).

Return type:

pd.DataFrame

property is_significant: bool

Check if the average ATT is statistically significant at the alpha level.

property significance_stars: str

Return significance stars for the average ATT based on p-value.

__init__(period_effects, avg_att, avg_se, avg_t_stat, avg_p_value, avg_conf_int, n_obs, n_treated, n_control, pre_periods, post_periods, alpha=0.05, coefficients=None, vcov=None, residuals=None, fitted_values=None, r_squared=None, reference_period=None, interaction_indices=None, survey_metadata=None, inference_method='analytical', n_bootstrap=None, n_clusters=None, vcov_type=None, cluster_name=None, conley_lag_cutoff=None, df_convention=None, inference_df=None, event_study_df=None)
Parameters:
Return type:

None

PeriodEffect#

Container for a single period’s treatment effect in event studies.

class diff_diff.PeriodEffect[source]

Bases: object

Treatment effect for a single time period.

period

The time period identifier.

Type:

any

effect

The treatment effect estimate for this period.

Type:

float

se

Standard error of the effect estimate.

Type:

float

t_stat

T-statistic for the effect estimate.

Type:

float

p_value

P-value for the null hypothesis that effect = 0.

Type:

float

conf_int

Confidence interval for the effect.

Type:

tuple[float, float]

period: Any
effect: float
se: float
t_stat: float
p_value: float
conf_int: Tuple[float, float]
__repr__()[source]

Concise string representation.

Return type:

str

property is_significant: bool

Check if the effect is statistically significant at 0.05 level.

property significance_stars: str

Return significance stars based on p-value.

__init__(period, effect, se, t_stat, p_value, conf_int)
Parameters:
Return type:

None

SyntheticDiDResults#

Results from SyntheticDiD estimation.

class diff_diff.SyntheticDiDResults[source]

Bases: BaseResults

Results from a Synthetic Difference-in-Differences estimation.

Combines DiD with synthetic control by re-weighting control units to match pre-treatment trends of treated units.

att

Average Treatment effect on the Treated (ATT).

Type:

float

se

Standard error of the ATT estimate (bootstrap, jackknife, or placebo-based).

Type:

float

t_stat

T-statistic for the ATT estimate.

Type:

float

p_value

P-value for the null hypothesis that ATT = 0.

Type:

float

conf_int

Confidence interval for the ATT.

Type:

tuple[float, float]

n_obs

Number of observations used in estimation.

Type:

int

n_treated

Number of treated units/observations.

Type:

int

n_control

Number of control units/observations.

Type:

int

unit_weights

Dictionary mapping control unit IDs to their synthetic weights. When survey weights are used, these are the composed effective weights (omega_eff = raw Frank-Wolfe * survey, renormalized) that were applied to produce the ATT, not the raw Frank-Wolfe solution.

Type:

dict

time_weights

Dictionary mapping pre-treatment periods to their time weights.

Type:

dict

pre_periods

List of pre-treatment period identifiers.

Type:

list

post_periods

List of post-treatment period identifiers.

Type:

list

variance_method

Method used for variance estimation: "bootstrap" (paper-faithful pairs bootstrap re-estimating ω and λ via Frank-Wolfe on each draw; Arkhangelsky et al. 2021 Algorithm 2 step 2, and R’s default synthdid::vcov(method="bootstrap")), "jackknife", or "placebo".

Type:

str

variance_effects

Method-specific per-iteration estimates: placebo treatment effects (for "placebo"), bootstrap ATT estimates with re-estimated weights per draw (for "bootstrap"), or leave-one-out estimates (for "jackknife"). The variance_method field disambiguates the contents. (The deprecated read-only alias placebo_effects returns this array and is removed in v4.0.0.)

Type:

np.ndarray, optional

pre_treatment_fit

Shape-only pre-treatment fit: the RMSE of the pre-period residual (treated mean minus synthetic control) after removing its mean. The Frank-Wolfe unit weights are fit on column-centered outcomes, so a constant level gap is not a fit failure (SDID differences it out) and is excluded here. NaN with a single pre-period.

Type:

float, optional

pre_treatment_level_gap

Signed mean pre-period gap, treated minus synthetic control. This is the constant offset absorbed by the DiD step; it is reported for inspection and never enters the poor-fit warning.

Type:

float, optional

pre_fit_placebo_rmse

Reference distribution for pre_treatment_fit: the shape-only pre-fit RMSE of each placebo fit in which a random set of n_treated control units is treated as if treated and the unit weights are re-estimated on the remaining controls (Algorithm 4 draws, fit-time zeta). At most 20 draws (min(n_bootstrap, 20)); None when no placebo fit is possible (no pseudo-control left, or a single pre-period).

Type:

np.ndarray, optional

pre_fit_placebo_pvalue

(1 + #{placebo RMSE >= treated RMSE}) / (1 + n_draws): the plus-one-adjusted rank of the treated units’ fit among the placebo fits — a fit diagnostic, not a treatment-effect p-value. The poor-fit warning fires at <= 0.05 (in-space placebo fit assessment, Abadie, Diamond & Hainmueller 2010; Abadie 2021), which needs at least 19 successful draws to be reachable.

Type:

float, optional

synthetic_pre_trajectory

Synthetic control trajectory in pre-treatment periods, shape (n_pre,). Equal to Y_pre_control @ omega_eff where omega_eff is the composed effective weight vector.

Type:

np.ndarray, optional

synthetic_post_trajectory

Synthetic control trajectory in post-treatment periods, shape (n_post,).

Type:

np.ndarray, optional

treated_pre_trajectory

Treated-unit mean trajectory in pre-treatment periods, shape (n_pre,). Survey-weighted when the fit used survey weights.

Type:

np.ndarray, optional

treated_post_trajectory

Treated-unit mean trajectory in post-treatment periods, shape (n_post,).

Type:

np.ndarray, optional

time_weights_array

The Frank-Wolfe time weights as a 1-D array (same values as the time_weights dict but order-stable and usable for re-estimation by sensitivity methods). Shape (n_pre,).

Type:

np.ndarray, optional

Attributes

att: float
se: float
t_stat: float
p_value: float
conf_int: Tuple[float, float]
n_obs: int
n_treated: int
n_control: int
unit_weights: Dict[Any, float]
time_weights: Dict[Any, float]
pre_periods: List[Any]
post_periods: List[Any]
alpha: float = 0.05
variance_method: str = 'placebo'
noise_level: float | None = None
zeta_omega: float | None = None
zeta_lambda: float | None = None
pre_treatment_fit: float | None = None
pre_treatment_level_gap: float | None = None
pre_fit_placebo_rmse: ndarray | None = None
pre_fit_placebo_pvalue: float | None = None
variance_effects: ndarray | None = None
n_bootstrap: int | None = None
survey_metadata: Any | None = None
synthetic_pre_trajectory: ndarray | None = None
synthetic_post_trajectory: ndarray | None = None
treated_pre_trajectory: ndarray | None = None
treated_post_trajectory: ndarray | None = None
time_weights_array: ndarray | None = None
__repr__()[source]

Concise string representation.

Return type:

str

__getstate__()[source]

Exclude the internal fit snapshot from pickling.

The snapshot retains outcome matrices, unit IDs, and survey weights to support post-hoc diagnostics (in_time_placebo, sensitivity_to_zeta_omega). Serialization would otherwise carry that panel state to wherever the pickle is sent, which is a privacy hazard for survey-weighted or sensitive fits.

Unpickled results keep the public fields (ATT, weights, trajectories, etc.); calling a diagnostic method that needs the snapshot raises a ValueError directing the user to re-fit.

Return type:

Dict[str, Any]

__setstate__(state)[source]

Restore from pickle, migrating the legacy field name.

Results pickled before the placebo_effects → variance_effects rename (<= 3.5.x) carry the old key in their state; map it so the stored variance draws survive and remain reachable through both variance_effects and the deprecated placebo_effects alias. Remove together with the alias in v4.0.0.

Parameters:

state (Dict[str, Any])

Return type:

None

property coef_var: float

SE / abs(ATT). NaN when ATT is 0 or SE non-finite.

Type:

Coefficient of variation

property placebo_effects: ndarray | None

Deprecated alias for variance_effects (removed in v4.0.0).

Deprecated since version 3.5.2: Renamed to variance_effects because the array’s contents are method-specific (placebo effects, bootstrap ATT draws, or leave-one-out estimates depending on variance_method).

summary(alpha=None)[source]

Generate a formatted summary of the estimation results.

Parameters:

alpha (float, optional) – Accepted for signature uniformity. The stored intervals were computed at fit time; a value different from the stored alpha raises ValueError rather than silently recomputing or relabeling. Re-fit at the desired alpha instead.

Returns:

Formatted summary table.

Return type:

str

print_summary(alpha=None)[source]

Print the summary to stdout.

Parameters:

alpha (float | None)

Return type:

None

to_dict()[source]

Convert results to a dictionary.

Returns:

Dictionary containing all estimation results.

Return type:

Dict[str, Any]

to_dataframe()[source]

Convert results to a pandas DataFrame.

Returns:

DataFrame with estimation results.

Return type:

pd.DataFrame

get_unit_weights_df()[source]

Get unit weights as a pandas DataFrame.

Returns:

DataFrame with unit IDs and their weights.

Return type:

pd.DataFrame

get_time_weights_df()[source]

Get time weights as a pandas DataFrame.

Returns:

DataFrame with time periods and their weights.

Return type:

pd.DataFrame

get_loo_effects_df()[source]

Per-unit leave-one-out ATT from the jackknife variance pass.

Requires variance_method='jackknife' (ValueError otherwise) and unit-level LOO granularity (NotImplementedError for the full-design survey jackknife path, which uses PSU-level LOO).

Available on:

  • non-survey jackknife fits (classical Arkhangelsky Algorithm 3).

  • pweight-only survey jackknife fits (Algorithm 3 with post-hoc ω_eff composition; PSU labels in survey_metadata come from implicit-PSU metadata but the LOO remains unit-level).

Blocked on:

  • full-design survey jackknife fits (strata / PSU / FPC set in SurveyDesign) - the underlying replicates are PSU-level τ̂_{(h,j)} (Rust & Rao 1996), not unit-level. See result.variance_effects for the raw PSU-level replicate array and REGISTRY §SyntheticDiD “Note (survey + jackknife composition)” for the aggregation formula.

The underlying unit-level values come from the jackknife loops in SyntheticDiD._jackknife_se: control LOO estimates fill the first n_control positions (in the order of the control units seen by fit), then treated LOO estimates fill the next n_treated positions. This method joins those estimates back to user-facing unit identities.

att_loo is NaN when the fit hit the zero-sum weight guard for that unit (survey weights composed to zero once the unit was dropped). delta_from_full propagates NaN in that case.

Returns:

Columns:

  • unit - user’s unit ID

  • role - 'control' or 'treated'

  • att_loo - ATT with this unit dropped

  • delta_from_full - att_loo - self.att

Sorted by |delta_from_full| descending, NaN rows at the end.

Return type:

pd.DataFrame

in_time_placebo(fake_treatment_periods=None, zeta_omega_override=None, zeta_lambda_override=None)[source]

Re-estimate the ATT on shifted fake treatment periods within the original pre-treatment window.

A credible placebo should produce near-zero ATTs at every shifted date. Departures from zero signal that whatever the estimator picked up at the real treatment date is also present pre-treatment, weakening the causal interpretation.

The post-treatment data is never used — only the pre-window is re-sliced. Regularization reuses self.zeta_omega and self.zeta_lambda from the original fit (R synthdid convention), unless overrides are supplied.

Parameters:
  • fake_treatment_periods (list, optional) – Explicit pre-period values to test. If None (default), sweeps every feasible pre-period — every P in pre_periods whose position i satisfies i >= 2 (so at least 2 pre-fake periods remain for weight estimation) and i <= n_pre - 1 (so at least 1 post-fake period exists). Values not in pre_periods raise ValueError (a value in post_periods is explicitly not a placebo).

  • zeta_omega_override (float, optional) – Override self.zeta_omega for the refit. Default reuses the original.

  • zeta_lambda_override (float, optional) – Override self.zeta_lambda for the refit.

Returns:

Columns:
  • fake_treatment_period — the shifted date

  • att — placebo ATT (ideally near 0)

  • pre_fit_rmse — shape-only RMSE on the fake pre-window (pre-period mean gap removed, matching pre_treatment_fit)

  • n_pre_fake — periods before the fake date

  • n_post_fake — periods from the fake date onward

NaN is emitted only for dimensional infeasibility. Frank-Wolfe does not expose a mid-solver non-convergence signal; inspect pre_fit_rmse for poor refit quality.

Return type:

pd.DataFrame

sensitivity_to_zeta_omega(zeta_grid=None, multipliers=(0.25, 0.5, 1.0, 2.0, 4.0))[source]

Re-estimate the ATT across a grid of zeta_omega values to show how sensitive the estimate is to the unit-weight regularization.

The Frank-Wolfe time weights computed during the original fit are held fixed here — this method isolates sensitivity to zeta_omega specifically. zeta_lambda and the time weights are not re-fit.

Parameters:
  • zeta_grid (list of float, optional) – Absolute zeta_omega values to evaluate. If None (default), uses multipliers * self.zeta_omega — i.e. a 5-point grid by default, spanning 16x from the smallest to the largest multiplier and symmetric in log space around 1.0.

  • multipliers (tuple of float, default (0.25, 0.5, 1.0, 2.0, 4.0)) – Multipliers on self.zeta_omega. Ignored when zeta_grid is supplied.

Returns:

Columns:
  • zeta_omega — the regularization value evaluated

  • att — resulting ATT

  • pre_fit_rmse — shape-only RMSE on the original pre-period (pre-period mean gap removed, matching pre_treatment_fit; NaN with a single pre-period)

  • max_unit_weight — max element of the composed omega_eff (sensitivity indicator: close to 1 means near-one-hot solutions; close to 1/n_control means near-uniform)

  • effective_n — 1 / sum(omega_eff**2)

Return type:

pd.DataFrame

Notes

Extreme zeta_omega: very small values push weights toward sparse one-hot solutions (few controls dominate); very large values push toward uniform weighting. The pre_fit_rmse column exposes the tradeoff.

get_weight_concentration(top_k=5)[source]

Concentration metrics for the control unit weights.

Operates on self.unit_weights, which for survey-weighted fits stores the composed effective weights (omega_eff = raw_omega * w_control, renormalized to sum to 1) that were applied to produce the ATT. For non-survey fits the values equal the raw Frank-Wolfe solution. Either way, the concentration reflects the distribution actually used by the estimator.

Parameters:

top_k (int, default 5) – Number of largest weights to sum for top_k_share. Must be non-negative. Clamped to the available number of control units.

Returns:

Keys:
  • effective_n — 1 / sum(w**2), inverse Herfindahl

  • herfindahl — sum(w**2)

  • top_k_share — sum of the top_k largest weights

  • top_k — the (possibly clamped) value used

Return type:

dict

Raises:

ValueError – If top_k is negative.

property is_significant: bool

Check if the ATT is statistically significant at the alpha level.

property significance_stars: str

Return significance stars based on p-value.

__init__(att, se, t_stat, p_value, conf_int, n_obs, n_treated, n_control, unit_weights, time_weights, pre_periods, post_periods, alpha=0.05, variance_method='placebo', noise_level=None, zeta_omega=None, zeta_lambda=None, pre_treatment_fit=None, pre_treatment_level_gap=None, pre_fit_placebo_rmse=None, pre_fit_placebo_pvalue=None, variance_effects=None, n_bootstrap=None, survey_metadata=None, synthetic_pre_trajectory=None, synthetic_post_trajectory=None, treated_pre_trajectory=None, treated_post_trajectory=None, time_weights_array=None)
Parameters:
Return type:

None

Results-contract foundations#

Shared bases introduced by the 4.0 API-unification program. BaseResults is the estimator-results base; Diagnostic marks diagnostic result containers (which carry no inference row); EventStudyResults is the unified per-event-time representation.

class diff_diff.BaseResults[source]

Bases: object

Shared base for ESTIMATOR result containers (spec section 5).

Behaviorally inert in 3.9 - a TRANSITIONAL marker base that makes the results contract checkable and anchors the 4.0 storage flip. It does not yet enforce a uniform runtime protocol: most subclasses are scalar estimator results (one canonical quintet), but EventStudyResults is intentionally vector-valued, and a few inherited classes still expose summary() without an alpha argument. Full protocol uniformity (uniform summary(alpha=None), native canonical storage) is enforced in later Phase 2 / 4.0 PRs. The target contract every ESTIMATOR-results subclass converges to:

  • Canonical quintet. att, se, t_stat, p_value, conf_int bound to ONE coherent inference row (native fields or property aliases over legacy overall_*/avg_* storage; the storage flips to native canonical fields at 4.0, ledger rows M-050..M-058, with legacy names living on as FutureWarning properties until 5.0).

  • Serialization. summary(alpha=None), to_dict() (canonical key names only - deprecated names never leak into serialized output), and to_dataframe(level=...) where multiple views exist.

  • Pickle migration. Classes whose stored field names change ship __setstate__ migration following the SyntheticDiDResults.__setstate__ precedent (diff_diff/results.py) so 3.x pickles load under 4.0.

  • Planned estimand self-description hook. The per-class headline semantics (name / definition / aggregation / headline attribute) currently live in _reporting_helpers.describe_target_parameter, keyed by result-class name. A later PR lifts that block onto this base so results self-describe their target parameter (the MMM exporter’s verified allowlist is the first consumer); nothing here constrains that lift.

Diagnostic result containers (spec section 3.5) do NOT inherit this base - they are marked with Diagnostic and are exempt from the quintet by type.

class diff_diff.Diagnostic[source]

Bases: object

Marker base for diagnostic RESULT containers (spec section 3.5).

diff-diff distinguishes three object kinds: estimators, estimator results, and diagnostics. Exactly one bit is load-bearing: an estimator’s result carries a causal-effect inference row (the canonical quintet att/se/t_stat/p_value/conf_int); a diagnostic’s result does NOT - it assesses a design, an identifying assumption, or robustness.

Marked classes expose summary() and to_dataframe() and are exempt from the quintet BY TYPE, so consumers route with isinstance(result, Diagnostic) instead of result-class-name special-casing. The contract is enforced by the roster test in tests/test_diagnostic_marker.py (ledger row M-091), not by the type system - this class intentionally has no methods.

class diff_diff.EventStudyResults[source]

Bases: BaseResults

Unified event-study representation (spec section 5, row M-092).

ONE representation for per-event-time effects across all estimators. Columnar numpy arrays index-aligned to event_time (the HeterogeneousAdoptionDiDEventStudyResults precedent). Values are copied bit-exactly from each estimator’s native surface - never recomputed - except the mandated reference-row normalization (att=0.0, inference NaN).

Parameters:
  • event_time (np.ndarray) – Sorted estimator-native event-time labels, NEVER renumbered. Relative producers use their own origin (see event_time_convention); the pre-4.0 MultiPeriodDiD surface is calendar-keyed (time_scale="calendar") and may carry object dtype (str/datetime period labels).

  • att (np.ndarray) – Canonical per-event-time inference columns. On the reference row (if any): att == 0.0 and se/t_stat/p_value are NaN.

  • se (np.ndarray) – Canonical per-event-time inference columns. On the reference row (if any): att == 0.0 and se/t_stat/p_value are NaN.

  • t_stat (np.ndarray) – Canonical per-event-time inference columns. On the reference row (if any): att == 0.0 and se/t_stat/p_value are NaN.

  • p_value (np.ndarray) – Canonical per-event-time inference columns. On the reference row (if any): att == 0.0 and se/t_stat/p_value are NaN.

  • conf_int_lower (np.ndarray) – Confidence-interval bounds at alpha (NaN on the reference row).

  • conf_int_upper (np.ndarray) – Confidence-interval bounds at alpha (NaN on the reference row).

  • is_reference (np.ndarray) – Boolean; the EXPLICIT reference-period marking. This column - not any count sentinel - is the sole consumer-facing signal. Usually one True entry, but MULTIPLE are legal (CallawaySantAnna universal base on a gapped grid carries one per cohort’s positional base), and ZERO when the estimator omits no baseline (e.g. HAD, Wooldridge). Use reference_periods for the general case; reference_period is the single-reference convenience scalar.

  • n (np.ndarray) – Per-event-time count as float, NaN where the producer records none (and on the reference row - no estimation happened there).

  • n_kind (str or None) – Semantic of n for this producer, drawn from the shared N_KIND_VOCABULARY: "groups" (a group-level count - cohorts for CallawaySantAnna/SunAbraham, eligible switcher groups per horizon for de Chaisemartin-D’Haultfoeuille with L_max >= 1), "switcher_cells" (dCDH legacy L_max is None path: switching (g, t) cells, where one group may contribute several), "cells" ((g, t) cells generally - what CallawaySantAnna’s "group" aggregation counts), "units" (distinct units, as in the overall/simple aggregation), "obs" (observations), "clusters", or None when no count is recorded. Never conflate these units.

  • reference_period (Any or None) – Convenience scalar echo of the marked row’s event_time label when there is EXACTLY ONE reference row; None when there are zero or several. Use is_reference (or the reference_periods property) for the general case - some estimators (CallawaySantAnna universal base on a gapped grid) carry multiple reference-only horizons.

  • time_scale (str) – "relative" or "calendar".

  • event_time_convention (str or None) – Origin documentation for relative scales: "e0_first_treated" (e = t - g; first treated period at e=0) or "l1_first_switch" (de Chaisemartin-D’Haultfoeuille: instantaneous effect at l=1, placebos at negative keys). Horizons are documented, not renumbered. (EfficientDiD buckets fractional-period horizons by int(t - g) — see its REGISTRY truncation Note.)

  • vcov (np.ndarray or None) – Full event-study variance-covariance matrix where the RESULT CONTAINER exposes one (e.g. CallawaySantAnna, SunAbraham, MultiPeriodDiD, StackedDiD, and TwoStageDiD’s analytical modes), ordered by vcov_index. None when the producer records no matrix or when the stored SEs are no longer its diagonal (bootstrap and replicate-weight overrides clear it rather than ship an inconsistent matrix).

  • vcov_index (np.ndarray or None) – event_time labels labelling vcov’s rows/columns (explicit ordering for HonestDiD / PreTrendsPower consumption).

  • cband_lower (np.ndarray or None) – Simultaneous confidence-band bounds where computed; None when the producer has none (to_dataframe then emits NaN columns - the schema never changes).

  • cband_upper (np.ndarray or None) – Simultaneous confidence-band bounds where computed; None when the producer has none (to_dataframe then emits NaN columns - the schema never changes).

  • cband_crit_value (float or None) – Critical value of the simultaneous band, where computed.

  • alpha (float) – Significance level of the stored intervals.

  • source (str or None) – Producing results-class name (provenance).

  • df (float, np.ndarray, or None) – Per-row inference degrees of freedom: df[i] is the df ACTUALLY passed to safe_inference for row i’s stored p-value/CI, threaded from the producer. Accepts None (no df exposed -> all-NaN column), a scalar (broadcast to every row - e.g. CallawaySantAnna, whose explicit-survey event study applies ONE conservative df, the minimum per-horizon effective df, to all rows; or de Chaisemartin-D’Haultfoeuille, whose effect and placebo rows share one design df), or a length-n array (per-row producers: StackedDiD and SunAbraham hc2_bm per-event Bell-McCaffrey df, LPDiD per-horizon cluster df, MultiPeriodDiD hc2_bm per-period df). NaN on any row means normal-theory inference, an undefined df, bootstrap-overridden inference, or a producer that records none; reference rows and rows with NaN p-values are always NaN.

  • base_period (str or None) – Producer provenance: the fit’s base-period regime where the producer has one (CallawaySantAnna vocabulary: "varying" or "universal"). None when the producer has no such notion. HonestDiD reads this for its universal-base interpretation warning.

  • anticipation (int or None) – Producer provenance: the fit’s anticipation window in periods, where the producer has one. None when the producer has no such notion. PreTrendsPower reads this to exclude anticipation-window rows (event_time >= -anticipation) from the pre-trend set.

  • df_survey (float or None) – Producer provenance: the fit’s resolved SCALAR inference df, with the established semantics of the fit-time consumers - survey_metadata.df_survey where present (0.0 = replicate design with an undefined df, which fails closed to NaN critical values downstream), else df_inference (the bare-cluster= carrier), else None (no scalar df notion). Exists beside the per-row df column because that column CANNOT encode the replicate-undefined sentinel: __post_init__ forces per-row df to NaN wherever the p-value is non-finite.

  • reference_event_times (tuple or None) – Producer provenance: the DISTINCT per-cohort normalization-base event times (CallawaySantAnna base_period="universal": each cohort’s positional base period minus its cohort, deduplicated, sorted). More than one entry means the coefficients were normalized against DIFFERENT bases (gapped time grid) - and on such grids a cohort’s base can OVERLAP another cohort’s estimated horizon, where NO reference-only row exists to mark it, so this field (not is_reference) is the authoritative common-reference signal. HonestDiD and PreTrendsPower fail closed when it carries more than one entry. None when the producer records no such notion (varying base, non-CS producers, hand-built surfaces).

event_time: ndarray
att: ndarray
se: ndarray
t_stat: ndarray
p_value: ndarray
conf_int_lower: ndarray
conf_int_upper: ndarray
is_reference: ndarray
n: ndarray
n_kind: str | None = None
reference_period: Any | None = None
time_scale: str = 'relative'
event_time_convention: str | None = None
vcov: ndarray | None = None
vcov_index: ndarray | None = None
cband_lower: ndarray | None = None
cband_upper: ndarray | None = None
cband_crit_value: float | None = None
alpha: float = 0.05
source: str | None = None
df: float | ndarray | None = None
base_period: str | None = None
anticipation: int | None = None
df_survey: float | None = None
reference_event_times: Tuple[Any, ...] | None = None
estimand: str | None = None

The estimand label for the att column when the producer’s per-horizon estimand is NOT an ATT - HeterogeneousAdoptionDiD’s per-horizon “WAS”/”WAS_d_lower” (row M-027; _from_had relays target_parameter). None means the column is an ATT (every other producer), keeping their rendering byte-stable. summary() uses it as the column heading and to_dict() serializes it, so the numbers never silently change meaning. Optional provenance appended last (the M-092 pre-cut amendment convention).

post_periods: Tuple[Any, ...] | None = None

Calendar-partition provenance (M-092 pre-cut amendment #5, with row M-010): the producer’s AUTHORITATIVE post-treatment period labels. MultiPeriodDiD accepts an ARBITRARY post_periods subset (a non-suffix split is legal), and the partition is not recoverable from event_time/is_reference positionally - consumers that need pre/post classification on a calendar surface (HonestDiD, PreTrendsPower, the plotter) read THIS field; pre-periods derive as the complement: non-reference rows whose event_time is not in post_periods. Threaded by _from_mpd (and the TWFE event-study producer); None from every other builder.

estimation_spec: str | None = None

TWFE event-study design provenance (M-092 amendment #5, with row M-010): "within" (unit + time FE) or "pooled" (the MultiPeriodDiD design - treatment-group dummy + period dummies, no unit FE). The two specs differ materially in SEs, so the design must be recoverable from the container. None from every builder (only the TwoWayFixedEffects event-study producer sets it).

property reference_periods: List[Any]

All reference-row event_time labels (JSON-safe scalars).

The general accessor for the normalization anchors: one entry for the common single-reference case, several for CallawaySantAnna universal base on a gapped grid, empty when the estimator omits no baseline.

to_dataframe()[source]

Return the pinned per-event-time table (EVENT_STUDY_SCHEMA).

The column schema is identical for every producer; band columns are NaN-filled when the producer computes no simultaneous band.

Return type:

DataFrame

to_dict()[source]

Return a JSON-friendly dict (columns as lists, plus metadata).

Calendar event-time labels (pandas.Timestamp / Period / datetime) are converted to ISO strings so the result is JSON-serializable; relative-time integer labels pass through.

Return type:

Dict[str, Any]

summary(alpha=None)[source]

Return a formatted event-study table.

Parameters:

alpha (float, optional) – Accepted for signature uniformity (spec section 5). The stored confidence columns were computed at fit time; passing a value different from the stored alpha raises rather than silently recomputing or mislabeling - re-aggregate at the desired level instead.

Return type:

str

__init__(event_time, att, se, t_stat, p_value, conf_int_lower, conf_int_upper, is_reference, n, n_kind=None, reference_period=None, time_scale='relative', event_time_convention=None, vcov=None, vcov_index=None, cband_lower=None, cband_upper=None, cband_crit_value=None, alpha=0.05, source=None, df=None, base_period=None, anticipation=None, df_survey=None, reference_event_times=None, estimand=None, post_periods=None, estimation_spec=None)
Parameters:
Return type:

None

class diff_diff.AggregationResult[source]

Bases: BaseResults

One post-fit aggregation, as a table (spec section 6, row M-122).

Columnar arrays index-aligned to label. Values are computed by the producing estimator’s aggregation machinery and stored here verbatim - this container never re-derives inference.

Parameters:
  • level (str) – The aggregation type that produced this table - one of AGGREGATION_VOCABULARY or a documented per-estimator extra.

  • label (np.ndarray) – Per-row aggregation key: the cohort for "group", the calendar period for "calendar", the dose for "dose". A single "overall" entry for "simple"; a single "total" entry for "total".

  • target (np.ndarray) – Per-row estimand discriminator, so one container can carry two aligned estimands over the same labels (ContinuousDiD’s ATT(d) and ACRT(d) become 2N rows). "att" where an estimator has one; "total" on a "total" row - a total incremental outcome over the estimator’s aggregation mass, not an ATT.

  • att (np.ndarray) – The canonical quintet, per row, carrying WHATEVER inference the fit stored - never recomputed. On a bootstrapped fit that usually means the producer’s percentile-bootstrap statistics carried through unchanged; view-relay producers can mix regimes per row where the fit itself did (dCDH’s L_max >= 2 cost-benefit delta keeps analytical safe_inference even under n_bootstrap > 0 - see the REGISTRY Phase 2 cost-benefit delta SE note).

  • se (np.ndarray) – The canonical quintet, per row, carrying WHATEVER inference the fit stored - never recomputed. On a bootstrapped fit that usually means the producer’s percentile-bootstrap statistics carried through unchanged; view-relay producers can mix regimes per row where the fit itself did (dCDH’s L_max >= 2 cost-benefit delta keeps analytical safe_inference even under n_bootstrap > 0 - see the REGISTRY Phase 2 cost-benefit delta SE note).

  • t_stat (np.ndarray) – The canonical quintet, per row, carrying WHATEVER inference the fit stored - never recomputed. On a bootstrapped fit that usually means the producer’s percentile-bootstrap statistics carried through unchanged; view-relay producers can mix regimes per row where the fit itself did (dCDH’s L_max >= 2 cost-benefit delta keeps analytical safe_inference even under n_bootstrap > 0 - see the REGISTRY Phase 2 cost-benefit delta SE note).

  • p_value (np.ndarray) – The canonical quintet, per row, carrying WHATEVER inference the fit stored - never recomputed. On a bootstrapped fit that usually means the producer’s percentile-bootstrap statistics carried through unchanged; view-relay producers can mix regimes per row where the fit itself did (dCDH’s L_max >= 2 cost-benefit delta keeps analytical safe_inference even under n_bootstrap > 0 - see the REGISTRY Phase 2 cost-benefit delta SE note).

  • conf_int_lower (np.ndarray) – Interval bounds at the fit’s alpha.

  • conf_int_upper (np.ndarray) – Interval bounds at the fit’s alpha.

  • n (np.ndarray) – Per-row count as float, NaN where the producer records none. Its SEMANTIC is n_kind - never assume units.

  • n_kind (str or None) – Semantic of n, from N_KIND_VOCABULARY - the SAME closed vocabulary EventStudyResults draws on, so a consumer can route on n_kind across both containers. None when the producer records no count.

  • weight (np.ndarray or None) – Normalized aggregation mass per row, summing to 1 within one (level, target) group. None where no per-row mass exists - CallawaySantAnna’s "group" aggregation weights (g, t) cells equally WITHIN each cohort and has no cross-cohort mass, so inventing one would be a fabricated number. Single-row containers ("simple", "total") carry weight=[1.0].

  • df (np.ndarray) – Per-row inference degrees of freedom, NaN where none governed the stored p-value. NaN on percentile-bootstrap rows (no df governs them); a bootstrapped fit’s rows can still carry a finite df where the fit kept analytical inference for that row (the dCDH delta case above).

  • alpha (float) – Significance level the interval was computed at.

  • estimator (str or None) – Producing estimator class name, for provenance in summary().

level: str
label: ndarray
target: ndarray
att: ndarray
se: ndarray
t_stat: ndarray
p_value: ndarray
conf_int_lower: ndarray
conf_int_upper: ndarray
n: ndarray
df: ndarray
alpha: float = 0.05
n_kind: str | None = None
weight: ndarray | None = None
estimator: str | None = None
to_dataframe()[source]

Return the pinned AGGREGATION_SCHEMA columns, in order.

Rows are ordered by label when the labels are homogeneously sortable, and in producer order otherwise (mixed-type cohort labels cannot be ordered without raising). Heterogeneous-target containers (ContinuousDiD’s att/acrt, row M-025) order by FIRST-APPEARANCE target blocks instead - producer order, NOT lexicographic, which would put "acrt" before "att" - with labels ascending within each block under the same sortability guard.

Return type:

DataFrame

to_dict()[source]

Canonical-name mapping (deprecated names never leak into output).

Return type:

Dict[str, Any]

summary(alpha=None)[source]

Human-readable table.

Parameters:

alpha (float, optional) – Accepted for signature uniformity (spec section 5). The stored interval was computed at aggregation time; passing a value different from the stored alpha raises rather than silently recomputing or mislabeling it - re-aggregate instead. Mirrors summary().

Return type:

str

__init__(level, label, target, att, se, t_stat, p_value, conf_int_lower, conf_int_upper, n, df, alpha=0.05, n_kind=None, weight=None, estimator=None, _COLUMN_FIELDS=('label', 'target', 'att', 'se', 't_stat', 'p_value', 'conf_int_lower', 'conf_int_upper', 'n', 'df'))
Parameters:
Return type:

None