From 498771c6cd684760f27a8b03914fbfd3e2ed4221 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 22 Feb 2026 17:46:11 -0500 Subject: [PATCH 1/3] Add ContinuousDiD to ReadTheDocs API reference and choosing guide Wire the v2.6 ContinuousDiD estimator into Sphinx documentation: - New docs/api/continuous_did.rst with autoclass directives, examples, and comparison table vs CallawaySantAnna - Add estimator, results, data-prep, and module entries to api/index.rst - Add continuous treatment to decision flowchart, quick reference table, detailed guidance section, and SE table in choosing_estimator.rst Co-Authored-By: Claude Opus 4.6 --- docs/api/continuous_did.rst | 143 ++++++++++++++++++++++++++++++++++++ docs/api/index.rst | 5 ++ docs/choosing_estimator.rst | 37 ++++++++++ 3 files changed, 185 insertions(+) create mode 100644 docs/api/continuous_did.rst diff --git a/docs/api/continuous_did.rst b/docs/api/continuous_did.rst new file mode 100644 index 0000000..cf98bff --- /dev/null +++ b/docs/api/continuous_did.rst @@ -0,0 +1,143 @@ +Continuous Difference-in-Differences +===================================== + +Continuous DiD estimator for dose-response curves with continuous treatment intensity. + +This module implements the methodology from Callaway, Goodman-Bacon & Sant'Anna (2024), +"Difference-in-Differences with a Continuous Treatment" (NBER WP 32117), which: + +1. **Estimates dose-response curves**: ATT(d) and ACRT(d) as functions of dose +2. **Computes summary parameters**: Overall ATT and ACRT aggregated across doses +3. **Uses B-spline smoothing**: Flexible nonparametric estimation of dose-response functions +4. **Supports multiplier bootstrap**: Valid inference with proper standard errors and CIs + +**When to use Continuous DiD:** + +- Treatment varies in **intensity or dose** across units (not just binary on/off) +- You want to estimate how effects change with treatment dose +- Staggered adoption with heterogeneous dose levels +- You need the full dose-response curve, not just a single average effect + +**Reference:** Callaway, B., Goodman-Bacon, A., & Sant'Anna, P. H. C. (2024). +Difference-in-Differences with a Continuous Treatment. *NBER Working Paper* 32117. +``_ + +.. module:: diff_diff.continuous_did + +ContinuousDiD +-------------- + +Main estimator class for Continuous Difference-in-Differences. + +.. autoclass:: diff_diff.ContinuousDiD + :members: + :undoc-members: + :show-inheritance: + :inherited-members: + + .. rubric:: Methods + + .. autosummary:: + + ~ContinuousDiD.fit + ~ContinuousDiD.get_params + ~ContinuousDiD.set_params + +ContinuousDiDResults +-------------------- + +Results container for Continuous DiD estimation. + +.. autoclass:: diff_diff.continuous_did_results.ContinuousDiDResults + :members: + :undoc-members: + :show-inheritance: + + .. rubric:: Methods + + .. autosummary:: + + ~ContinuousDiDResults.summary + ~ContinuousDiDResults.print_summary + ~ContinuousDiDResults.to_dataframe + +DoseResponseCurve +----------------- + +Dose-response curve container for ATT(d) or ACRT(d). + +.. autoclass:: diff_diff.continuous_did_results.DoseResponseCurve + :members: + :undoc-members: + :show-inheritance: + + .. rubric:: Methods + + .. autosummary:: + + ~DoseResponseCurve.to_dataframe + +Example Usage +------------- + +Basic usage:: + + from diff_diff import ContinuousDiD, generate_continuous_did_data + + data = generate_continuous_did_data(n_units=200, seed=42) + + est = ContinuousDiD(n_bootstrap=199, seed=42) + results = est.fit(data, outcome='outcome', unit='unit', + time='period', first_treat='first_treat', + dose='dose', aggregate='dose') + results.print_summary() + +Accessing dose-response curves:: + + # ATT(d) dose-response curve as DataFrame + att_df = results.dose_response_att.to_dataframe() + print(att_df[['dose', 'effect', 'se', 'p_value']]) + + # ACRT(d) dose-response curve + acrt_df = results.dose_response_acrt.to_dataframe() + + # Overall summary parameters + print(f"Overall ATT: {results.overall_att:.3f} (SE: {results.overall_att_se:.3f})") + print(f"Overall ACRT: {results.overall_acrt:.3f} (SE: {results.overall_acrt_se:.3f})") + +Event study aggregation:: + + # Dynamic effects (binarized ATT by relative period) + results_es = est.fit(data, outcome='outcome', unit='unit', + time='period', first_treat='first_treat', + dose='dose', aggregate='eventstudy') + es_df = results_es.to_dataframe(level='event_study') + +Comparison with CallawaySantAnna +-------------------------------- + +.. list-table:: + :header-rows: 1 + :widths: 20 40 40 + + * - Feature + - ContinuousDiD + - CallawaySantAnna + * - Treatment type + - Continuous dose / intensity + - Binary (treated / not treated) + * - Target parameter + - ATT(d), ACRT(d), ATT_glob, ACRT_glob + - ATT(g,t), aggregated ATT + * - Smoothing + - B-spline basis for dose-response + - None (nonparametric group-time) + * - Dose-response curve + - Yes (full curve with CIs) + - No + * - Bootstrap + - Multiplier bootstrap (optional) + - Multiplier bootstrap (optional) + * - Control group + - never_treated / not_yet_treated + - never_treated / not_yet_treated diff --git a/docs/api/index.rst b/docs/api/index.rst index 0f6f403..4f41b4b 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -22,6 +22,7 @@ Core estimator classes for DiD analysis: diff_diff.StackedDiD diff_diff.TripleDifference diff_diff.TROP + diff_diff.ContinuousDiD Results Classes --------------- @@ -46,6 +47,8 @@ Result containers returned by estimators: diff_diff.TripleDifferenceResults diff_diff.StackedDiDResults diff_diff.trop.TROPResults + diff_diff.ContinuousDiDResults + diff_diff.DoseResponseCurve Visualization ------------- @@ -166,6 +169,7 @@ Utilities for preparing DiD data: :nosignatures: diff_diff.generate_did_data + diff_diff.generate_continuous_did_data diff_diff.make_treatment_indicator diff_diff.make_post_indicator diff_diff.wide_to_long @@ -190,6 +194,7 @@ Detailed documentation by module: stacked_did triple_diff trop + continuous_did results visualization diagnostics diff --git a/docs/choosing_estimator.rst b/docs/choosing_estimator.rst index 45eb179..ede12d6 100644 --- a/docs/choosing_estimator.rst +++ b/docs/choosing_estimator.rst @@ -8,6 +8,11 @@ Decision Flowchart Start here and follow the questions: +0. **Is treatment continuous?** (Units receive different doses or intensities) + + - **No** → Go to question 1 + - **Yes** → Use :class:`~diff_diff.ContinuousDiD` + 1. **Is treatment staggered?** (Different units treated at different times) - **No** → Go to question 2 @@ -58,6 +63,10 @@ Quick Reference - Few treated units, many controls - Synthetic parallel trends - ATT with unit/time weights + * - ``ContinuousDiD`` + - Continuous dose / treatment intensity + - Parallel trends across dose levels + - Dose-response curves ATT(d), ACRT(d) Detailed Guidance ----------------- @@ -173,6 +182,31 @@ Use :class:`~diff_diff.SyntheticDiD` when: # View the unit weights print(results.unit_weights) +Continuous Treatment +~~~~~~~~~~~~~~~~~~~~ + +Use :class:`~diff_diff.ContinuousDiD` when: + +- Treatment varies in **intensity or dose** (e.g., subsidy amount, hours of training) +- You want to estimate how effects change with treatment dose +- You need the full dose-response curve, not just a single average effect +- Staggered adoption where units receive different treatment levels + +.. code-block:: python + + from diff_diff import ContinuousDiD, generate_continuous_did_data + + data = generate_continuous_did_data(n_units=200, seed=42) + + est = ContinuousDiD(n_bootstrap=199, seed=42) + results = est.fit(data, outcome='outcome', unit='unit', + time='period', first_treat='first_treat', + dose='dose', aggregate='dose') + + # Overall effect and dose-response curve + print(f"Overall ATT: {results.overall_att:.3f}") + att_curve = results.dose_response_att.to_dataframe() + Common Pitfalls --------------- @@ -234,6 +268,9 @@ differences helps interpret results and choose appropriate inference. * - ``SyntheticDiD`` - Bootstrap or placebo-based - Default uses bootstrap resampling. Set ``n_bootstrap=0`` for placebo-based inference using pre-treatment residuals. + * - ``ContinuousDiD`` + - Analytical (default) + - Uses delta method SEs by default. Use ``n_bootstrap=199`` (or higher) for multiplier bootstrap inference with proper CIs. **Recommendations by sample size:** From f729d69ec84c9a95ed58ba7817d548bfd2195d31 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 22 Feb 2026 18:04:28 -0500 Subject: [PATCH 2/3] Add SPT identification caveat and data requirements per review Address P1: dose-response curves ATT(d)/ACRT(d) require Strong Parallel Trends (SPT); overall_att is ATT^{loc} under PT, equals ATT^{glob} only under SPT. Added notes in continuous_did.rst and choosing_estimator.rst. Address P2: document D=0 (untreated group) and balanced panel requirements in both the API reference and the choosing guide. Co-Authored-By: Claude Opus 4.6 --- docs/api/continuous_did.rst | 19 +++++++++++++++++-- docs/choosing_estimator.rst | 10 ++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/api/continuous_did.rst b/docs/api/continuous_did.rst index cf98bff..f787892 100644 --- a/docs/api/continuous_did.rst +++ b/docs/api/continuous_did.rst @@ -7,10 +7,20 @@ This module implements the methodology from Callaway, Goodman-Bacon & Sant'Anna "Difference-in-Differences with a Continuous Treatment" (NBER WP 32117), which: 1. **Estimates dose-response curves**: ATT(d) and ACRT(d) as functions of dose -2. **Computes summary parameters**: Overall ATT and ACRT aggregated across doses +2. **Computes summary parameters**: Overall ATT (binarized) and ACRT aggregated across doses 3. **Uses B-spline smoothing**: Flexible nonparametric estimation of dose-response functions 4. **Supports multiplier bootstrap**: Valid inference with proper standard errors and CIs +.. note:: + + **Identification assumptions.** The dose-response curves ATT(d) and ACRT(d), + as well as ATT\ :sup:`glob` and ACRT\ :sup:`glob`, require the **Strong Parallel + Trends (SPT)** assumption — that there is no selection into dose groups based on + treatment effects. Under the weaker standard Parallel Trends (PT) assumption, + only the binarized ATT\ :sup:`loc` (``overall_att``) is identified; it equals + ATT\ :sup:`glob` only when SPT holds. See Callaway, Goodman-Bacon & Sant'Anna + (2024), Assumptions 1–2. + **When to use Continuous DiD:** - Treatment varies in **intensity or dose** across units (not just binary on/off) @@ -18,6 +28,11 @@ This module implements the methodology from Callaway, Goodman-Bacon & Sant'Anna - Staggered adoption with heterogeneous dose levels - You need the full dose-response curve, not just a single average effect +**Data requirements:** + +- An **untreated group** (D = 0) must be present in the data +- A **balanced panel** is required (all units observed in all time periods) + **Reference:** Callaway, B., Goodman-Bacon, A., & Sant'Anna, P. H. C. (2024). Difference-in-Differences with a Continuous Treatment. *NBER Working Paper* 32117. ``_ @@ -127,7 +142,7 @@ Comparison with CallawaySantAnna - Continuous dose / intensity - Binary (treated / not treated) * - Target parameter - - ATT(d), ACRT(d), ATT_glob, ACRT_glob + - ATT\ :sup:`loc` (PT); ATT(d), ACRT(d), ATT\ :sup:`glob`, ACRT\ :sup:`glob` (SPT) - ATT(g,t), aggregated ATT * - Smoothing - B-spline basis for dose-response diff --git a/docs/choosing_estimator.rst b/docs/choosing_estimator.rst index ede12d6..de7348d 100644 --- a/docs/choosing_estimator.rst +++ b/docs/choosing_estimator.rst @@ -65,8 +65,8 @@ Quick Reference - ATT with unit/time weights * - ``ContinuousDiD`` - Continuous dose / treatment intensity - - Parallel trends across dose levels - - Dose-response curves ATT(d), ACRT(d) + - Strong Parallel Trends (SPT) for dose-response; PT for binarized ATT + - ATT\ :sup:`loc` (PT); ATT(d), ACRT(d) (SPT) Detailed Guidance ----------------- @@ -192,6 +192,12 @@ Use :class:`~diff_diff.ContinuousDiD` when: - You need the full dose-response curve, not just a single average effect - Staggered adoption where units receive different treatment levels +.. note:: + + Dose-response curves ATT(d) and ACRT(d) require **Strong Parallel Trends (SPT)**. + Under standard PT only the binarized ATT\ :sup:`loc` is identified. + Data must include an untreated group (D = 0) and a balanced panel. + .. code-block:: python from diff_diff import ContinuousDiD, generate_continuous_did_data From 37d2f961ae1697b9f3f164881a1b7863d94e97b7 Mon Sep 17 00:00:00 2001 From: igerber Date: Sun, 22 Feb 2026 18:16:20 -0500 Subject: [PATCH 3/3] Add time-invariant dose requirement to data requirements Co-Authored-By: Claude Opus 4.6 --- docs/api/continuous_did.rst | 1 + docs/choosing_estimator.rst | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api/continuous_did.rst b/docs/api/continuous_did.rst index f787892..01b2be7 100644 --- a/docs/api/continuous_did.rst +++ b/docs/api/continuous_did.rst @@ -32,6 +32,7 @@ This module implements the methodology from Callaway, Goodman-Bacon & Sant'Anna - An **untreated group** (D = 0) must be present in the data - A **balanced panel** is required (all units observed in all time periods) +- **Dose must be time-invariant** — each unit's dose is fixed across periods **Reference:** Callaway, B., Goodman-Bacon, A., & Sant'Anna, P. H. C. (2024). Difference-in-Differences with a Continuous Treatment. *NBER Working Paper* 32117. diff --git a/docs/choosing_estimator.rst b/docs/choosing_estimator.rst index de7348d..eb6366e 100644 --- a/docs/choosing_estimator.rst +++ b/docs/choosing_estimator.rst @@ -196,7 +196,8 @@ Use :class:`~diff_diff.ContinuousDiD` when: Dose-response curves ATT(d) and ACRT(d) require **Strong Parallel Trends (SPT)**. Under standard PT only the binarized ATT\ :sup:`loc` is identified. - Data must include an untreated group (D = 0) and a balanced panel. + Data must include an untreated group (D = 0), a balanced panel, and + time-invariant dose (each unit's dose is fixed across periods). .. code-block:: python