Skip to content

Commit 3340cc7

Browse files
authored
Merge pull request #210 from igerber/seo
Add SEO infrastructure and AI discoverability
2 parents 744ae97 + 6677b73 commit 3340cc7

17 files changed

Lines changed: 1776 additions & 26 deletions

.readthedocs.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ version: 2
55

66
build:
77
os: ubuntu-22.04
8+
apt_packages:
9+
- pandoc
810
tools:
911
python: "3.11"
1012
jobs:
@@ -16,7 +18,7 @@ build:
1618
#
1719
# Keep in sync with pyproject.toml [project.dependencies]
1820
# and [project.optional-dependencies.docs].
19-
- pip install "numpy>=1.20.0" "pandas>=1.3.0" "scipy>=1.7.0" "sphinx>=6.0" "sphinx-rtd-theme>=1.0"
21+
- pip install "numpy>=1.20.0" "pandas>=1.3.0" "scipy>=1.7.0" "sphinx>=6.0" "pydata-sphinx-theme>=0.15" "sphinxext-opengraph>=0.9" "sphinx-sitemap>=2.5" "nbsphinx>=0.9" "matplotlib>=3.5"
2022

2123
# Build documentation in the "docs/" directory with Sphinx
2224
sphinx:

CITATION.cff

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
cff-version: 1.2.0
2+
title: "diff-diff: Difference-in-Differences Causal Inference for Python"
3+
message: "If you use this software, please cite it as below."
4+
type: software
5+
authors:
6+
- name: "diff-diff contributors"
7+
license: MIT
8+
version: "2.7.1"
9+
date-released: "2026-03-18"
10+
url: "https://github.com/igerber/diff-diff"
11+
repository-code: "https://github.com/igerber/diff-diff"
12+
keywords:
13+
- difference-in-differences
14+
- causal-inference
15+
- econometrics
16+
- python
17+
- treatment-effects
18+
- event-study
19+
- staggered-adoption
20+
- parallel-trends
21+
- synthetic-control
22+
- panel-data
23+
abstract: >-
24+
A Python library for Difference-in-Differences (DiD) causal inference analysis.
25+
Provides sklearn-like estimators for modern DiD methods including
26+
Callaway-Sant'Anna, Synthetic DiD, Honest DiD, event studies, and parallel
27+
trends testing. Validated against R packages (did, synthdid, fixest).

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# diff-diff
22

