GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — diff-diff/docs/r_comparison.rst at main · UtilityTools/diff-diff

Expand file tree

/

r_comparison.rst

Copy path
More file actions
More file actions

Latest commit

 

History

History
History
463 lines (385 loc) · 11.1 KB
/

r_comparison.rst

Copy path

File metadata and controls

463 lines (385 loc) · 11.1 KB
Raw
Copy raw file
Download raw file
Outline
Edit and raw actions

R Comparison

This guide compares diff-diff with popular R packages for DiD analysis, helping users familiar with R transition to Python.

Overview

Feature diff-diff (Python) did (R) Other R
Basic DiD ✅ DifferenceInDifferences ✅ att_gt ✅ fixest::feols
Staggered DiD ✅ CallawaySantAnna ✅ att_gt did2s, DRDID
Covariate adjustment ✅ DR, IPW, Reg ✅ DR, IPW, Reg ✅ Varies
Honest DiD ✅ HonestDiD HonestDiD package N/A
Synthetic DiD ✅ SyntheticDiD synthdid package N/A
Wild bootstrap ✅ wild_bootstrap_se fwildclusterboot N/A

Package Correspondence

R did Package → diff-diff

The R did package by Callaway and Sant'Anna is the gold standard for staggered DiD. Here's how to translate common operations:

Basic estimation:

# R (did package)
library(did)
out <- att_gt(
  yname = "Y",
  tname = "period",
  idname = "id",
  gname = "G",
  data = data
)
# Python (diff-diff)
from diff_diff import CallawaySantAnna

cs = CallawaySantAnna()
results = cs.fit(
    data,
    outcome='Y',
    time='period',
    unit='id',
    first_treat='G'
)

With covariates (doubly robust):

# R
out <- att_gt(
  yname = "Y", tname = "period",
  idname = "id", gname = "G",
  xformla = ~ X1 + X2,
  est_method = "dr",
  data = data
)
# Python
cs = CallawaySantAnna(estimation_method='dr')
results = cs.fit(
    data,
    outcome='Y',
    time='period',
    unit='id',
    first_treat='G',
    covariates=['X1', 'X2']
)

Aggregations:

# R
agg_simple <- aggte(out, type = "simple")
agg_dynamic <- aggte(out, type = "dynamic")
agg_group <- aggte(out, type = "group")
# Python (unlike R's aggte(), aggregation is requested at fit time)
results = cs.fit(data, outcome='Y', time='period', unit='id',
                 first_treat='G', aggregate='all')
overall_att = results.overall_att  # Simple aggregation
event_study = results.event_study_effects  # Dynamic
by_group = results.group_effects  # By cohort

R HonestDiD Package → diff-diff

The HonestDiD package implements Rambachan & Roth (2023) sensitivity analysis:

Relative magnitudes (ΔRM):

# R
library(HonestDiD)
delta_rm_results <- createSensitivityResults_relativeMagnitudes(
  betahat = beta_hat,
  sigma = sigma,
  numPrePeriods = 4,
  numPostPeriods = 3,
  Mbarvec = seq(0, 2, by = 0.5)
)
# Python
from diff_diff import HonestDiD

honest = HonestDiD(method='relative_magnitude', M=1.0)
results = honest.fit(event_study_results)

# Sensitivity analysis over M grid
sensitivity = honest.sensitivity_analysis(
    event_study_results,
    M_grid=[0, 0.5, 1.0, 1.5, 2.0]
)

Smoothness restrictions (ΔSD):

# R
delta_sd_results <- createSensitivityResults(
  betahat = beta_hat,
  sigma = sigma,
  numPrePeriods = 4,
  numPostPeriods = 3,
  Mvec = seq(0, 0.1, by = 0.02)
)
# Python
from diff_diff import HonestDiD

honest = HonestDiD(method='smoothness', M=0.05)
results = honest.fit(event_study_results)

R synthdid Package → diff-diff

The synthdid package implements Arkhangelsky et al. (2021):

# R
library(synthdid)
setup <- panel.matrices(data, unit = "unit", time = "time",
                        outcome = "Y", treatment = "treatment")
tau.hat <- synthdid_estimate(setup$Y, setup$N0, setup$T0)
# Python
from diff_diff import SyntheticDiD

# SyntheticDiD requires a time-invariant ever-treated indicator
data['ever_treated'] = data.groupby('unit')['treatment'].transform('max')

# Derive post-treatment periods from treatment timing
post_periods = sorted(data.loc[data['treatment'] == 1, 'time'].unique())

sdid = SyntheticDiD()
results = sdid.fit(
    data,
    outcome='Y',
    unit='unit',
    time='time',
    treatment='ever_treated',
    post_periods=post_periods
)

Heterogeneous Adoption (HAD)

