|
def to_dataframe(self) -> pd.DataFrame: |
|
"""Convert results to a pandas DataFrame. |
|
|
|
Returns |
|
------- |
|
pd.DataFrame |
|
For common timing: a single-row DataFrame (plus period rows if available). |
|
For staggered: one row per cohort plus an "Overall" row. |
|
""" |
|
if not self.is_staggered: |
|
rows: List[Dict[str, Any]] = [ |
|
{ |
|
"term": "ATT", |
|
"att": self.att, |
|
"se": self.se, |
|
"t_stat": self.t_stat, |
|
"p_value": self.p_value, |
|
"ci_lower": self.conf_int[0], |
|
"ci_upper": self.conf_int[1], |
|
"n_obs": self.n_obs, |
|
"n_treated": self.n_treated, |
|
"n_control": self.n_control, |
|
"rolling": self.rolling, |
|
"estimator": self.estimator, |
|
"vce_type": self.vce_type, |
|
} |
|
] |
|
if self.has_period_effects: |
|
for period, eff in sorted(self.period_effects.items()): # type: ignore[union-attr] |
|
ci = eff.get("conf_int", (np.nan, np.nan)) |
|
rows.append( |
|
{ |
|
"term": f"Period {period}", |
|
"att": eff.get("att", np.nan), |
|
"se": eff.get("se", np.nan), |
|
"t_stat": eff.get("t_stat", np.nan), |
|
"p_value": eff.get("p_value", np.nan), |
|
"ci_lower": ci[0] if ci else np.nan, |
|
"ci_upper": ci[1] if ci else np.nan, |
|
"n_obs": eff.get("n_obs", 0), |
|
"n_treated": None, |
|
"n_control": None, |
|
"rolling": self.rolling, |
|
"estimator": self.estimator, |
|
"vce_type": self.vce_type, |
|
} |
|
) |
|
return pd.DataFrame(rows) |
|
|
|
rows_stag: List[Dict[str, Any]] = [] |
|
for cohort, eff in self.cohort_effects.items(): # type: ignore[union-attr] |
|
ci = eff.get("conf_int", (np.nan, np.nan)) |
|
n_t = eff.get("n_treated", 0) |
|
n_c = eff.get("n_control", 0) |
|
rows_stag.append( |
|
{ |
|
"cohort": cohort, |
|
"att": eff.get("att", np.nan), |
|
"se": eff.get("se", np.nan), |
|
"t_stat": eff.get("t_stat", np.nan), |
|
"p_value": eff.get("p_value", np.nan), |
|
"ci_lower": ci[0] if ci else np.nan, |
|
"ci_upper": ci[1] if ci else np.nan, |
|
"n_treated": n_t, |
|
"n_control": n_c, |
|
} |
|
) |
|
# Append overall row |
|
rows_stag.append( |
|
{ |
|
"cohort": "Overall", |
|
"att": self.att, |
|
"se": self.se, |
|
"t_stat": self.t_stat, |
|
"p_value": self.p_value, |
|
"ci_lower": self.conf_int[0], |
|
"ci_upper": self.conf_int[1], |
|
"n_treated": self.n_treated, |
|
"n_control": self.n_control, |
|
} |
|
) |
|
return pd.DataFrame(rows_stag) |
Summary
LWDiD computes event-study effects, simultaneous confidence-band metadata, and event-time counts, but its result object does not expose that output through the shared result contract. As a result, generic consumers cannot serialise or tabulate the event-study result.
Current behaviour
The estimator stores
event_study_effectsand confidence-band fields onLWDiDResults, but:to_dataframe()only emits scalar, common-timing, or cohort rows;to_dict()omits event-study effects and confidence-band metadata; andLWDiDResultsdoes not inheritBaseResultsor use the repository'sEventStudyResultssurface.Relevant implementation:
diff-diff/diff_diff/lwdid.py
Line 1254 in c2694ff
to_dataframe():diff-diff/diff_diff/lwdid_results.py
Lines 151 to 232 in c2694ff
to_dict():diff-diff/diff_diff/lwdid_results.py
Lines 234 to 271 in c2694ff
Current
mainhas a sharedBaseResults/EventStudyResultscontract used by estimator result objects and serialisation tests. LWDiD should integrate with it as part of its rebase.Expected behaviour
LWDiD should provide a public, serialisable event-study result compatible with the package-wide API, preferably by making
LWDiDResultsaBaseResultsimplementation and exposingaggregate("event_study") -> EventStudyResults.A different public surface is possible, but it should provide equivalent dataframe, dictionary, plotting, and confidence-band behaviour rather than leaving the information as an internal dictionary.
Acceptance criteria
main.