# idid: instrumented difference-in-differences
`idid` implements instrumented difference-in-differences (IDiD)
estimators for cohort-time local average treatment effects on the
treated, $LATT(e,t)$. The package covers both balanced panel data and
repeated cross-sections, and includes the doubly robust (DR) and double
machine learning (DML) estimators developed in the
[paper](https://arxiv.org/abs/2605.03699). It also provides tools for
aggregating cohort-time effects into event-study and summary parameters,
together with plotting utilities for inspecting the aggregated effects.
Links:
- Documentation:
- Paper: [Raaschou-Pedersen (2026)](https://arxiv.org/abs/2605.03699)
- Repository:
## Installation
``` bash
uv pip install git+https://github.com/jsr-p/idid
```
or from PyPi:
``` bash
uv pip install idid-py
```
### Try it out
With [uv](https://docs.astral.sh/uv/) run the following:
``` bash
uv run --with "git+https://github.com/jsr-p/idid" python -i -c '
import idid
import polars as pl
res = idid.estimate(
data=(data := idid.sim_stag_panel(n=10_000, T=5, E_cohorts=[0, 2, 3, 4, 5])),
cohort="E",
time="t",
outcome="Y_t",
treatment="D_t",
unit="id",
covariates=["X"],
control="never",
method="dr",
balanced=True,
verbose=False,
)
print(res)
res.summary()
'
```
and inspect the results in the interactive shell.
## Minimal Example
``` python
import idid
data = idid.sim_stag_panel(n=10_000, T=5, E_cohorts=[0, 2, 3, 4, 5])
print(data.head())
```
shape: (5, 6)
┌─────┬─────┬─────┬──────────┬─────┬──────────┐
│ id ┆ E ┆ t ┆ X ┆ D_t ┆ Y_t │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ i64 ┆ f64 ┆ i64 ┆ f64 │
╞═════╪═════╪═════╪══════════╪═════╪══════════╡
│ 0 ┆ 2 ┆ 1 ┆ 0.496714 ┆ 1 ┆ 1.429874 │
│ 0 ┆ 2 ┆ 2 ┆ 0.496714 ┆ 1 ┆ 1.89475 │
│ 0 ┆ 2 ┆ 3 ┆ 0.496714 ┆ 0 ┆ 0.573911 │
│ 0 ┆ 2 ┆ 4 ┆ 0.496714 ┆ 0 ┆ 2.3326 │
│ 0 ┆ 2 ┆ 5 ┆ 0.496714 ┆ 1 ┆ 4.496302 │
└─────┴─────┴─────┴──────────┴─────┴──────────┘
``` python
res = idid.estimate(
data,
cohort="E",
time="t",
outcome="Y_t",
treatment="D_t",
unit="id",
covariates=["X"],
control="never",
method="dr",
balanced=True,
verbose=False,
)
res.summary()
```
Cohort-Time Local Average Treatment Effects on the Treated:
E t AET(e, t) LATT(e, t) Std. Error [95% Pointwise. Conf. Band]
2 2 0.1950 1.2059 0.2793 0.6586 1.7533 *
2 3 0.1918 0.9700 0.2806 0.4201 1.5200 *
2 4 0.2445 1.0540 0.2245 0.6141 1.4940 *
2 5 0.2147 1.3310 0.2582 0.8249 1.8371 *
3 3 0.2117 0.5478 0.2551 0.0478 1.0478 *
3 4 0.2447 0.7353 0.2188 0.3063 1.1642 *
3 5 0.2260 0.7074 0.2401 0.2368 1.1781 *
4 4 0.2656 0.9291 0.2026 0.5320 1.3261 *
4 5 0.2661 1.0409 0.2047 0.6397 1.4420 *
5 5 0.2261 0.8852 0.2399 0.4151 1.3554 *
---
Signif. codes: `*' confidence band does not cover 0
Control group: Never treated
Estimation Method: Doubly Robust
``` python
dynamic = idid.agg_latt(res, method="dynamic")
dynamic.summary()
```
Overall summary of ATT's based on event-study/dynamic aggregation:
LATT Std. Error [95% Conf. Band]
1.0047 0.1281 0.7537 1.2557 *
Dynamic effects:
Event time Estimate Std. Error [95% Pointwise Conf. Band]
0 0.8868 0.0946 0.7013 1.0723 *
1 0.9157 0.1215 0.6775 1.1539 *
2 0.8853 0.1631 0.5656 1.2051 *
3 1.3310 0.2582 0.8249 1.8371 *
---
Signif. codes: `*' confidence band does not cover 0
Control group: Never treated
Estimation Method: Doubly Robust
## Documentation
For the user guide, see the
[documentation](https://jsr-p.github.io/idid/), in particular the
[quickstart](https://jsr-p.github.io/idid/quickstart).
## Replication of simulation experiments from paper
Run `just replication`.
See the [Justfile](https://github.com/jsr-p/idid/blob/main/Justfile) and
[scripts/sim_exps.py](https://github.com/jsr-p/idid/blob/main/scripts/sim_exps.py).
## Notes
- When `control="never"`, the never-exposed cohort must be coded as
`E = 0` (compared to the $E = \infty$ notation in the paper).
## Citation
If you use `idid`, please cite [Raaschou-Pedersen
(2026)](https://arxiv.org/abs/2605.03699).