When every unit is treated at the post period (universal-rollout policies, industry-wide regime changes) but treatment intensity varies across units, the standard R workhorses (did, fixest, synthdid, DIDmultiplegtDYN) assume an untreated comparison group exists and do not apply. The dedicated R package DIDHAD (de Chaisemartin et al., August 2025) covers the QUG case (Design 1', d_lower = 0) from the same arXiv paper.

diff-diff ships :class:`~diff_diff.HeterogeneousAdoptionDiD`, which implements de Chaisemartin, Ciccia, D'Haultfoeuille and Knau (2026, arXiv:2405.04465v6) and adds two surfaces beyond the QUG-focused R package: Design 1 (no QUG, d_lower > 0, targets WAS_{d_lower} under Assumption 6 or sign-only under Assumption 5), and survey-design integration via Binder (1983) Taylor-series linearization (sampling weights + optional strata / PSU / FPC). The diagnostic battery :func:`~diff_diff.did_had_pretest_workflow` surfaces violations of the HAD identification assumptions (the design path is auto-detected separately by :meth:`HeterogeneousAdoptionDiD.fit` from the dose support).

import numpy as np
import pandas as pd
from diff_diff import HeterogeneousAdoptionDiD

# Build a HAD-shape panel: D=0 in pre-periods (t < F), D > 0 only at F+.
rng = np.random.default_rng(42)
G, F, T = 200, 4, 5
doses = rng.beta(0.5, 1.0, size=G)
rows = []
for g in range(G):
    for t in range(1, T + 1):
        y = (rng.normal()
             + (doses[g] + doses[g] ** 2) * (t >= F)
             + rng.normal(0, 0.5))
        d = doses[g] if t >= F else 0.0
        rows.append({'unit': g, 'period': t, 'y': y, 'dose': d})
had_data = pd.DataFrame(rows)

est = HeterogeneousAdoptionDiD()
results = est.fit(had_data, outcome_col='y', unit_col='unit',
                  time_col='period', dose_col='dose',
                  aggregate='event_study')

Key Differences

Design Philosophy

  • diff-diff: sklearn-style API with fit() method, returning rich result objects
  • R packages: Function-based, returning lists or S3/S4 objects

Inference

  • diff-diff: Analytical SEs by default, wild bootstrap available
  • R did: Multiplier bootstrap by default

Fixed Effects

  • diff-diff: absorb parameter for high-dimensional FE (within transformation)
  • R fixest: feols with | notation for absorbed FE

Output Format

diff-diff results have convenience methods:

results.summary()       # Print formatted table
results.to_dict()       # Dictionary representation
results.to_dataframe()  # pandas DataFrame

Feature Comparison Table

Feature diff-diff R did R HonestDiD R synthdid
Basic 2x2 DiD ✅ ✅ ❌ ❌
TWFE ✅ ❌ ❌ ❌
Staggered DiD (CS) ✅ ✅ ❌ ❌
Covariate adjustment ✅ ✅ ❌ ❌
Doubly robust ✅ ✅ ❌ ❌
Group-time effects ✅ ✅ ❌ ❌
Event study ✅ ✅ ✅ ❌
Synthetic DiD ✅ ❌ ❌ ✅
Honest DiD (ΔRM) ✅ ❌ ✅ ❌
Honest DiD (ΔSD) ✅ ❌ ✅ ❌
Wild bootstrap ✅ ❌ ❌ ❌
Cluster-robust SE ✅ ✅ ❌ ✅
Placebo tests ✅ ❌ ❌ ✅
Parallel trends tests ✅ ✅ ❌ ❌
Bacon decomposition ✅ ❌ ❌ ❌
Sun-Abraham ✅ ❌ ❌ ❌
Imputation DiD ✅ ❌ ❌ ❌
Two-Stage DiD (did2s) ✅ ❌ ❌ ❌
Stacked DiD ✅ ❌ ❌ ❌
Continuous DiD ✅ ✅ ❌ ❌
Triple Difference (DDD) ✅ ❌ ❌ ❌
TROP ✅ ❌ ❌ ❌
Efficient DiD ✅ ❌ ❌ ❌
Heterogeneous adoption (HAD) ✅ ❌ ❌ ❌

Note

R equivalents for estimators not covered by the did, HonestDiD, or synthdid packages: Sun-Abraham is available via fixest::sunab(); Imputation DiD via the didimputation package; Two-Stage DiD via the did2s package; Bacon Decomposition via the bacondecomp package; Stacked DiD requires manual implementation or the stackedev package; Continuous DiD is available via the did package continuous extension; Triple Difference requires manual implementation in R. TROP and Efficient DiD have no direct R equivalents. HeterogeneousAdoptionDiD (dCDH 2026) overlaps with the dedicated R package DIDHAD (de Chaisemartin et al., 2025), which covers the QUG case (Design 1'); diff-diff additionally covers Design 1 (no QUG, WAS_{d_lower}) and survey-design integration via Binder TSL.

Migration Tips

  1. Column names: diff-diff uses string column names, similar to R packages
  2. Formula interface: diff-diff supports R-style formulas for basic DiD: formula='y ~ treated * post'
  3. Results access: Use .att, .se, .ci instead of $att, $se
  4. Visualization: plot_event_study() produces matplotlib figures similar to ggdid() output
  5. Missing data: diff-diff requires complete data; use balance_panel() or dropna() first
  6. Heterogeneous Adoption (HAD): If you need surfaces the R DIDHAD package does not cover - Design 1 (no QUG, WAS_{d_lower}) or survey-design integration - reach for :class:`~diff_diff.HeterogeneousAdoptionDiD`. See the Heterogeneous Adoption (HAD) section above for the migration pattern.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.