diff_diff.ChaisemartinDHaultfoeuilleResults#

class diff_diff.ChaisemartinDHaultfoeuilleResults[source]#

Bases: BaseResults, AggregationMixin

Results from de Chaisemartin-D’Haultfoeuille (dCDH) Phase 1 estimation.

Phase 1 ships the contemporaneous-switch estimator DID_M (= DID_1 at horizon l = 1 of the dynamic companion paper) plus the joiners- only / leavers-only views, the single-lag placebo DID_M^pl, and optionally the TWFE decomposition diagnostic (per-cell weights, fraction negative, sigma_fe).

Notes

The analytical confidence interval is conservative under Assumption 8 (independent groups) of the dynamic companion paper, and exact only under iid sampling. This is documented as a deliberate deviation from “default nominal coverage” in the methodology registry.

For binary treatment in Phase 1, multi-switch groups (i.e., groups that switch treatment more than once) are dropped before estimation when drop_larger_lower=True (the default), matching the R DIDmultiplegtDYN reference. The number of dropped groups is exposed via n_groups_dropped_crossers.

Inference-method switch when bootstrap is enabled. The overall_p_value / overall_conf_int (and joiners/leavers analogues) fields are populated by normal-theory inference from the cohort-recentered analytical SE when n_bootstrap=0 (the default). When n_bootstrap > 0, the same fields are populated by percentile-based bootstrap inference from the multiplier bootstrap distribution computed by _compute_dcdh_bootstrap(). The t-stat (overall_t_stat, etc.) is computed from the SE in both cases, since percentile bootstrap does not define an alternative t-stat semantic. event_study_effects[1], summary(), to_dataframe(), is_significant, and significance_stars all read from these top-level fields and therefore reflect the bootstrap inference automatically. The single-period placebo (L_max=None) still has NaN bootstrap fields; multi-horizon placebos (L_max >= 1) have valid bootstrap SE/CI/p via placebo_horizon_ses/cis/p_values. See the methodology registry Note (bootstrap inference surface) for the full contract and library precedent.

overall_att#

DID_M = DID_1: the contemporaneous-switch dCDH point estimate.

Type:

float

overall_se#

Standard error of DID_M.

Type:

float

overall_t_stat#
Type:

float

overall_p_value#
Type:

float

overall_conf_int#
Type:

tuple of float

joiners_att#

DID_+: the joiners-only contribution. NaN when joiners_available is False.

Type:

float

joiners_se#
Type:

float

joiners_t_stat#
Type:

float

joiners_p_value#
Type:

float

joiners_conf_int#
Type:

tuple of float

n_joiner_cells#

