diff_diff.ChaisemartinDHaultfoeuilleResults#

class diff_diff.ChaisemartinDHaultfoeuilleResults[source]#

Bases: object

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 groups. 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

groups#

Group identifiers in the post-filter sample.

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

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 controls 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, ...[, ...])

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.

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, groups, 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)#
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]])

  • groups (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])

Return type:

None

classmethod __new__(*args, **kwargs)#