3+
[![PyPI version](https://img.shields.io/pypi/v/diff-diff.svg)](https://pypi.org/project/diff-diff/)
4+
[![Python versions](https://img.shields.io/pypi/pyversions/diff-diff.svg)](https://pypi.org/project/diff-diff/)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6+
[![Downloads](https://img.shields.io/pypi/dm/diff-diff.svg)](https://pypi.org/project/diff-diff/)
7+
[![Documentation](https://readthedocs.org/projects/diff-diff/badge/?version=stable)](https://diff-diff.readthedocs.io/en/stable/)
8+
39
A Python library for Difference-in-Differences (DiD) causal inference analysis with an sklearn-like API and statsmodels-style outputs.
410

511
## Installation
@@ -2909,6 +2915,21 @@ The `HonestDiD` module implements sensitivity analysis methods for relaxing the
29092915

29102916
- **Cunningham, S. (2021).** *Causal Inference: The Mixtape*. Yale University Press. [https://mixtape.scunning.com/](https://mixtape.scunning.com/)
29112917

2918+
## Citing diff-diff
2919+
2920+
If you use diff-diff in your research, please cite it:
2921+
2922+
```bibtex
2923+
@software{diff_diff,
2924+
title = {diff-diff: Difference-in-Differences Causal Inference for Python},
2925+
author = {{diff-diff contributors}},
2926+
url = {https://github.com/igerber/diff-diff},
2927+
license = {MIT},
2928+
}
2929+
```
2930+
2931+
See [`CITATION.cff`](CITATION.cff) for the full citation metadata.
2932+
29122933
## License
29132934

29142935
MIT License

TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Deferred items from PR reviews that were not addressed before merge.
6363
| R comparison tests spawn separate `Rscript` per test (slow CI) | `tests/test_methodology_twfe.py:294` | #139 | Low |
6464
| CS R helpers hard-code `xformla = ~ 1`; no covariate-adjusted R benchmark for IRLS path | `tests/test_methodology_callaway.py` | #202 | Low |
6565
| Context-dependent doc snippets pass via blanket NameError; no standalone validation | `tests/test_doc_snippets.py`, `docs/api/visualization.rst`, `docs/python_comparison.rst`, `docs/r_comparison.rst` | #206 | Low |
66+
| ~1,460 `duplicate object description` Sphinx warnings — each class attribute is documented in both module API pages and autosummary stubs; fix by adding `:no-index:` to one location or restructuring API docs to avoid overlap | `docs/api/*.rst`, `docs/api/_autosummary/` || Low |
6667

6768
---
6869

docs/_static/custom.css

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ table.docutils td, table.docutils th {
2626
font-weight: bold;
2727
}
2828

29-
/* Method/function signature styling */
30-
.sig-name {
31-
font-weight: bold;
32-
}
33-
3429
/* Better parameter list styling */
3530
.field-list {
3631
margin-top: 1em;

docs/_templates/layout.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% extends "pydata_sphinx_theme/layout.html" %}
2+
{% block extrahead %}
3+
{{ super() }}
4+
<script type="application/ld+json">
5+
{
6+
"@context": "https://schema.org",
7+
"@type": "SoftwareApplication",
8+
"name": "diff-diff",
9+
"description": "Python library for Difference-in-Differences causal inference analysis with sklearn-like API",
10+
"applicationCategory": "Scientific Software",
11+
"operatingSystem": "Cross-platform",
12+
"programmingLanguage": "Python",
13+
"url": "https://diff-diff.readthedocs.io",
14+
"downloadUrl": "https://pypi.org/project/diff-diff/",
15+
"softwareVersion": "{{ release }}",
16+
"license": "https://opensource.org/licenses/MIT",
17+
"offers": {"@type": "Offer", "price": "0", "priceCurrency": "USD"},
18+
"author": {"@type": "Organization", "name": "diff-diff contributors"}
19+
}
20+
</script>
21+
{% endblock %}

docs/benchmarks.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.. meta::
2+
:description: Validation benchmarks comparing diff-diff against R packages (did, synthdid, fixest). Coefficient accuracy, standard error comparison, and performance metrics.
3+
:keywords: difference-in-differences benchmark, DiD validation R, python econometrics accuracy, did package comparison
4+
15
Benchmarks: Validation Against R Packages
26
=========================================
37

docs/choosing_estimator.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.. meta::
2+
:description: Guide to choosing the right Difference-in-Differences estimator. Covers basic DiD, TWFE, staggered adoption methods (Callaway-Sant'Anna, Sun-Abraham), Synthetic DiD, and more.
3+
:keywords: which DiD estimator, staggered DiD estimator, difference-in-differences method selection, TWFE alternatives
4+
15
Choosing an Estimator
26
=====================
37

docs/conf.py

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88

99
# Add repository root to sys.path so autodoc imports from checked-out source
1010
# without needing pip install (which would require the Rust/maturin toolchain).
11-
# Note: visualization.py lazily imports matplotlib inside functions, so it is
12-
# not needed as a build dependency. If a future module adds a top-level
13-
# matplotlib import, add it to the RTD dep list in .readthedocs.yaml.
1411
sys.path.insert(0, os.path.abspath(".."))
1512

1613
import diff_diff
@@ -30,10 +27,13 @@
3027
"sphinx.ext.viewcode",
3128
"sphinx.ext.intersphinx",
3229
"sphinx.ext.mathjax",
30+
"sphinxext.opengraph",
31+
"sphinx_sitemap",
32+
"nbsphinx",
3333
]
3434

3535
templates_path = ["_templates"]
36-
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
36+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "llms.txt", "llms-full.txt"]
3737

3838
# -- Options for autodoc -----------------------------------------------------
3939
autodoc_default_options = {
@@ -62,17 +62,56 @@
6262
napoleon_attr_annotations = True
6363

6464
# -- Options for HTML output -------------------------------------------------
65-
html_theme = "sphinx_rtd_theme"
65+
html_theme = "pydata_sphinx_theme"
6666
html_static_path = ["_static"]
67+
html_title = "diff-diff: Difference-in-Differences Causal Inference for Python"
68+
# Use RTD's canonical URL when available; fall back to stable for local builds.
69+
_canonical_url = os.environ.get(
70+
"READTHEDOCS_CANONICAL_URL",
71+
"https://diff-diff.readthedocs.io/en/stable/",
72+
)
73+
html_baseurl = _canonical_url
74+
html_extra_path = ["llms.txt", "llms-full.txt"]
75+
sitemap_url_scheme = "{link}"
6776

6877
html_theme_options = {
78+
"icon_links": [
79+
{
80+
"name": "GitHub",
81+
"url": "https://github.com/igerber/diff-diff",
82+
"icon": "fa-brands fa-github",
83+
},
84+
{
85+
"name": "PyPI",
86+
"url": "https://pypi.org/project/diff-diff/",
87+
"icon": "fa-brands fa-python",
88+
},
89+
],
6990
"navigation_depth": 4,
70-
"collapse_navigation": False,
71-
"sticky_navigation": True,
72-
"includehidden": True,
73-
"titles_only": False,
91+
"show_toc_level": 2,
92+
"use_edit_page_button": True,
7493
}
7594

95+
html_context = {
96+
"github_user": "igerber",
97+
"github_repo": "diff-diff",
98+
"github_version": "main",
99+
"doc_path": "docs",
100+
}
101+
102+
# -- Options for sphinxext-opengraph -----------------------------------------
103+
ogp_site_url = _canonical_url
104+
ogp_site_name = "diff-diff"
105+
ogp_description_length = 200
106+
ogp_type = "website"
107+
ogp_enable_meta_description = True
108+
ogp_social_cards = {
109+
"line_color": "#1f77b4",
110+
}
111+
112+
# -- Options for nbsphinx ---------------------------------------------------
113+
nbsphinx_execute = "never"
114+
76115
# -- Options for intersphinx -------------------------------------------------
77116
intersphinx_mapping = {
78117
"python": ("https://docs.python.org/3", None),
@@ -83,19 +122,17 @@
83122

84123
# -- ReadTheDocs version-aware banner ----------------------------------------
85124
# Shows a warning on development builds so users know they may be reading
86-
# docs for unreleased features. Only activates on RTD (not local builds).
125+
# docs for unreleased features. Uses PyData theme's announcement bar on RTD,
126+
# falls back to rst_prolog for local builds.
87127
rtd_version = os.environ.get("READTHEDOCS_VERSION", "")
88128
rtd_version_type = os.environ.get("READTHEDOCS_VERSION_TYPE", "")
89129

90130
if rtd_version == "latest" or rtd_version_type == "branch":
91-
rst_prolog = """
92-
.. warning::
93-
94-
This documentation is for the **development version** of diff-diff.
95-
It may describe features not yet available in the latest PyPI release.
96-
For stable documentation, use the version selector (bottom-left) to switch to **stable**.
97-
98-
"""
131+
html_theme_options["announcement"] = (
132+
"This documentation is for the <strong>development version</strong> of diff-diff. "
133+
"It may describe features not yet available in the latest PyPI release. "
134+
'Use the version selector to switch to <a href="/en/stable/">stable</a>.'
135+
)
99136

100137
# -- Custom CSS --------------------------------------------------------------
101138
def setup(app):

docs/index.rst

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.. meta::
2+
:description: diff-diff — Python library for Difference-in-Differences causal inference. Callaway-Sant'Anna, Synthetic DiD, Honest DiD, event studies, parallel trends. sklearn-like API, validated against R.
3+
:keywords: difference-in-differences, python, causal inference, DiD, econometrics, treatment effects, staggered adoption, event study
4+
15
diff-diff: Difference-in-Differences in Python
26
==============================================
37

@@ -67,6 +71,84 @@ Quick Links
6771

6872
api/index
6973

74+
.. toctree::
75+
:maxdepth: 1
76+
:caption: Tutorials
77+
:hidden:
78+
79+
tutorials/01_basic_did
80+
tutorials/02_staggered_did
81+
tutorials/03_synthetic_did
82+
tutorials/04_parallel_trends
83+
tutorials/05_honest_did
84+
tutorials/06_power_analysis
85+
tutorials/07_pretrends_power
86+
tutorials/08_triple_diff
87+
tutorials/09_real_world_examples
88+
tutorials/10_trop
89+
tutorials/11_imputation_did
90+
tutorials/12_two_stage_did
91+
tutorials/13_stacked_did
92+
tutorials/14_continuous_did
93+
tutorials/15_efficient_did
94+
95+
What is Difference-in-Differences?
96+
----------------------------------
97+
98+
Difference-in-Differences (DiD) is a quasi-experimental research design that estimates
99+
causal treatment effects by comparing outcome changes over time between treated and
100+
control groups. It is one of the most widely used methods in applied economics,
101+
public policy evaluation, and social science research.
102+
103+
Why diff-diff?
104+
--------------
105+
106+
- **Complete method coverage**: 13+ estimators from basic 2x2 DiD to cutting-edge methods like Efficient DiD (Chen et al. 2025) and TROP (Athey et al. 2025)
107+
- **Familiar API**: sklearn-like ``fit()`` interface — if you know scikit-learn, you know diff-diff
108+
- **Modern staggered methods**: Callaway-Sant'Anna, Sun-Abraham, Imputation DiD, Two-Stage DiD, and Stacked DiD handle heterogeneous treatment timing correctly
109+
- **Robust inference**: Heteroskedasticity-robust, cluster-robust, wild cluster bootstrap, and multiplier bootstrap
110+
- **Sensitivity analysis**: Honest DiD (Rambachan & Roth 2023) for robust inference under parallel trends violations
111+
- **Validated against R**: Benchmarked against ``did``, ``synthdid``, and ``fixest`` — see :doc:`benchmarks`
112+
- **No heavy dependencies**: Only numpy, pandas, and scipy
113+
114+
Supported Estimators
115+
--------------------
116+
117+
.. list-table::
118+
:header-rows: 1
119+
:widths: 30 70
120+
121+
* - Estimator
122+
- Description
123+
* - :class:`~diff_diff.DifferenceInDifferences`
124+
- Basic 2x2 DiD with robust/clustered standard errors
125+
* - :class:`~diff_diff.TwoWayFixedEffects`
126+
- Panel data with unit and time fixed effects
127+
* - :class:`~diff_diff.MultiPeriodDiD`
128+
- Event study with period-specific treatment effects
129+
* - :class:`~diff_diff.CallawaySantAnna`
130+
- Callaway & Sant'Anna (2021) for staggered adoption
131+
* - :class:`~diff_diff.SunAbraham`
132+
- Sun & Abraham (2021) interaction-weighted estimator
133+
* - :class:`~diff_diff.ImputationDiD`
134+
- Borusyak, Jaravel & Spiess (2024) imputation estimator
135+
* - :class:`~diff_diff.TwoStageDiD`
136+
- Gardner (2022) two-stage residualized estimator
137+
* - :class:`~diff_diff.SyntheticDiD`
138+
- Synthetic DiD combining DiD and synthetic control
139+
* - :class:`~diff_diff.StackedDiD`
140+
- Wing, Freedman & Hollingsworth (2024) stacked DiD
141+
* - :class:`~diff_diff.EfficientDiD`
142+
- Chen, Sant'Anna & Xie (2025) efficient DiD
143+
* - :class:`~diff_diff.TripleDifference`
144+
- Triple difference (DDD) estimator
145+
* - :class:`~diff_diff.ContinuousDiD`
146+
- Continuous treatment DiD
147+
* - :class:`~diff_diff.TROP`
148+
- Triply Robust Panel with factor model adjustment (Athey et al. 2025)
149+
* - :class:`~diff_diff.BaconDecomposition`
150+
- Goodman-Bacon decomposition diagnostics
151+
70152
Indices and tables
71153
------------------
72154

0 commit comments

Comments
 (0)