Total number of joiner switching (g, t) cells across all periods. Each cell counted once. Equals sum_t (#{g : D_{g,t-1}=0, D_{g,t}=1}).

Type:

int

n_joiner_obs#

Total raw observation count across joiner cells, summing n_gt over the same set of cells. For balanced one-observation-per-cell panels this equals n_joiner_cells; for individual-level inputs with multiple observations per (g, t) it can be larger.

Type:

int

joiners_available#

True if at least one joiner switching cell exists.

Type:

bool

leavers_att#

DID_-: the leavers-only contribution. NaN when leavers_available is False.

Type:

float

leavers_se#
Type:

float

leavers_t_stat#
Type:

float

leavers_p_value#
Type:

float

leavers_conf_int#
Type:

tuple of float

n_leaver_cells#

Total number of leaver switching (g, t) cells (mirror of n_joiner_cells).

Type:

int

n_leaver_obs#

Total raw observation count across leaver cells (mirror of n_joiner_obs).

Type:

int

leavers_available#
Type:

bool

placebo_effect#

DID_M^pl: the single-lag placebo. NaN when placebo_available is False.

Type:

float

placebo_se#
Type:

float

placebo_t_stat#
Type:

float

placebo_p_value#
Type:

float

placebo_conf_int#
Type:

tuple of float

placebo_available#

True when T >= 3 and at least one qualifying placebo cell exists.

Type:

bool

per_period_effects#

Per-period decomposition. Keys are period values; each value is a dict with the following keys:

  • "did_plus_t" (float): joiner effect at this period (0.0 if no joiners or A11 violation)

  • "did_minus_t" (float): leaver effect at this period

  • "n_10_t" (int): joiner cell count

  • "n_01_t" (int): leaver cell count

  • "n_00_t" (int): stable-untreated cell count

  • "n_11_t" (int): stable-treated cell count

  • "did_plus_t_a11_zeroed" (bool): True when joiners exist but no stable-untreated controls (Assumption 11 violation, period contributes 0 to numerator with non-zero weight in denominator)

  • "did_minus_t_a11_zeroed" (bool): mirror for leavers

Type:

dict

twfe_weights#

Per-cell TWFE decomposition weights from Theorem 1 of de Chaisemartin & D’Haultfoeuille (2020). Columns: group, time, weight. Computed on the FULL pre-filter cell sample passed by the user (the same input the standalone twowayfeweights() function uses) — NOT the post-filter estimation sample described by overall_att and units. When fit() drops groups via the ragged-panel or drop_larger_lower filters, results.twfe_* and results.overall_att describe different samples and a UserWarning is emitted; see REGISTRY.md ChaisemartinDHaultfoeuille Note (TWFE diagnostic sample contract) for the rationale. Only populated when twfe_diagnostic=True.

Type:

pd.DataFrame, optional

twfe_fraction_negative#

Fraction of treated-cell weights that are negative. > 0 is the diagnostic for the heterogeneous-treatment-effect bias of the plain TWFE estimator on the FULL pre-filter cell sample (NOT the post-filter estimation sample). See the twfe_weights docstring above for the sample contract.

Type:

float, optional

twfe_sigma_fe#

Smallest standard deviation of per-cell treatment effects that could flip the sign of the plain TWFE estimator (Corollary 1 of the AER 2020 paper). Computed on the FULL pre-filter cell sample.

Type:

float, optional

twfe_beta_fe#

The plain TWFE coefficient computed on the FULL pre-filter cell sample, for comparison with overall_att. Note that the two are computed on different samples when fit() filters drop groups — see the twfe_weights docstring above for the sample contract.

Type:

float, optional

units#

Unit identifiers in the post-filter sample. (The deprecated read-only alias groups warns and returns this list; removed in 4.0 — row M-114.)

Type:

list

time_periods#

Time periods in the panel.

Type:

list

n_obs#

Total observations after filtering.

Type:

int

n_treated_obs#

Treated observations in the post-filter sample.

Type:

int

n_switcher_cells#

When L_max=None: number of switching (g, t) cells (N_S = sum_t (n_10_t + n_01_t)). When L_max >= 1: number of eligible switcher groups at horizon 1 (N_1). Previously this field always held the cell count; for L_max >= 1 it was repurposed to hold the per-group count that matches the DID_1 estimand. Originally equals once regardless of how many original observations fed into it. This is the N_S denominator of DID_M under the library’s equal-cell weighting convention (cell counts, not within-cell observation sums). The AER 2020 paper’s Equation 3 defines N_{d,d',t} = sum_g N_{g,t} (observation sums); the library’s choice is a documented deviation - see docs/methodology/REGISTRY.md ## ChaisemartinDHaultfoeuille L517 for the full Note.

Type:

int

n_cohorts#

Distinct cohorts (D_{g,1}, F_g, S_g) after filtering.

Type:

int

n_groups_dropped_crossers#

Number of groups dropped because they were multi-switch (matches R’s drop_larger_lower=TRUE behavior). 0 when drop_larger_lower=False or no crossers exist.

Type:

int

n_groups_dropped_singleton_baseline#

Number of groups whose baseline D_{g,1} is unique in the post-drop panel (footnote 15 of the dynamic paper). They are excluded from the cohort-recentered VARIANCE computation only — they remain in the point-estimate sample as period-based stable controls (see REGISTRY.md ChaisemartinDHaultfoeuille for the period-vs-cohort deviation that makes this distinction matter).

Type:

int

n_groups_dropped_never_switching#

Number of groups with S_g = 0 (never switched). Reported for backwards compatibility only. Per the Round 2 full influence-function fix, never-switching groups are NOT excluded from the variance: they contribute via their stable-control roles in the per-period IF formula. The field name retains “dropped” for API stability but no actual exclusion happens.

Type:

int

alpha#

Significance level used for confidence intervals.

Type:

float

event_study_effects#

Populated with horizon 1 when L_max=None, or horizons 1..L_max when L_max >= 1. When L_max >= 1, uses the per-group DID_{g,l} path; when L_max=None, uses the per-period DID_M path.

Type:

dict, optional

event_study_df#

Inference degrees-of-freedom PROVENANCE for the event-study and placebo surfaces: the single df every stored row’s safe_inference received. One scalar suffices because both surfaces are computed from the same design df (and are refreshed together to the final effective df under replicate weights). None when the rows used normal theory (no survey design), when the df was undefined, or when bootstrap overrode the stored inference with percentile values that never used a df.

Type:

float, optional

normalized_effects#

Normalized estimator DID^n_l. Populated when L_max >= 1.

Type:

dict, optional

cost_benefit_delta#

Cost-benefit aggregate delta. Populated when L_max >= 2.

Type:

dict, optional

sup_t_bands#

Sup-t simultaneous confidence-band metadata for the OVERALL event-study surface. Holds {"crit_value": float, "alpha": float, "n_bootstrap": int, "method": str}. Populated when n_bootstrap > 0 AND there are at least 2 valid horizons with finite bootstrap SE > 0 AND a strict majority (more than 50%) of sup-t draws are finite. The band itself is written per-horizon as cband_conf_int on event_study_effects[l]. None otherwise. Python-only library extension; R did_multiplegt_dyn provides no joint / sup-t bands.

Type:

dict, optional

covariate_residuals#

DID^X first-stage diagnostics: per-baseline theta_hat, n_obs, and r_squared. Populated when covariates is set.

Type:

pd.DataFrame, optional

Cumulated DID^{fd} level effects delta^{fd}_l. Keyed by horizon. Populated when trends_linear=True.

Type:

dict, optional

heterogeneity_effects#

Per-horizon heterogeneity test results beta^{het}_l. Populated when heterogeneity is set.

Type:

dict, optional

design2_effects#

Design-2 switch-in/switch-out descriptive summary. Populated when design2=True.

Type:

dict, optional

path_effects#

Per-path event-study effects keyed by observed treatment trajectory (tuple of int). Populated when by_path is a positive int OR paths_of_interest is a list of int tuples at estimator construction. Each entry holds {"n_groups": int, "frequency_rank": int, "horizons": {l: {"effect", "se", "t_stat", "p_value", "conf_int", "n_obs"}}} for l = 1..L_max. Under paths_of_interest, dict-insertion order matches the user- specified path order; frequency_rank is the within- selected-paths rank by descending observed-group count (decoupled from iteration order).

Type:

dict, optional

path_placebo_event_study#

Per-path backward-horizon placebos DID^{pl}_{path, l} for l = 1..L_max, keyed by observed treatment trajectory (tuple of int). Inner dict keys are negative ints (-l for lag l) to mirror the placebo_event_study convention so a unified {**path_effects[p]["horizons"], **path_placebo_event_study[p]} view is well-formed across forward and backward horizons. Each inner entry holds {"effect", "se", "t_stat", "p_value", "conf_int", "n_obs"}. Populated when (by_path is a positive int OR paths_of_interest is set) AND placebo=True AND L_max >= 1. Empty-state contract mirrors path_effects: None when by_path / paths_of_interest + placebo was not requested; {} when requested but no observed path has a complete window [F_g-1, F_g-1+L_max] within the panel (the same regime where path_effects returns {}, with the same UserWarning at fit-time). Downstream callers should distinguish the two states. Inherits the cross-path cohort-sharing SE deviation from R documented for path_effects. See REGISTRY.md Note (Phase 3 by_path ...) → “Per-path placebos”.

Type:

dict, optional

path_heterogeneity_effects#

Per-path heterogeneity test results (Web Appendix Section 1.5, Lemma 7) when heterogeneity is set AND (by_path=k or paths_of_interest=[(...), ...]) is set. Inner dict keyed by horizon directly (no "horizons" wrapper); each entry holds {"beta", "se", "t_stat", "p_value", "conf_int", "n_obs"}, where beta is the heterogeneity coefficient on the path- restricted switcher subsample - plain OLS on the non-survey path, WLS-on-pweights under survey_design. Cohort dummies in the design matrix absorb baseline by construction. Empty-state contract mirrors path_effects: None when not requested; {} when requested but no path has eligible switchers. Mirrors R did_multiplegt_dyn(..., by_path, predict_het) per-by_level dispatch. See REGISTRY.md Note (Phase 3 by_path ...) → “Per-path heterogeneity testing”.

Type:

dict, optional

path_cumulated_event_study#

Per-path cumulated level effects delta_{path, l} = sum_{l'=1..l} DID^{fd}_{path, l'} for l = 1..L_max, keyed by observed treatment trajectory (tuple of int). Inner dict is keyed by horizon directly (no "horizons" wrapper); each entry holds {"effect", "se", "t_stat", "p_value", "conf_int", "n_obs"}. Populated when (by_path is a positive int OR paths_of_interest is set) AND trends_linear=True AND L_max >= 1; None otherwise. Mirrors the global linear_trends_effects cumulation: SE on the cumulated layer is the conservative upper bound (sum of per-horizon component SEs from path_effects[path]["horizons"][l]["se"], NaN-consistent). Built AFTER bootstrap propagation so the cumulated SE / t / p / CI are derived from the FINAL post-bootstrap per-horizon SEs when n_bootstrap > 0. Surfaced as cumulated_effect / cumulated_se columns on to_dataframe(level="by_path") (always-present, NaN-when- None) and as a per-path “Cumulated Level Effects” sub-section in summary(). See REGISTRY.md Note (Phase 3 by_path ...) → “Per-path linear-trends DID^{fd}”.

Type:

dict, optional

path_sup_t_bands#

Per-path joint sup-t simultaneous-band metadata, keyed by observed treatment trajectory (tuple of int). Each entry holds {"crit_value": float, "alpha": float, "n_bootstrap": int, "method": str, "n_valid_horizons": int}. Populated when (by_path is a positive int OR paths_of_interest is set) AND n_bootstrap > 0. The band itself is applied per-horizon as cband_conf_int on path_effects[path]["horizons"][l] and rendered as cband_lower / cband_upper columns on to_dataframe(level="by_path"). Empty-state contract: None when not requested (no bootstrap, or both by_path and paths_of_interest are None); {} when requested but no path passed both gates (>=2 valid horizons with finite bootstrap SE > 0 AND a strict majority — more than 50% — of finite sup-t draws). Bands cover joint inference WITHIN a single path across horizons; they do NOT provide simultaneous coverage across paths. Inherits the cross-path cohort-sharing SE deviation from R documented for path_effects (the bootstrap SE used as the t-stat denominator carries the same deviation). Python-only library extension; R did_multiplegt_dyn provides no joint / sup-t bands at any surface. See REGISTRY.md Note (Phase 3 by_path per-path joint sup-t bands).

Type:

dict, optional

honest_did_results#

HonestDiD sensitivity analysis bounds (Rambachan & Roth 2023). Populated when honest_did=True in fit() or by calling compute_honest_did(results) post-hoc. Contains identified set bounds, robust confidence intervals, and breakdown analysis.

Type:

HonestDiDResults, optional

survey_metadata#

Populated when fit(..., survey_design=sd) is called; None otherwise. Carries the resolved survey design summary (weight_type, strata/PSU counts, df_survey, weight range, and replicate-method info when applicable). df_survey is threaded into survey-aware inference (t-distribution at all analytical surfaces) and consumed by compute_honest_did() to produce survey-aware critical values.

Type:

Any, optional

bootstrap_results#

Bootstrap inference results when n_bootstrap > 0.

Type:

DCDHBootstrapResults, optional

Methods

__init__(overall_att, overall_se, ...[, ...])

aggregate(type[, weights, balance_e])

Re-aggregate this fit without refitting.

print_summary([alpha])

Print the formatted summary to stdout.

summary([alpha])

Generate a formatted summary of dCDH estimation results.

to_dataframe([level])

Convert results to a DataFrame at the requested level of aggregation.

to_dict()

Convert headline results to a dictionary.

Attributes

__init__(overall_att, overall_se, overall_t_stat, overall_p_value, overall_conf_int, joiners_att, joiners_se, joiners_t_stat, joiners_p_value, joiners_conf_int, n_joiner_cells, n_joiner_obs, joiners_available, leavers_att, leavers_se, leavers_t_stat, leavers_p_value, leavers_conf_int, n_leaver_cells, n_leaver_obs, leavers_available, placebo_effect, placebo_se, placebo_t_stat, placebo_p_value, placebo_conf_int, placebo_available, per_period_effects, units, time_periods, n_obs, n_treated_obs, n_switcher_cells, n_cohorts, n_groups_dropped_crossers, n_groups_dropped_singleton_baseline, n_groups_dropped_never_switching, event_study_effects=None, L_max=None, placebo_event_study=None, twfe_weights=None, twfe_fraction_negative=None, twfe_sigma_fe=None, twfe_beta_fe=None, alpha=0.05, normalized_effects=None, cost_benefit_delta=None, sup_t_bands=None, covariate_residuals=None, linear_trends_effects=None, trends_linear=None, heterogeneity_effects=None, design2_effects=None, path_effects=None, path_placebo_event_study=None, path_heterogeneity_effects=None, path_cumulated_event_study=None, path_sup_t_bands=None, honest_did_results=None, survey_metadata=None, bootstrap_results=None, _estimator_ref=None, event_study_df=None)#
Parameters:
  • overall_att (float)

  • overall_se (float)

  • overall_t_stat (float)

  • overall_p_value (float)

  • overall_conf_int (Tuple[float, float])

  • joiners_att (float)

  • joiners_se (float)

  • joiners_t_stat (float)

  • joiners_p_value (float)

  • joiners_conf_int (Tuple[float, float])

  • n_joiner_cells (int)

  • n_joiner_obs (int)

  • joiners_available (bool)

  • leavers_att (float)

  • leavers_se (float)

  • leavers_t_stat (float)

  • leavers_p_value (float)

  • leavers_conf_int (Tuple[float, float])

  • n_leaver_cells (int)

  • n_leaver_obs (int)

  • leavers_available (bool)

  • placebo_effect (float)

  • placebo_se (float)

  • placebo_t_stat (float)

  • placebo_p_value (float)

  • placebo_conf_int (Tuple[float, float])

  • placebo_available (bool)

  • per_period_effects (Dict[Any, Dict[str, Any]])

  • units (List[Any])

  • time_periods (List[Any])

  • n_obs (int)

  • n_treated_obs (int)

  • n_switcher_cells (int)

  • n_cohorts (int)

  • n_groups_dropped_crossers (int)

  • n_groups_dropped_singleton_baseline (int)

  • n_groups_dropped_never_switching (int)

  • event_study_effects (Optional[Dict[int, Dict[str, Any]]])

  • L_max (Optional[int])

  • placebo_event_study (Optional[Dict[int, Dict[str, Any]]])

  • twfe_weights (Optional[pd.DataFrame])

  • twfe_fraction_negative (Optional[float])

  • twfe_sigma_fe (Optional[float])

  • twfe_beta_fe (Optional[float])

  • alpha (float)

  • normalized_effects (Optional[Dict[int, Dict[str, Any]]])

  • cost_benefit_delta (Optional[Dict[str, Any]])

  • sup_t_bands (Optional[Dict[str, Any]])

  • covariate_residuals (Optional[pd.DataFrame])

  • linear_trends_effects (Optional[Dict[int, Dict[str, Any]]])

  • trends_linear (Optional[bool])

  • heterogeneity_effects (Optional[Dict[int, Dict[str, Any]]])

  • design2_effects (Optional[Dict[str, Any]])

  • path_effects (Optional[Dict[Tuple[int, ...], Dict[str, Any]]])

  • path_placebo_event_study (Optional[Dict[Tuple[int, ...], Dict[int, Dict[str, Any]]]])

  • path_heterogeneity_effects (Optional[Dict[Tuple[int, ...], Dict[int, Dict[str, Any]]]])

  • path_cumulated_event_study (Optional[Dict[Tuple[int, ...], Dict[int, Dict[str, Any]]]])

  • path_sup_t_bands (Optional[Dict[Tuple[int, ...], Dict[str, Any]]])

  • honest_did_results (Optional['HonestDiDResults'])

  • survey_metadata (Optional[Any])

  • bootstrap_results (Optional[DCDHBootstrapResults])

  • _estimator_ref (Optional[Any])

  • event_study_df (Optional[float])

Return type:

None

classmethod __new__(*args, **kwargs)#