<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>IPW and Doubly Robust | Carlos Mendez</title><link>https://carlos-mendez.org/category/ipw-and-doubly-robust/</link><atom:link href="https://carlos-mendez.org/category/ipw-and-doubly-robust/index.xml" rel="self" type="application/rss+xml"/><description>IPW and Doubly Robust</description><generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><copyright>Carlos Mendez</copyright><lastBuildDate>Tue, 24 Mar 2026 00:00:00 +0000</lastBuildDate><image><url>https://carlos-mendez.org/media/icon_huedfae549300b4ca5d201a9bd09a3ecd5_79625_512x512_fill_lanczos_center_3.png</url><title>IPW and Doubly Robust</title><link>https://carlos-mendez.org/category/ipw-and-doubly-robust/</link></image><item><title>Evaluating a Cash Transfer Program (RCT) with Panel Data in Stata</title><link>https://carlos-mendez.org/post/stata_rct/</link><pubDate>Tue, 24 Mar 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/stata_rct/</guid><description>&lt;h2 id="1-overview">1. Overview&lt;/h2>
&lt;p>Cash transfer programs are among the most common development interventions worldwide. Governments and international organizations spend billions of dollars each year providing direct cash transfers to low-income households. But how do we rigorously evaluate whether these programs actually work? This tutorial walks through the complete workflow of analyzing a &lt;strong>randomized controlled trial (RCT)&lt;/strong> with &lt;strong>panel data&lt;/strong> in Stata &amp;mdash; from verifying that randomization succeeded, to estimating treatment effects using increasingly sophisticated methods, to comparing results across all approaches.&lt;/p>
&lt;p>We use simulated data from a hypothetical cash transfer program targeting 2,000 households in a developing country. The key advantage of simulated data is that we know the &lt;strong>true treatment effect&lt;/strong> before we begin: the program increases household consumption by &lt;strong>12%&lt;/strong> (0.12 log points). This known ground truth gives us a perfect benchmark to evaluate how well each econometric method recovers the correct answer.&lt;/p>
&lt;p>The tutorial progresses from simple to sophisticated. We start with basic balance checks, then estimate treatment effects three different ways using only endline data &amp;mdash; regression adjustment (RA), inverse probability weighting (IPW), and doubly robust (DR) methods. Next, we unlock the full power of panel data with difference-in-differences (DiD) and its doubly robust extension (DRDID). Finally, we address the real-world complication of imperfect compliance.&lt;/p>
&lt;h3 id="learning-objectives">Learning objectives&lt;/h3>
&lt;ul>
&lt;li>Verify baseline balance using t-tests, standardized mean differences, and balance plots&lt;/li>
&lt;li>Distinguish between ATE and ATT and identify which estimand each method targets&lt;/li>
&lt;li>Understand three estimation strategies &amp;mdash; regression adjustment, inverse probability weighting, and doubly robust &amp;mdash; and when to use each&lt;/li>
&lt;li>Estimate treatment effects using all three approaches and compare their results&lt;/li>
&lt;li>Leverage panel data structure with difference-in-differences and understand why DiD estimates ATT&lt;/li>
&lt;li>Apply doubly robust difference-in-differences (DRDID) for modern panel data analysis&lt;/li>
&lt;li>Separate the effect of treatment offer from treatment receipt under imperfect compliance&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="2-study-design">2. Study design&lt;/h2>
&lt;p>This RCT evaluates a cash transfer program designed to boost household consumption. The study tracks 2,000 households across two survey waves &amp;mdash; a &lt;strong>baseline&lt;/strong> in 2021 (before the program) and an &lt;strong>endline&lt;/strong> in 2024 (after the program was implemented). The diagram below summarizes the experimental design.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
POP[&amp;quot;&amp;lt;b&amp;gt;2,000 Households&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Balanced panel&amp;lt;br/&amp;gt;(observed in 2021 and 2024)&amp;quot;]
STRAT[&amp;quot;&amp;lt;b&amp;gt;Stratified Randomization&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Within poverty strata&amp;quot;]
TRT[&amp;quot;&amp;lt;b&amp;gt;Treatment Group&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;(~1,000 households)&amp;lt;br/&amp;gt;Offered cash transfer&amp;quot;]
CTL[&amp;quot;&amp;lt;b&amp;gt;Control Group&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;(~1,000 households)&amp;lt;br/&amp;gt;No offer&amp;quot;]
COMP1[&amp;quot;85% receive&amp;lt;br/&amp;gt;the transfer&amp;quot;]
COMP2[&amp;quot;15% do not&amp;lt;br/&amp;gt;receive&amp;quot;]
COMP3[&amp;quot;5% receive&amp;lt;br/&amp;gt;the transfer&amp;quot;]
COMP4[&amp;quot;95% do not&amp;lt;br/&amp;gt;receive&amp;quot;]
BASE[&amp;quot;&amp;lt;b&amp;gt;Baseline 2021&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Pre-treatment survey&amp;quot;]
END[&amp;quot;&amp;lt;b&amp;gt;Endline 2024&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Post-treatment survey&amp;quot;]
POP --&amp;gt; BASE
BASE --&amp;gt; STRAT
STRAT --&amp;gt; TRT
STRAT --&amp;gt; CTL
TRT --&amp;gt; COMP1
TRT --&amp;gt; COMP2
CTL --&amp;gt; COMP3
CTL --&amp;gt; COMP4
COMP1 --&amp;gt; END
COMP2 --&amp;gt; END
COMP3 --&amp;gt; END
COMP4 --&amp;gt; END
style POP fill:#6a9bcc,stroke:#141413,color:#fff
style STRAT fill:#d97757,stroke:#141413,color:#fff
style TRT fill:#00d4c8,stroke:#141413,color:#141413
style CTL fill:#6a9bcc,stroke:#141413,color:#fff
style BASE fill:#6a9bcc,stroke:#141413,color:#fff
style END fill:#d97757,stroke:#141413,color:#fff
style COMP1 fill:#00d4c8,stroke:#141413,color:#141413
style COMP2 fill:#141413,stroke:#d97757,color:#fff
style COMP3 fill:#d97757,stroke:#141413,color:#fff
style COMP4 fill:#141413,stroke:#6a9bcc,color:#fff
&lt;/code>&lt;/pre>
&lt;p>The randomization was &lt;strong>stratified by poverty status&lt;/strong> (block randomization), ensuring that treatment and control groups started with similar proportions of poor and non-poor households. A critical real-world feature of this study is &lt;strong>imperfect compliance&lt;/strong> &amp;mdash; only 85% of households offered the treatment actually received the cash transfer, while 5% of control households received it through other channels.&lt;/p>
&lt;h3 id="variables">Variables&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variable&lt;/th>
&lt;th>Description&lt;/th>
&lt;th>Type&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>id&lt;/code>&lt;/td>
&lt;td>Household identifier&lt;/td>
&lt;td>Panel ID&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>year&lt;/code>&lt;/td>
&lt;td>Survey year (2021 or 2024)&lt;/td>
&lt;td>Time variable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>post&lt;/code>&lt;/td>
&lt;td>Endline indicator (1 = 2024)&lt;/td>
&lt;td>Binary&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>treat&lt;/code>&lt;/td>
&lt;td>Random assignment to offer (intent-to-treat)&lt;/td>
&lt;td>Binary&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>D&lt;/code>&lt;/td>
&lt;td>Actual receipt of cash transfer&lt;/td>
&lt;td>Binary (endogenous)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>y&lt;/code>&lt;/td>
&lt;td>Log monthly consumption&lt;/td>
&lt;td>Continuous (outcome)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>age&lt;/code>&lt;/td>
&lt;td>Age of household head&lt;/td>
&lt;td>Continuous&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>female&lt;/code>&lt;/td>
&lt;td>Female-headed household&lt;/td>
&lt;td>Binary&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>poverty&lt;/code>&lt;/td>
&lt;td>Poverty status at baseline&lt;/td>
&lt;td>Binary&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>edu&lt;/code>&lt;/td>
&lt;td>Years of education&lt;/td>
&lt;td>Continuous&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>y0&lt;/code>&lt;/td>
&lt;td>Log monthly consumption at baseline (pre-treatment)&lt;/td>
&lt;td>Continuous&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>&lt;strong>Offer vs. receipt&lt;/strong> &amp;mdash; The variable &lt;code>treat&lt;/code> captures random assignment to the program offer. It is exogenous (determined by randomization) and unrelated to household characteristics. The variable &lt;code>D&lt;/code> captures actual receipt of the cash transfer. It is &lt;strong>endogenous&lt;/strong> &amp;mdash; households that chose to take up the program may differ systematically from those that did not. Most methods in this tutorial estimate the effect of the &lt;strong>offer&lt;/strong> (intent-to-treat). Section 10 addresses the effect of &lt;strong>receipt&lt;/strong>.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="3-analytical-roadmap">3. Analytical roadmap&lt;/h2>
&lt;p>The diagram below shows the progression of methods we will use. Each stage builds on the previous one, adding complexity and robustness.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph LR
A[&amp;quot;&amp;lt;b&amp;gt;Balance&amp;lt;br/&amp;gt;Checks&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Section 5&amp;lt;/i&amp;gt;&amp;quot;]
B[&amp;quot;&amp;lt;b&amp;gt;Cross-sectional&amp;lt;br/&amp;gt;RA / IPW / DR&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Sections 7--8&amp;lt;/i&amp;gt;&amp;quot;]
C[&amp;quot;&amp;lt;b&amp;gt;Panel Data&amp;lt;br/&amp;gt;DiD / DR-DiD&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Section 9&amp;lt;/i&amp;gt;&amp;quot;]
D[&amp;quot;&amp;lt;b&amp;gt;Endogenous&amp;lt;br/&amp;gt;Treatment&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Section 10&amp;lt;/i&amp;gt;&amp;quot;]
A --&amp;gt; B
B --&amp;gt; C
C --&amp;gt; D
style A fill:#6a9bcc,stroke:#141413,color:#fff
style B fill:#d97757,stroke:#141413,color:#fff
style C fill:#00d4c8,stroke:#141413,color:#141413
style D fill:#141413,stroke:#d97757,color:#fff
&lt;/code>&lt;/pre>
&lt;p>We first establish that randomization worked (balance checks). Then we estimate treatment effects three ways using only endline data &amp;mdash; regression adjustment, inverse probability weighting, and doubly robust methods. Next, we leverage the full panel structure with difference-in-differences. Finally, we address imperfect compliance by separating the effect of the offer from the effect of receipt.&lt;/p>
&lt;hr>
&lt;h2 id="4-data-loading-and-exploration">4. Data loading and exploration&lt;/h2>
&lt;p>We begin by loading the simulated dataset from a public GitHub repository and examining its structure.&lt;/p>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/quarcs-lab/data-open/raw/master/ametrics/dataSIM4RCT.dta&amp;quot;, clear
des y age edu female poverty treat D
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Contains data
Observations: 4,000
Variables: 10
Variable Storage Display Value
name type format label Variable label
─────────────────────────────────────────────────────────────
y float %9.0g Log monthly consumption
age float %9.0g
edu float %9.0g
female float %9.0g
poverty float %9.0g
treat float %9.0g Assignment to offer (Z)
D float %9.0g Receipt of cash transfer
&lt;/code>&lt;/pre>
&lt;p>The dataset contains 4,000 observations &amp;mdash; 2,000 households observed at two time points (baseline 2021 and endline 2024). The outcome variable &lt;code>y&lt;/code> is log monthly consumption, &lt;code>treat&lt;/code> is the random assignment indicator, and &lt;code>D&lt;/code> is the actual receipt indicator.&lt;/p>
&lt;p>Now let us examine summary statistics at baseline and endline separately.&lt;/p>
&lt;pre>&lt;code class="language-stata">sum y age edu female poverty treat D if post==0
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Variable | Obs Mean Std. dev. Min Max
─────────────+─────────────────────────────────────────────────────────
y | 2,000 10.0154 .4348886 8.454445 11.48253
age | 2,000 35.126 9.650839 18 68
edu | 2,000 12.0275 1.9889 6 18
female | 2,000 .5085 .5000528 0 1
poverty | 2,000 .3125 .4636283 0 1
treat | 2,000 .518 .4998009 0 1
D | 2,000 0 0 0 0
&lt;/code>&lt;/pre>
&lt;p>At baseline, mean log consumption is approximately 10.02, the average household head is 35 years old with 12 years of education, about 51% of households are female-headed, and 31% are in poverty. Treatment assignment (&lt;code>treat&lt;/code>) is approximately 50%, as expected from the randomization. Crucially, the receipt variable &lt;code>D&lt;/code> is zero for all households at baseline &amp;mdash; the program had not yet been implemented.&lt;/p>
&lt;pre>&lt;code class="language-stata">sum y age edu female poverty treat D if post==1
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Variable | Obs Mean Std. dev. Min Max
─────────────+─────────────────────────────────────────────────────────
y | 2,000 10.1137 .4382183 8.638689 11.55002
age | 2,000 35.126 9.650839 18 68
edu | 2,000 12.0275 1.9889 6 18
female | 2,000 .5085 .5000528 0 1
poverty | 2,000 .3125 .4636283 0 1
treat | 2,000 .518 .4998009 0 1
D | 2,000 .4615 .4986402 0 1
&lt;/code>&lt;/pre>
&lt;p>At endline, mean consumption has risen to approximately 10.11, reflecting both the natural time trend and the treatment effect. The receipt variable &lt;code>D&lt;/code> is now non-zero &amp;mdash; about 46% of all households received the cash transfer (combining treated households who took up the program and control households who received it through other channels).&lt;/p>
&lt;p>Finally, we declare the panel structure so Stata knows we have repeated observations.&lt;/p>
&lt;pre>&lt;code class="language-stata">xtset id year
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Panel variable: id (strongly balanced)
Time variable: year, 2021 to 2024, but with gaps
Delta: 1 unit
&lt;/code>&lt;/pre>
&lt;p>The panel is &lt;strong>strongly balanced&lt;/strong> &amp;mdash; all 2,000 households appear in both survey waves, with no attrition. This is an ideal scenario that simplifies our analysis.&lt;/p>
&lt;hr>
&lt;h2 id="5-baseline-balance-checks">5. Baseline balance checks&lt;/h2>
&lt;p>Before estimating any treatment effects, we must verify that randomization produced comparable treatment and control groups at baseline. This is the most fundamental quality check in any RCT.&lt;/p>
&lt;h3 id="51-t-tests-and-proportion-tests">5.1 T-tests and proportion tests&lt;/h3>
&lt;p>We compare the treatment and control groups on all baseline characteristics using two-sample t-tests for continuous variables and proportion tests for binary variables.&lt;/p>
&lt;pre>&lt;code class="language-stata">ttest y if post==0, by(treat)
ttest age if post==0, by(treat)
ttest edu if post==0, by(treat)
prtest female if post==0, by(treat)
prtest poverty if post==0, by(treat)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Variable | Control Mean Treat Mean Diff p-value
────────────+──────────────────────────────────────────────
y | 10.025 10.006 0.019 0.330
age | 35.335 34.931 0.404 0.350
edu | 11.974 12.077 -0.103 0.247
female | 0.484 0.531 -0.046 0.038 **
poverty | 0.307 0.318 -0.011 0.612
&lt;/code>&lt;/pre>
&lt;p>Most variables show no statistically significant differences between the treatment and control groups. However, the variable &lt;code>female&lt;/code> has a p-value of 0.038 &amp;mdash; a statistically significant imbalance. The treatment group has about 4.6 percentage points more female-headed households than the control group. This imbalance occurred purely by chance but must be addressed in our estimation.&lt;/p>
&lt;h3 id="52-balance-table-with-standardized-mean-differences">5.2 Balance table with standardized mean differences&lt;/h3>
&lt;p>P-values are sensitive to sample size &amp;mdash; a large sample can make tiny differences &amp;ldquo;significant.&amp;rdquo; Standardized mean differences (SMDs) provide a scale-free measure of imbalance that is more informative. The SMD is computed as the difference in group means divided by the pooled standard deviation &amp;mdash; this puts all variables on the same scale regardless of their units. The common rule of thumb is that SMDs below 10% indicate adequate balance.&lt;/p>
&lt;pre>&lt;code class="language-stata">capture ssc install ietoolkit, replace
iebaltab y age edu female poverty if post==0, grpvar(treat)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> (1) (2) (2)-(1)
Control Treatment Difference
y 10.025 10.006 0.019
(0.014) (0.014) (0.019)
age 35.335 34.931 0.404
(0.316) (0.295) (0.432)
edu 11.974 12.077 -0.103
(0.063) (0.063) (0.089)
female 0.484 0.531 -0.046**
(0.016) (0.016) (0.022)
poverty 0.307 0.318 -0.011
(0.015) (0.014) (0.021)
N 964 1,036
&lt;/code>&lt;/pre>
&lt;p>The balance table confirms our t-test findings. With 964 control and 1,036 treatment households, all variables are well balanced except &lt;code>female&lt;/code>, which shows a statistically significant difference (marked with **). The outcome variable &lt;code>y&lt;/code> has a negligible difference of 0.019 at baseline &amp;mdash; the groups started with essentially identical consumption levels.&lt;/p>
&lt;h3 id="53-visual-balance-plot">5.3 Visual balance plot&lt;/h3>
&lt;p>A balance plot provides a visual overview of all SMDs at once, making it easy to spot problematic variables.&lt;/p>
&lt;pre>&lt;code class="language-stata">net install balanceplot, from(&amp;quot;https://tdmize.github.io/data&amp;quot;) replace
balanceplot y age edu i.female i.poverty, group(treat) table nodropdv
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_rct_balance_plot.png" alt="Balance plot showing standardized mean differences for all covariates. All variables fall within the 10% threshold, with female closest at approximately 9.3%.">&lt;/p>
&lt;p>The balance plot shows that all SMDs fall below the 10% threshold (indicated by the dashed vertical lines). The variable &lt;code>female&lt;/code> has the largest SMD at approximately 9.3% &amp;mdash; close to but still below the conventional threshold. The remaining variables &amp;mdash; consumption, age, education, and poverty &amp;mdash; all have SMDs well below 5%. Overall, randomization was successful, but we should control for &lt;code>female&lt;/code> (and other covariates) in our estimation to improve precision.&lt;/p>
&lt;h3 id="54-aipw-as-a-formal-balance-test">5.4 AIPW as a formal balance test&lt;/h3>
&lt;p>As a final and more formal balance check, we can use the Augmented Inverse Probability Weighting (AIPW) estimator on &lt;strong>baseline data only&lt;/strong>. If randomization was successful, the estimated &amp;ldquo;treatment effect&amp;rdquo; at baseline should be zero &amp;mdash; since the program had not yet been implemented, there should be no difference between groups.&lt;/p>
&lt;pre>&lt;code class="language-stata">preserve
keep if post==0
teffects aipw (y age edu i.female i.poverty) (treat age edu i.female i.poverty)
&lt;/code>&lt;/pre>
&lt;blockquote>
&lt;p>&lt;strong>Tip:&lt;/strong> The &lt;code>preserve&lt;/code> command saves a snapshot of the current data. After the balance analysis, use &lt;code>restore&lt;/code> to return to the full dataset. The companion do-file handles this automatically.&lt;/p>
&lt;/blockquote>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : augmented IPW
Outcome model : linear
Treatment model: logit
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATE |
treat |
(1 vs 0) | -.0244086 .018861 -1.29 0.196 -.0613754 .0125582
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.02792 .0138363 724.75 0.000 10.0008 10.05504
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The AIPW-estimated &amp;ldquo;ATE&amp;rdquo; at baseline is -0.024 with a p-value of 0.196 &amp;mdash; not statistically significant. This confirms that there is no detectable pre-treatment difference between the groups after adjusting for covariates. The treatment and control groups were statistically comparable before the program began.&lt;/p>
&lt;p>Now we run the diagnostic checks for the AIPW model.&lt;/p>
&lt;pre>&lt;code class="language-stata">tebalance overid
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Overidentification test for covariate balance
H0: Covariates are balanced
chi2(5) = 3.216
Prob &amp;gt; chi2 = 0.6670
&lt;/code>&lt;/pre>
&lt;p>The overidentification test fails to reject the null hypothesis of covariate balance (p = 0.667). There is no statistical evidence of residual imbalance after weighting.&lt;/p>
&lt;pre>&lt;code class="language-stata">tebalance summarize
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> |Standardized differences Variance ratio
| Raw Weighted Raw Weighted
----------------+------------------------------------------------
age | -.0417918 .0002505 .9318894 .9446877
edu | .0519015 -6.96e-06 1.071677 1.078214
female |
1 | .0929611 6.51e-06 .9970775 .9999996
poverty |
1 | .0226764 .0002864 1.018475 1.000233
&lt;/code>&lt;/pre>
&lt;p>The balance summary reveals that the raw standardized differences (before weighting) show the &lt;code>female&lt;/code> imbalance at 0.093, consistent with our earlier findings. After weighting, all standardized differences shrink to near zero (all below 0.001) &amp;mdash; excellent balance. The variance ratios are all close to 1.0, indicating similar spread across groups.&lt;/p>
&lt;pre>&lt;code class="language-stata">tebalance density y
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_rct_density_y.png" alt="Density plot showing the distribution of log consumption for treatment and control groups, before and after AIPW weighting. The weighted distributions overlap almost perfectly.">&lt;/p>
&lt;p>The density plot confirms that after AIPW weighting, the distributions of log consumption in the treatment and control groups overlap almost perfectly. Any small pre-existing differences in the outcome variable have been eliminated by the weighting scheme.&lt;/p>
&lt;pre>&lt;code class="language-stata">teffects overlap
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_rct_overlap_baseline.png" alt="Overlap plot showing kernel densities of estimated propensity scores for treatment and control groups. Both distributions span approximately 0.43 to 0.55 with substantial overlap.">&lt;/p>
&lt;p>The overlap plot shows that propensity scores for both groups are concentrated between approximately 0.43 and 0.55 &amp;mdash; well within the range where matching and weighting are feasible. There are no extreme propensity scores near 0 or 1, confirming that the common support condition is satisfied. This is expected in a well-designed RCT where treatment probability is approximately 0.50 for all households.&lt;/p>
&lt;pre>&lt;code class="language-stata">restore
&lt;/code>&lt;/pre>
&lt;p>This AIPW-based balance analysis also serves a pedagogical purpose: it introduces the concept of &lt;strong>doubly robust&lt;/strong> estimation before we use it for treatment effect estimation in Section 8.&lt;/p>
&lt;hr>
&lt;h2 id="6-what-are-we-estimating-ate-vs-att">6. What are we estimating? ATE vs. ATT&lt;/h2>
&lt;p>Before diving into estimation, we need to be precise about &lt;strong>what&lt;/strong> we are trying to estimate. There are two fundamental causal quantities in program evaluation.&lt;/p>
&lt;p>The &lt;strong>Average Treatment Effect (ATE)&lt;/strong> answers the policymaker&amp;rsquo;s question: &lt;em>&amp;ldquo;What would happen if we scaled this program to the entire population?&amp;quot;&lt;/em>&lt;/p>
&lt;p>$$ATE = E[Y(1) - Y(0)]$$&lt;/p>
&lt;p>where $Y(1)$ is the potential outcome under treatment and $Y(0)$ is the potential outcome under control, averaged over the &lt;strong>entire population&lt;/strong> (both treated and untreated).&lt;/p>
&lt;p>The &lt;strong>Average Treatment Effect on the Treated (ATT)&lt;/strong> answers the evaluator&amp;rsquo;s question: &lt;em>&amp;ldquo;Did the program benefit those who were assigned to it?&amp;quot;&lt;/em>&lt;/p>
&lt;p>$$ATT = E[Y(1) - Y(0) \mid T = 1]$$&lt;/p>
&lt;p>This averages the treatment effect only over the &lt;strong>treated group&lt;/strong> &amp;mdash; the households that were assigned to receive the cash transfer.&lt;/p>
&lt;p>In a well-designed RCT with &lt;strong>homogeneous treatment effects&lt;/strong> (the program affects everyone equally), ATE and ATT are the same. But when treatment effects are &lt;strong>heterogeneous&lt;/strong> (the program benefits some households more than others), they can differ. For example, if poorer households benefit more from cash transfers and the treatment group has a higher share of poor households, the ATT could be larger than the ATE.&lt;/p>
&lt;p>Understanding this distinction is critical because different methods target different estimands. Cross-sectional methods (RA, IPW, DR) can estimate &lt;strong>either&lt;/strong> ATE or ATT. Difference-in-differences inherently estimates the &lt;strong>ATT only&lt;/strong>. We will return to this point in Section 9.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Note on RCTs&lt;/strong> &amp;mdash; In a randomized experiment, treatment assignment is independent of potential outcomes. This means that simple comparisons between treatment and control groups are already unbiased estimates of the ATE. When we add covariates (regression adjustment, IPW, doubly robust), we are not removing bias &amp;mdash; we are &lt;strong>improving precision&lt;/strong> by accounting for residual variation. This is different from observational studies, where covariate adjustment is needed to address confounding.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="7-three-strategies-for-causal-estimation">7. Three strategies for causal estimation&lt;/h2>
&lt;p>We now understand &lt;em>what&lt;/em> we want to estimate (ATE and ATT from Section 6). The question becomes &lt;em>how&lt;/em> to estimate it. Three families of methods exist, each taking a fundamentally different approach to solving the missing-data problem at the heart of causal inference. Each method models a different part of the data-generating process, and understanding these differences is essential for interpreting results and choosing the right tool.&lt;/p>
&lt;h3 id="71-regression-adjustment-ra-----modeling-the-outcome">7.1 Regression Adjustment (RA) &amp;mdash; modeling the outcome&lt;/h3>
&lt;p>Regression adjustment solves the missing-data problem by &lt;strong>predicting the unobserved potential outcomes&lt;/strong>. It fits separate regression models for treated and untreated groups. For each household, it uses these models to predict two potential outcomes: what consumption would be if treated, $\hat{\mu}_1(X_i)$, and what consumption would be if untreated, $\hat{\mu}_0(X_i)$. Since we only observe one of these for each household, the model fills in the missing counterfactual. The treatment effect for each household is the difference between the two predictions, and the ATE is the average across all households.&lt;/p>
&lt;p>The Stata documentation describes this succinctly: &lt;em>&amp;ldquo;RA estimators use means of predicted outcomes for each treatment level to estimate each POM. ATEs and ATETs are differences in estimated POMs.&amp;quot;&lt;/em>&lt;/p>
&lt;p>&lt;strong>Analogy &amp;mdash; predicting exam scores.&lt;/strong> Imagine two study methods (A and B) being tested on students. You observe each student using only one method. RA fits a model predicting test scores based on student characteristics (prior GPA, hours studied) separately for method-A and method-B users. Then, for &lt;em>every&lt;/em> student, it predicts what their score would have been under &lt;em>both&lt;/em> methods &amp;mdash; even the one they did not use. The average difference in predicted scores is the treatment effect.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
DATA[&amp;quot;&amp;lt;b&amp;gt;Observed Data&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Each household observed&amp;lt;br/&amp;gt;under ONE treatment only&amp;quot;]
M0[&amp;quot;&amp;lt;b&amp;gt;Fit outcome model&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;using control group&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Y = f(age, edu, female, poverty)&amp;lt;/i&amp;gt;&amp;quot;]
M1[&amp;quot;&amp;lt;b&amp;gt;Fit outcome model&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;using treated group&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Y = f(age, edu, female, poverty)&amp;lt;/i&amp;gt;&amp;quot;]
P0[&amp;quot;Predict &amp;lt;b&amp;gt;Ŷ₀&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;for ALL households&amp;quot;]
P1[&amp;quot;Predict &amp;lt;b&amp;gt;Ŷ₁&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;for ALL households&amp;quot;]
ATE[&amp;quot;&amp;lt;b&amp;gt;ATE&amp;lt;/b&amp;gt; = Average of&amp;lt;br/&amp;gt;(Ŷ₁ − Ŷ₀)&amp;quot;]
DATA --&amp;gt; M0
DATA --&amp;gt; M1
M0 --&amp;gt; P0
M1 --&amp;gt; P1
P0 --&amp;gt; ATE
P1 --&amp;gt; ATE
style DATA fill:#141413,stroke:#6a9bcc,color:#fff
style M0 fill:#6a9bcc,stroke:#141413,color:#fff
style M1 fill:#6a9bcc,stroke:#141413,color:#fff
style P0 fill:#6a9bcc,stroke:#141413,color:#fff
style P1 fill:#6a9bcc,stroke:#141413,color:#fff
style ATE fill:#6a9bcc,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>The RA estimator.&lt;/strong> Formally, the ATE under regression adjustment is:&lt;/p>
&lt;p>$$\hat{\tau}_{RA}^{ATE} = \frac{1}{N} \sum_{i=1}^{N} \left[ \hat{\mu}_1(X_i) - \hat{\mu}_0(X_i) \right]$$&lt;/p>
&lt;p>where $\hat{\mu}_1(X)$ is the predicted outcome under treatment (fitted from treated observations) and $\hat{\mu}_0(X)$ is the predicted outcome under control (fitted from untreated observations), both evaluated at each household&amp;rsquo;s covariates $X_i$. In plain language: for each household, the model predicts what their consumption would be if they received the cash transfer and what it would be if they did not. The difference is the household&amp;rsquo;s estimated treatment effect. Averaging these across all $N$ households gives the ATE.&lt;/p>
&lt;p>For the ATT, we restrict the average to treated units only:&lt;/p>
&lt;p>$$\hat{\tau}_{RA}^{ATT} = \frac{1}{N_1} \sum_{i: T_i = 1} \left[ \hat{\mu}_1(X_i) - \hat{\mu}_0(X_i) \right]$$&lt;/p>
&lt;p>where $N_1$ is the number of treated households.&lt;/p>
&lt;p>&lt;strong>Mini example from our data.&lt;/strong> Consider Household A: a 40-year-old female in poverty with 10 years of education. The treated outcome model predicts her consumption at 10.17 log points. The untreated outcome model predicts 10.05. Her estimated individual treatment effect is $10.17 - 10.05 = 0.12$. Averaging such predictions over all 2,000 endline households gives the ATE.&lt;/p>
&lt;p>&lt;strong>Stata implementation.&lt;/strong> The &lt;code>teffects ra&lt;/code> command fits linear outcome models by default. The first parenthesis specifies the outcome model (outcome variable + covariates), and the second specifies the treatment variable: &lt;code>teffects ra (y c.age c.edu i.female i.poverty) (treat), ate&lt;/code>.&lt;/p>
&lt;p>&lt;strong>What can go wrong &amp;mdash; model misspecification.&lt;/strong> RA&amp;rsquo;s Achilles heel is that it relies entirely on the outcome model being correctly specified. If consumption depends on age nonlinearly (for example, a U-shaped relationship), but we assume a linear model, the predictions $\hat{\mu}_1$ and $\hat{\mu}_0$ will be systematically wrong, biasing the ATE. As the Stata manual notes, RA works well when the outcome model is correct, but &amp;ldquo;relying on a correctly specified outcome model with little data is extremely risky.&amp;rdquo; RA gives the right answer &lt;strong>only if the outcome model is correct&lt;/strong>. If it is wrong, the ATE estimate can be biased even with infinite data.&lt;/p>
&lt;p>What if we are unsure about the functional form of the outcome model? Is there an approach that avoids modeling the outcome entirely?&lt;/p>
&lt;h3 id="72-inverse-probability-weighting-ipw-----modeling-the-treatment-assignment">7.2 Inverse Probability Weighting (IPW) &amp;mdash; modeling the treatment assignment&lt;/h3>
&lt;p>IPW takes the opposite approach. Instead of modeling consumption, it models the probability of being assigned to treatment &amp;mdash; the &lt;strong>propensity score&lt;/strong>, defined as $p(X) = \Pr(T = 1 \mid X)$. It then reweights observations so that the treatment and control groups become comparable. The Stata documentation explains: &lt;em>&amp;ldquo;IPW estimators use weighted averages of the observed outcome variable to estimate means of the potential outcomes. The weights account for the missing data inherent in the potential-outcome framework.&amp;quot;&lt;/em>&lt;/p>
&lt;p>The logic is elegant: in a perfectly randomized experiment, every household has the same 50% chance of treatment, and a simple comparison of means is unbiased. When chance imbalances arise (like our 9.3% gender SMD), the estimated propensity scores deviate slightly from 0.50. IPW corrects for these imbalances by making the reweighted sample look as if randomization had been perfect &amp;mdash; without ever modeling the outcome.&lt;/p>
&lt;p>&lt;strong>Analogy &amp;mdash; opinion polling.&lt;/strong> Election pollsters know their survey overrepresents some demographics. If 60% of respondents are college graduates but only 35% of voters are, pollsters give lower weight to each college graduate&amp;rsquo;s response and higher weight to non-graduates. IPW does the same thing for treatment groups &amp;mdash; it reweights households so the treated and control groups have the same covariate distribution.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
DATA[&amp;quot;&amp;lt;b&amp;gt;Observed Data&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Treatment and control groups&amp;lt;br/&amp;gt;may have imbalances&amp;quot;]
PS[&amp;quot;&amp;lt;b&amp;gt;Estimate propensity score&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;p(X) = Pr(T=1 | X)&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;via logistic regression&amp;lt;/i&amp;gt;&amp;quot;]
WT[&amp;quot;&amp;lt;b&amp;gt;Compute weights&amp;lt;/b&amp;gt;&amp;quot;]
WTR[&amp;quot;Treated: weight = 1/p(X)&amp;quot;]
WCT[&amp;quot;Control: weight = 1/(1−p(X))&amp;quot;]
ATE[&amp;quot;&amp;lt;b&amp;gt;ATE&amp;lt;/b&amp;gt; = Weighted mean(treated)&amp;lt;br/&amp;gt;− Weighted mean(control)&amp;quot;]
DATA --&amp;gt; PS
PS --&amp;gt; WT
WT --&amp;gt; WTR
WT --&amp;gt; WCT
WTR --&amp;gt; ATE
WCT --&amp;gt; ATE
style DATA fill:#141413,stroke:#d97757,color:#fff
style PS fill:#d97757,stroke:#141413,color:#fff
style WT fill:#d97757,stroke:#141413,color:#fff
style WTR fill:#d97757,stroke:#141413,color:#fff
style WCT fill:#d97757,stroke:#141413,color:#fff
style ATE fill:#d97757,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>The propensity score.&lt;/strong> The propensity score is estimated via logistic regression:&lt;/p>
&lt;p>$$\hat{p}(X_i) = \Pr(T_i = 1 \mid X_i) = \text{logit}^{-1}(\hat{\alpha} + \hat{\beta}' X_i)$$&lt;/p>
&lt;p>In plain language: we fit a logistic model predicting whether each household was assigned to treatment, based on their covariates (age, education, gender, poverty status). The predicted probability is their propensity score.&lt;/p>
&lt;p>&lt;strong>The IPW estimator.&lt;/strong> The ATE under IPW is:&lt;/p>
&lt;p>$$\hat{\tau}_{IPW}^{ATE} = \frac{1}{N} \sum_{i=1}^{N} \left[ \frac{T_i \cdot Y_i}{\hat{p}(X_i)} - \frac{(1 - T_i) \cdot Y_i}{1 - \hat{p}(X_i)} \right]$$&lt;/p>
&lt;p>Each treated household&amp;rsquo;s outcome is divided by its probability of being treated &amp;mdash; this upweights treated households that &amp;ldquo;look like&amp;rdquo; control households (the Stata manual calls this placing &amp;ldquo;a larger weight on those observations for which $y_{1i}$ is observed even though its observation was not likely&amp;rdquo;). Each control household&amp;rsquo;s outcome is divided by its probability of being in the control group. The reweighting creates a pseudo-population where treatment assignment is independent of covariates.&lt;/p>
&lt;p>For the ATT, only the control group needs reweighting (because the treated group is already the reference population):&lt;/p>
&lt;p>$$\hat{\tau}_{IPW}^{ATT} = \frac{1}{N_1} \sum_{i=1}^{N} \left[ T_i \cdot Y_i - \frac{(1 - T_i) \cdot \hat{p}(X_i) \cdot Y_i}{1 - \hat{p}(X_i)} \right]$$&lt;/p>
&lt;p>&lt;strong>Mini example from our data.&lt;/strong> In our RCT, a female household in poverty might have $\hat{p}(X) = 0.52$ (slightly more likely to be treated due to the gender imbalance). If treated, her weight is $1/0.52 = 1.92$. If in the control group, her weight is $1/(1 - 0.52) = 2.08$. A male non-poor household might have $\hat{p}(X) = 0.49$, giving weights close to 2.0 in either group. These mild adjustments rebalance the groups to remove the chance gender imbalance.&lt;/p>
&lt;p>&lt;strong>Why IPW matters even in RCTs.&lt;/strong> In a perfect RCT, the true propensity score is exactly 0.50 for everyone, and IPW does nothing. But finite samples produce chance imbalances. IPW uses the estimated propensity scores (which deviate slightly from 0.50) to correct for these imbalances without making any assumptions about how covariates affect the outcome.&lt;/p>
&lt;p>&lt;strong>Stata implementation.&lt;/strong> The &lt;code>teffects ipw&lt;/code> command fits a logistic treatment model by default. Note that the first parenthesis specifies only the outcome variable (no covariates &amp;mdash; IPW does not model the outcome), and the second specifies the treatment model: &lt;code>teffects ipw (y) (treat c.age c.edu i.female i.poverty), ate&lt;/code>.&lt;/p>
&lt;p>&lt;strong>What can go wrong &amp;mdash; extreme weights.&lt;/strong> IPW&amp;rsquo;s vulnerability is extreme propensity scores. If $\hat{p}(X) = 0.01$ for some household, the weight becomes $1/0.01 = 100$ &amp;mdash; that single household dominates the ATE estimate, causing high variance and instability. The Stata manual warns: &lt;em>&amp;ldquo;When propensity scores are extreme (near 0 or 1), the inverse weights become very large, producing unstable estimates.&amp;quot;&lt;/em> This happens when the treatment and control groups have poor &lt;strong>overlap&lt;/strong> &amp;mdash; some covariate combinations appear only in one group. In our well-designed RCT, all propensity scores are between 0.43 and 0.55 (we verified this in Section 5.4), so extreme weights are not a concern.&lt;/p>
&lt;p>RA works well if the outcome model is correct but can be biased if it is wrong. IPW works well if the propensity score model is correct but can be unstable if it is wrong. Is there a method that protects us against both types of misspecification?&lt;/p>
&lt;h3 id="73-doubly-robust-dr-----modeling-both">7.3 Doubly Robust (DR) &amp;mdash; modeling both&lt;/h3>
&lt;p>Doubly robust methods combine RA and IPW into a single estimator. They fit an outcome model &lt;strong>and&lt;/strong> estimate a propensity score. The key property &amp;mdash; the reason they are called &amp;ldquo;doubly robust&amp;rdquo; &amp;mdash; is that the estimator is consistent (converges to the true treatment effect with enough data) if &lt;strong>either&lt;/strong> the outcome model &lt;strong>or&lt;/strong> the propensity score model is correctly specified. You do not need both to be right &amp;mdash; just one.&lt;/p>
&lt;p>The Stata manual describes this property: &lt;em>&amp;ldquo;AIPW estimators model both the outcome and the treatment probability. A surprising fact is that only one of the two models must be correctly specified to consistently estimate the treatment effects.&amp;quot;&lt;/em>&lt;/p>
&lt;p>&lt;strong>Analogy &amp;mdash; backup power.&lt;/strong> Think of a house with two independent power sources: the electrical grid (the outcome model) and a solar panel system (the propensity score model). If the grid goes down (outcome model is misspecified), solar power keeps the lights on. If clouds block the solar panels (propensity score model is wrong), the grid still works. As long as at least one power source is functioning, the house stays lit. That is doubly robust estimation &amp;mdash; as long as at least one model is correct, the estimator gives the right answer.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
DATA[&amp;quot;&amp;lt;b&amp;gt;Observed Data&amp;lt;/b&amp;gt;&amp;quot;]
RA_C[&amp;quot;&amp;lt;b&amp;gt;RA component&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Predict Ŷ₁ and Ŷ₀&amp;lt;br/&amp;gt;for each household&amp;quot;]
IPW_C[&amp;quot;&amp;lt;b&amp;gt;IPW component&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Estimate propensity&amp;lt;br/&amp;gt;score p(X)&amp;quot;]
RESID[&amp;quot;&amp;lt;b&amp;gt;Prediction errors&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Y − Ŷ for each&amp;lt;br/&amp;gt;household&amp;quot;]
CORRECT[&amp;quot;&amp;lt;b&amp;gt;Bias-correction term&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;IPW-weighted residuals&amp;quot;]
DR[&amp;quot;&amp;lt;b&amp;gt;DR estimate&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;= RA prediction&amp;lt;br/&amp;gt;+ Bias correction&amp;quot;]
DATA --&amp;gt; RA_C
DATA --&amp;gt; IPW_C
RA_C --&amp;gt; RESID
IPW_C --&amp;gt; CORRECT
RESID --&amp;gt; CORRECT
RA_C --&amp;gt; DR
CORRECT --&amp;gt; DR
style DATA fill:#141413,stroke:#00d4c8,color:#fff
style RA_C fill:#6a9bcc,stroke:#141413,color:#fff
style IPW_C fill:#d97757,stroke:#141413,color:#fff
style RESID fill:#6a9bcc,stroke:#141413,color:#fff
style CORRECT fill:#d97757,stroke:#141413,color:#fff
style DR fill:#00d4c8,stroke:#141413,color:#141413
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>The AIPW estimator.&lt;/strong> The most common doubly robust form is Augmented Inverse Probability Weighting (AIPW):&lt;/p>
&lt;p>$$\hat{\tau}_{DR}^{ATE} = \frac{1}{N} \sum_{i=1}^{N} \left[ \hat{\mu}_1(X_i) - \hat{\mu}_0(X_i) + \frac{T_i (Y_i - \hat{\mu}_1(X_i))}{\hat{p}(X_i)} - \frac{(1 - T_i)(Y_i - \hat{\mu}_0(X_i))}{1 - \hat{p}(X_i)} \right]$$&lt;/p>
&lt;p>This equation has two clearly interpretable components:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>RA component&lt;/strong> (first two terms): $\hat{\mu}_1(X_i) - \hat{\mu}_0(X_i)$ &amp;mdash; the regression adjustment prediction, exactly as in Section 7.1&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Bias-correction component&lt;/strong> (last two terms): IPW-weighted residuals $(Y_i - \hat{\mu})$ &amp;mdash; the difference between actual and predicted outcomes, weighted by inverse propensity scores&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>In plain language: start with the RA prediction of each household&amp;rsquo;s treatment effect. Then ask: how far off was that prediction from reality? Weight those prediction errors by the propensity score. If RA was already right, the errors average to zero and you just get RA. If RA was wrong but IPW is right, the weighted errors exactly cancel the RA bias.&lt;/p>
&lt;p>&lt;strong>Why the magic works &amp;mdash; four scenarios.&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Outcome model correct, propensity model wrong:&lt;/strong> The residuals $(Y_i - \hat{\mu})$ are zero on average, so the correction terms vanish. DR reduces to RA. Correct answer.&lt;/li>
&lt;li>&lt;strong>Propensity model correct, outcome model wrong:&lt;/strong> The IPW reweighting is valid, so the correction terms fix the RA bias. Correct answer.&lt;/li>
&lt;li>&lt;strong>Both models correct:&lt;/strong> Both components work together, producing the most efficient estimate.&lt;/li>
&lt;li>&lt;strong>Both models wrong:&lt;/strong> Neither safety net catches the error. The estimate can be biased. DR provides insurance, not invincibility.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>AIPW vs. IPWRA in Stata.&lt;/strong> Stata offers two doubly robust commands. &lt;code>teffects aipw&lt;/code> augments the IPW estimator with an outcome-model correction (the equation above). &lt;code>teffects ipwra&lt;/code> applies propensity score weights to the regression adjustment &amp;mdash; arriving at the same property from the other direction. Both are doubly robust and produce nearly identical results in practice.&lt;/p>
&lt;p>&lt;strong>Stata implementation.&lt;/strong> Both commands require specifying the outcome model in the first parenthesis and the treatment model in the second: &lt;code>teffects ipwra (y c.age c.edu i.female i.poverty) (treat c.age c.edu i.female i.poverty), vce(robust)&lt;/code>.&lt;/p>
&lt;p>&lt;strong>What can go wrong.&lt;/strong> DR fails only when &lt;strong>both&lt;/strong> models are wrong. This is much less likely than either single model being wrong &amp;mdash; getting at least one model approximately right is much easier than getting both perfectly right. However, the Stata manual notes: &lt;em>&amp;ldquo;When both the outcome and the treatment model are misspecified, which estimator is more robust is a matter of debate.&amp;quot;&lt;/em> Using flexible specifications (polynomials, interactions) reduces the risk of both models failing simultaneously.&lt;/p>
&lt;h3 id="comparison-of-the-three-approaches">Comparison of the three approaches&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Feature&lt;/th>
&lt;th>RA&lt;/th>
&lt;th>IPW&lt;/th>
&lt;th>DR (AIPW/IPWRA)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Models the outcome?&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Models the treatment?&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Key equation&lt;/td>
&lt;td>$\hat{\mu}_1(X) - \hat{\mu}_0(X)$&lt;/td>
&lt;td>$T \cdot Y / \hat{p}(X)$&lt;/td>
&lt;td>RA + IPW-weighted residuals&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Consistent if outcome model correct?&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>&amp;mdash;&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Consistent if treatment model correct?&lt;/td>
&lt;td>&amp;mdash;&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Main vulnerability&lt;/td>
&lt;td>Outcome misspecification&lt;/td>
&lt;td>Extreme weights&lt;/td>
&lt;td>Both models wrong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Stata command&lt;/td>
&lt;td>&lt;code>teffects ra&lt;/code>&lt;/td>
&lt;td>&lt;code>teffects ipw&lt;/code>&lt;/td>
&lt;td>&lt;code>teffects ipwra&lt;/code> / &lt;code>teffects aipw&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;pre>&lt;code class="language-mermaid">graph LR
RA[&amp;quot;&amp;lt;b&amp;gt;Regression Adjustment&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Models the outcome&amp;quot;]
IPW[&amp;quot;&amp;lt;b&amp;gt;Inverse Probability&amp;lt;br/&amp;gt;Weighting&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Models the treatment&amp;quot;]
DR[&amp;quot;&amp;lt;b&amp;gt;Doubly Robust&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Models both&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Consistent if either&amp;lt;br/&amp;gt;model is correct&amp;lt;/i&amp;gt;&amp;quot;]
RA --&amp;gt; DR
IPW --&amp;gt; DR
style RA fill:#6a9bcc,stroke:#141413,color:#fff
style IPW fill:#d97757,stroke:#141413,color:#fff
style DR fill:#00d4c8,stroke:#141413,color:#141413
&lt;/code>&lt;/pre>
&lt;p>The doubly robust estimator combines the strengths of both RA and IPW. It is the &lt;strong>standard recommendation in modern causal inference&lt;/strong> because it provides an extra layer of protection against model misspecification. Now that we understand what each method does, what it assumes, and what can go wrong, let us apply all three to our cash transfer data and compare their results.&lt;/p>
&lt;hr>
&lt;h2 id="8-cross-sectional-estimation-at-endline-----ra-ipw-and-dr">8. Cross-sectional estimation at endline &amp;mdash; RA, IPW, and DR&lt;/h2>
&lt;p>We now estimate treatment effects using only endline data. For each method, we compute both the &lt;strong>ATE&lt;/strong> (the policymaker&amp;rsquo;s quantity) and the &lt;strong>ATT&lt;/strong> (the evaluator&amp;rsquo;s quantity).&lt;/p>
&lt;h3 id="81-simple-difference-in-means">8.1 Simple difference in means&lt;/h3>
&lt;p>The simplest approach is to compare mean outcomes between treated and control groups at endline.&lt;/p>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/quarcs-lab/data-open/raw/master/ametrics/dataSIM4RCT.dta&amp;quot;, clear
keep if post==1
reg y treat, robust
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Linear regression Number of obs = 2,000
F(1, 1998) = 35.43
Prob &amp;gt; F = 0.0000
R-squared = 0.0174
Root MSE = .43449
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. t P&amp;gt;|t| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
treat | .1157465 .0194443 5.95 0.000 .0776132 .1538798
_cons | 10.05374 .014001 718.07 0.000 10.02628 10.0812
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The simple difference in means yields an estimate of 0.116 (SE = 0.019, p &amp;lt; 0.001, 95% CI [0.078, 0.154]). Because the outcome is in logs, this means being offered the cash transfer increased household consumption by approximately 11.6%. This estimate is close to the true effect of 12% and is our benchmark for comparison. However, it does not adjust for the gender imbalance we discovered at baseline.&lt;/p>
&lt;h3 id="82-regression-adjustment-----ate-and-att">8.2 Regression Adjustment &amp;mdash; ATE and ATT&lt;/h3>
&lt;p>Regression adjustment models the outcome as a function of treatment and covariates, then computes predicted outcomes under treatment and control for each observation.&lt;/p>
&lt;pre>&lt;code class="language-stata">* RA: Average Treatment Effect (ATE)
teffects ra (y c.age c.edu i.female i.poverty) (treat), ate
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : regression adjustment
Outcome model : linear
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATE |
treat |
(1 vs 0) | .1125431 .0190927 5.89 0.000 .0751221 .1499641
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.05503 .0138703 724.93 0.000 10.02785 10.08222
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* RA: Average Treatment Effect on the Treated (ATT)
teffects ra (y c.age c.edu i.female i.poverty) (treat), atet
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : regression adjustment
Outcome model : linear
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATET |
treat |
(1 vs 0) | .1132537 .0191498 5.91 0.000 .0757208 .1507865
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.05623 .0140082 717.88 0.000 10.02878 10.08369
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The RA estimates are ATE = 0.113 (SE = 0.019, 95% CI [0.075, 0.150]) and ATT = 0.113 (SE = 0.019, 95% CI [0.076, 0.151]). The ATE and ATT are nearly identical, which confirms that treatment effects are approximately &lt;strong>homogeneous&lt;/strong> across households. The RA approach models the outcome with covariates (age, education, gender, poverty), which adjusts for the baseline gender imbalance and can improve precision.&lt;/p>
&lt;h3 id="83-inverse-probability-weighting-----ate-and-att">8.3 Inverse Probability Weighting &amp;mdash; ATE and ATT&lt;/h3>
&lt;p>IPW reweights observations based on their estimated probability of treatment, without modeling the outcome.&lt;/p>
&lt;pre>&lt;code class="language-stata">* IPW: Average Treatment Effect (ATE)
teffects ipw (y) (treat c.age c.edu i.female i.poverty), ate
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : inverse-probability weights
Outcome model : weighted mean
Treatment model: logit
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATE |
treat |
(1 vs 0) | .1126713 .0190886 5.90 0.000 .0752583 .1500844
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.05495 .0138651 725.20 0.000 10.02778 10.08213
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* IPW: Average Treatment Effect on the Treated (ATT)
teffects ipw (y) (treat c.age c.edu i.female i.poverty), atet
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : inverse-probability weights
Outcome model : weighted mean
Treatment model: logit
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATET |
treat |
(1 vs 0) | .1134031 .0191397 5.93 0.000 .0758899 .1509162
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.05608 .0140004 718.27 0.000 10.02864 10.08352
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The IPW estimates are ATE = 0.113 (SE = 0.019, 95% CI [0.075, 0.150]) and ATT = 0.113 (SE = 0.019, 95% CI [0.076, 0.151]). These are very close to the RA results, which is expected in a well-designed RCT where propensity scores are near 0.50 for all households. Notice that IPW does &lt;strong>not&lt;/strong> model the outcome &amp;mdash; it only models the treatment assignment process using the propensity score. The close agreement between RA and IPW gives us confidence that both the outcome model and the treatment model are approximately correct.&lt;/p>
&lt;h3 id="84-doubly-robust-----ate-and-att-ipwra">8.4 Doubly Robust &amp;mdash; ATE and ATT (IPWRA)&lt;/h3>
&lt;p>The doubly robust IPWRA estimator combines outcome modeling and propensity score weighting.&lt;/p>
&lt;pre>&lt;code class="language-stata">* IPWRA: Average Treatment Effect (ATE)
teffects ipwra (y c.age c.edu i.female i.poverty) ///
(treat c.age c.edu i.female i.poverty), vce(robust)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : IPW regression adjustment
Outcome model : linear
Treatment model: logit
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATE |
treat |
(1 vs 0) | .112639 .0190901 5.90 0.000 .0752231 .1500549
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.055 .0138677 725.07 0.000 10.02782 10.08218
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* IPWRA: Average Treatment Effect on the Treated (ATT)
teffects ipwra (y c.age c.edu i.female i.poverty) ///
(treat c.age c.edu i.female i.poverty), atet vce(robust)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : IPW regression adjustment
Outcome model : linear
Treatment model: logit
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATET |
treat |
(1 vs 0) | .1133162 .0191469 5.92 0.000 .0757889 .1508435
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.05617 .0140019 718.20 0.000 10.02873 10.08361
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The doubly robust IPWRA estimates are ATE = 0.113 (SE = 0.019, 95% CI [0.075, 0.150]) and ATT = 0.113 (SE = 0.019, 95% CI [0.076, 0.151]). These are very close to the RA and IPW estimates, confirming that all three approaches converge in this well-designed RCT. The DR method provides the most reliable cross-sectional estimate because it is protected against misspecification of either the outcome or treatment model.&lt;/p>
&lt;h3 id="85-doubly-robust-----aipw-alternative">8.5 Doubly Robust &amp;mdash; AIPW alternative&lt;/h3>
&lt;p>As a robustness check, we can also compute the doubly robust estimate using the AIPW formulation instead of IPWRA.&lt;/p>
&lt;pre>&lt;code class="language-stata">* AIPW: Average Treatment Effect (ATE)
teffects aipw (y c.age c.edu i.female i.poverty) ///
(treat c.age c.edu i.female i.poverty)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : augmented IPW
Outcome model : linear by ML
Treatment model: logit
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATE |
treat |
(1 vs 0) | .1126412 .0190903 5.90 0.000 .075225 .1500574
─────────────+────────────────────────────────────────────────────────────────
POmean |
treat |
0 | 10.055 .013868 725.05 0.000 10.02782 10.08218
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The AIPW estimate of ATE = 0.113 (SE = 0.019, 95% CI [0.075, 0.150]) is virtually identical to the IPWRA result (0.113). Both are doubly robust &amp;mdash; the difference lies in the computational approach (AIPW augments the IPW estimator with a bias-correction term, while IPWRA applies IPW weights to the regression adjustment), but the theoretical properties and estimates are the same.&lt;/p>
&lt;h3 id="86-cross-sectional-comparison">8.6 Cross-sectional comparison&lt;/h3>
&lt;p>The table below summarizes all cross-sectional estimates.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Approach&lt;/th>
&lt;th>Estimand&lt;/th>
&lt;th style="text-align:center">Estimate&lt;/th>
&lt;th style="text-align:center">SE&lt;/th>
&lt;th style="text-align:center">95% CI&lt;/th>
&lt;th style="text-align:center">Contains 0.12?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Simple regression&lt;/td>
&lt;td>None&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td style="text-align:center">0.116&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.078, 0.154]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Regression Adjustment&lt;/td>
&lt;td>Outcome model&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Regression Adjustment&lt;/td>
&lt;td>Outcome model&lt;/td>
&lt;td>ATT&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.076, 0.151]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Inverse Prob. Weighting&lt;/td>
&lt;td>Treatment model&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Inverse Prob. Weighting&lt;/td>
&lt;td>Treatment model&lt;/td>
&lt;td>ATT&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.076, 0.151]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IPWRA (Doubly Robust)&lt;/td>
&lt;td>Both models&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IPWRA (Doubly Robust)&lt;/td>
&lt;td>Both models&lt;/td>
&lt;td>ATT&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.076, 0.151]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>True effect&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td style="text-align:center">&lt;strong>0.12&lt;/strong>&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Several patterns emerge from this comparison. First, &lt;strong>ATE and ATT are nearly identical&lt;/strong> for every method, confirming that treatment effects are homogeneous across households. Second, &lt;strong>RA, IPW, and DR all give remarkably similar results&lt;/strong> (all approximately 0.113) because, in this well-designed RCT, randomization ensures that both the outcome model and the propensity score model are approximately correct. Third, the simple difference in means (0.116) is slightly higher than the covariate-adjusted estimates (0.113), reflecting the precision improvement from controlling for covariates including the gender imbalance. Finally, all confidence intervals contain the true effect of 0.12 &amp;mdash; every method successfully recovers the correct answer.&lt;/p>
&lt;p>The real value of doubly robust methods becomes apparent in less ideal settings. When one model might be misspecified &amp;mdash; a common situation in practice &amp;mdash; DR methods provide insurance that RA or IPW alone cannot offer.&lt;/p>
&lt;hr>
&lt;h2 id="9-leveraging-panel-data-----difference-in-differences">9. Leveraging panel data &amp;mdash; Difference-in-Differences&lt;/h2>
&lt;p>All estimates in Section 8 used only endline data. But we have panel data &amp;mdash; the same 2,000 households observed before and after the intervention. Can we do better?&lt;/p>
&lt;h3 id="91-why-use-panel-data">9.1 Why use panel data?&lt;/h3>
&lt;p>Cross-sectional methods (RA, IPW, DR) compare treated and control groups at a single point in time &amp;mdash; the endline. They control for &lt;strong>observable&lt;/strong> covariates like age, education, and gender. But there may be &lt;strong>unobservable&lt;/strong> characteristics &amp;mdash; household motivation, geographic advantages, cultural factors &amp;mdash; that differ between groups and affect consumption. No amount of cross-sectional covariate adjustment can control for these, because we simply do not observe them.&lt;/p>
&lt;p>&lt;strong>Analogy &amp;mdash; comparing students across schools.&lt;/strong> Imagine comparing test scores between students at a charter school (treatment) and a traditional school (control). You can adjust for observable differences like family income and prior grades. But what about unmeasured factors &amp;mdash; parental involvement, neighborhood quality, student ambition? A cross-sectional comparison cannot disentangle the school effect from these hidden differences. Now suppose you observe the &lt;em>same students&lt;/em> before and after they switch schools. By comparing each student&amp;rsquo;s score change, you automatically cancel out all fixed student characteristics &amp;mdash; because they are the same at both time points. That is the power of panel data.&lt;/p>
&lt;p>Panel data methods like difference-in-differences (DiD) solve this problem by comparing each household &lt;strong>to itself&lt;/strong> over time. By looking at how each household&amp;rsquo;s consumption changed from baseline to endline, we effectively control for all &lt;strong>time-invariant unobservable characteristics&lt;/strong> (household fixed effects). This is a powerful advantage that cross-sectional methods cannot replicate.&lt;/p>
&lt;h4 id="the-did-estimator">The DiD estimator&lt;/h4>
&lt;p>The DiD estimator computes a simple but powerful quantity &amp;mdash; a &amp;ldquo;difference of differences&amp;rdquo;:&lt;/p>
&lt;p>$$\hat{\tau}_{DiD} = \underbrace{(\bar{Y}_{treat,post} - \bar{Y}_{treat,pre})}_{\text{Change for treated}} - \underbrace{(\bar{Y}_{control,post} - \bar{Y}_{control,pre})}_{\text{Change for control}}$$&lt;/p>
&lt;p>The first difference ($\bar{Y}_{treat,post} - \bar{Y}_{treat,pre}$) captures the treatment group&amp;rsquo;s change over time &amp;mdash; the treatment effect &lt;strong>plus&lt;/strong> any common time trend (e.g., economic growth that affects all households). The second difference ($\bar{Y}_{control,post} - \bar{Y}_{control,pre}$) captures the control group&amp;rsquo;s change &amp;mdash; the common time trend &lt;strong>only&lt;/strong>, since they did not receive treatment. Subtracting the second from the first removes the time trend, isolating the treatment effect.&lt;/p>
&lt;p>&lt;strong>Mini example from our data.&lt;/strong> Suppose the treated group&amp;rsquo;s average log consumption went from 10.01 at baseline to 10.17 at endline (change = +0.16). The control group went from 10.03 to 10.06 (change = +0.03). The DiD estimate is $0.16 - 0.03 = 0.13$ &amp;mdash; close to the true effect of 0.12. The control group&amp;rsquo;s +0.03 change captures the natural time trend that would have affected everyone, and subtracting it isolates the treatment effect.&lt;/p>
&lt;h4 id="the-parallel-trends-assumption">The parallel trends assumption&lt;/h4>
&lt;p>The key identifying assumption of DiD is the &lt;strong>parallel trends assumption (PTA)&lt;/strong>: absent the treatment, the treatment and control groups would have followed the same time trend. Formally:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Notation note&lt;/strong> &amp;mdash; In the DiD literature and in the Sant&amp;rsquo;Anna and Zhao (2020) paper, $D$ denotes treatment group assignment (equivalent to our &lt;code>treat&lt;/code> variable). This differs from our data dictionary where &lt;code>D&lt;/code> is the receipt indicator. In this section and Section 9.4, we follow the paper&amp;rsquo;s convention: $D = 1$ means assigned to treatment, $D = 0$ means assigned to control.&lt;/p>
&lt;/blockquote>
&lt;p>$$E[Y_1(0) - Y_0(0) \mid D = 1] = E[Y_1(0) - Y_0(0) \mid D = 0]$$&lt;/p>
&lt;p>This says that the average change in &lt;em>untreated&lt;/em> potential outcomes is the same for the treated and control groups. Note that this does &lt;strong>not&lt;/strong> require the two groups to have the same &lt;em>level&lt;/em> of consumption &amp;mdash; only the same &lt;em>trend&lt;/em>. The treated group can start higher or lower, as long as their consumption would have evolved at the same rate as the control group in the absence of the program.&lt;/p>
&lt;p>In an RCT, the parallel trends assumption is very plausible because randomization ensures the groups were similar at baseline. Any pre-existing differences between groups occurred by chance and are unlikely to produce different time trends. This makes DiD a strong estimator in our setting.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph LR
subgraph &amp;quot;Parallel Trends Assumption&amp;quot;
PRE[&amp;quot;&amp;lt;b&amp;gt;Baseline 2021&amp;lt;/b&amp;gt;&amp;quot;]
POST[&amp;quot;&amp;lt;b&amp;gt;Endline 2024&amp;lt;/b&amp;gt;&amp;quot;]
end
PRE --&amp;gt;|&amp;quot;Treated group&amp;lt;br/&amp;gt;change = effect + trend&amp;quot;| POST
PRE --&amp;gt;|&amp;quot;Control group&amp;lt;br/&amp;gt;change = trend only&amp;quot;| POST
style PRE fill:#6a9bcc,stroke:#141413,color:#fff
style POST fill:#d97757,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;h3 id="92-why-does-did-estimate-att-and-not-ate">9.2 Why does DiD estimate ATT and not ATE?&lt;/h3>
&lt;p>This is a point that many beginners miss, so it is worth explaining carefully.&lt;/p>
&lt;p>Recall from Section 6 that the ATT is $E[Y_1(1) - Y_1(0) \mid D = 1]$ &amp;mdash; the effect on those who were treated. Sant&amp;rsquo;Anna and Zhao (2020) make this explicit: the main challenge is computing $E[Y_1(0) \mid D = 1]$ &amp;mdash; what would the treated group&amp;rsquo;s consumption have been at endline &lt;em>without&lt;/em> the program?&lt;/p>
&lt;p>DiD solves this by using the control group&amp;rsquo;s time trend as a stand-in. Specifically, it constructs the counterfactual for the treated group as:&lt;/p>
&lt;p>$$\underbrace{E[Y_1(0) \mid D = 1]}_{\text{Counterfactual}} = \underbrace{E[Y_0 \mid D = 1]}_{\text{Treated at baseline}} + \underbrace{(E[Y_1 \mid D = 0] - E[Y_0 \mid D = 0])}_{\text{Control group&amp;rsquo;s time trend}}$$&lt;/p>
&lt;p>This counterfactual is &lt;strong>specific to the treated group&lt;/strong> &amp;mdash; it starts from their baseline level and adds the control group&amp;rsquo;s trend. DiD therefore estimates what happened to the treated group relative to this counterfactual. This is precisely the ATT.&lt;/p>
&lt;p>&lt;strong>Why not the ATE?&lt;/strong> To estimate the ATE, we would also need the treatment effect for the untreated &amp;mdash; what would happen if we gave the program to those who did not receive it. DiD does not provide this, because the counterfactual it constructs runs in only one direction (control trend applied to treated baseline, not treated trend applied to control baseline).&lt;/p>
&lt;p>&lt;strong>In our RCT context&lt;/strong>, since treatment was randomly assigned, ATE and ATT are likely very similar (as we saw in Section 8). But in observational studies with heterogeneous treatment effects, this distinction matters greatly. A job-training program might have a larger effect on those who voluntarily enrolled (ATT) than it would have on randomly selected workers (ATE).&lt;/p>
&lt;h3 id="93-basic-did-with-panel-fixed-effects">9.3 Basic DiD with panel fixed effects&lt;/h3>
&lt;p>We now implement the basic DiD estimator using Stata&amp;rsquo;s &lt;code>xtdidregress&lt;/code> command, which handles the panel structure and computes clustered standard errors.&lt;/p>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/quarcs-lab/data-open/raw/master/ametrics/dataSIM4RCT.dta&amp;quot;, clear
* Create the treatment-post interaction
gen treat_post = treat * post
label var treat_post &amp;quot;Treated x Post (1 only for treated in 2024)&amp;quot;
* Declare panel structure
xtset id year
* Basic DiD with individual fixed effects
xtdidregress (y) (treat_post), group(id) time(year) vce(cluster id)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Number of obs = 4,000
Number of groups = 2,000
Outcome model : linear
Treatment model: none
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. t P&amp;gt;|t| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATET |
treat_post | .1347161 .0272737 4.94 0.000 .0812282 .188204
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The basic DiD estimate of the ATT is 0.135 (SE = 0.027, p &amp;lt; 0.001, 95% CI [0.081, 0.188]). This is slightly higher than the cross-sectional estimates (0.113&amp;ndash;0.116) but still contains the true effect of 0.12 within its confidence interval. The wider standard error (0.027 vs. 0.019) reflects the additional variability introduced by differencing within households. Standard errors are clustered at the household level to account for serial correlation within panels.&lt;/p>
&lt;p>The key advantage of this DiD estimate is that it controls for all &lt;strong>time-invariant unobservable characteristics&lt;/strong> of each household. In an RCT, randomization already handles confounding, so the cross-sectional and panel estimates are similar. But in observational settings, DiD&amp;rsquo;s ability to absorb household fixed effects can correct biases that cross-sectional methods cannot.&lt;/p>
&lt;h3 id="94-from-cross-sectional-dr-to-panel-dr-----doubly-robust-did-drdid">9.4 From cross-sectional DR to panel DR &amp;mdash; Doubly Robust DiD (DRDID)&lt;/h3>
&lt;p>In Section 7, we saw that doubly robust methods combine outcome modeling and propensity score modeling for cross-sectional data. &lt;strong>DRDID extends this logic to the panel setting.&lt;/strong> It combines the DiD framework (using pre/post variation) with doubly robust covariate adjustment.&lt;/p>
&lt;p>This approach was introduced by Sant&amp;rsquo;Anna and Zhao (2020) in a landmark paper published in the &lt;em>Journal of Econometrics&lt;/em>. They proposed estimators that are &amp;ldquo;consistent if either (but not necessarily both) a propensity score or outcome regression working models are correctly specified&amp;rdquo; &amp;mdash; bringing the doubly robust property from the cross-sectional world into the DiD framework.&lt;/p>
&lt;h4 id="why-do-we-need-drdid">Why do we need DRDID?&lt;/h4>
&lt;p>Recall from Section 9.2 that basic DiD relies on the &lt;strong>parallel trends assumption&lt;/strong> &amp;mdash; absent treatment, the treated and control groups would have followed the same time trend. But what if parallel trends holds only &lt;strong>conditional on covariates&lt;/strong>? For example, what if consumption trends differ between poor and non-poor households, but within each poverty group the trends are parallel?&lt;/p>
&lt;p>In this case, we need a &lt;strong>conditional&lt;/strong> parallel trends assumption:&lt;/p>
&lt;p>$$E[Y_1(0) - Y_0(0) \mid D = 1, X] = E[Y_1(0) - Y_0(0) \mid D = 0, X]$$&lt;/p>
&lt;p>This says that the average change in untreated potential outcomes is the same for treated and control groups &lt;em>who share the same covariates&lt;/em> $X$. Note that this allows for covariate-specific time trends (e.g., different consumption growth rates for poor and non-poor households) while still identifying the ATT.&lt;/p>
&lt;p>Under this conditional parallel trends assumption, there are two ways to estimate the ATT:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Outcome regression (OR) approach&lt;/strong> &amp;mdash; model how the outcome evolves over time for the control group, and use that model to predict the counterfactual evolution for the treated group&lt;/li>
&lt;li>&lt;strong>IPW approach&lt;/strong> &amp;mdash; reweight the control group so its covariate distribution matches the treated group, then compute the standard DiD&lt;/li>
&lt;/ul>
&lt;p>The problem is the same as in the cross-sectional case: OR requires a correctly specified outcome model, and IPW requires a correctly specified propensity score model. Sant&amp;rsquo;Anna and Zhao&amp;rsquo;s insight was that &lt;strong>you can combine both into a single estimator that works if either model is correct&lt;/strong>.&lt;/p>
&lt;h4 id="the-drdid-estimator-for-panel-data">The DRDID estimator for panel data&lt;/h4>
&lt;p>When panel data are available (as in our case &amp;mdash; same households observed at baseline and endline), the DRDID estimator takes a particularly clean form. Let $\Delta Y_i = Y_{i,post} - Y_{i,pre}$ denote each household&amp;rsquo;s change in consumption. The DR DID estimator is:&lt;/p>
&lt;p>$$\hat{\tau}_{DR}^{DiD} = \frac{1}{N_1} \sum_{i=1}^{N} \left[ w_1(D_i) - w_0(D_i, X_i) \right] \left[ \Delta Y_i - \hat{\mu}_{0,\Delta}(X_i) \right]$$&lt;/p>
&lt;p>where:&lt;/p>
&lt;ul>
&lt;li>$w_1(D_i) = D_i / \bar{D}$ assigns equal weight to each treated unit (the fraction treated)&lt;/li>
&lt;li>$w_0(D_i, X_i)$ reweights control units using the propensity score $\hat{p}(X)$, so they resemble the treated group&lt;/li>
&lt;li>$\hat{\mu}_{0,\Delta}(X_i) = \hat{\mu}_{0,post}(X_i) - \hat{\mu}_{0,pre}(X_i)$ is the predicted change in consumption for the control group, fitted from control-group data&lt;/li>
&lt;/ul>
&lt;p>In plain language: for each household, compute the change in consumption over time ($\Delta Y$) and subtract the model-predicted change for the control group ($\hat{\mu}_{0,\Delta}$). This residual captures the treatment effect plus any prediction error. Then reweight these residuals using IPW so that the control group matches the treated group&amp;rsquo;s covariate profile.&lt;/p>
&lt;h4 id="why-is-this-doubly-robust">Why is this doubly robust?&lt;/h4>
&lt;p>The doubly robust property works through the same logic as in the cross-sectional case (Section 7.3), but applied to &lt;strong>changes&lt;/strong> rather than levels:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>If the outcome model is correct&lt;/strong> ($\hat{\mu}_{0,\Delta}(X) = E[\Delta Y \mid D=0, X]$), then the residuals $\Delta Y_i - \hat{\mu}_{0,\Delta}(X_i)$ average to zero for the control group, regardless of the propensity score weights. The estimator reduces to an outcome-regression DiD. Correct answer.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>If the propensity score model is correct&lt;/strong> ($\hat{p}(X) = \Pr(D=1 \mid X)$), the IPW reweighting makes the control group comparable to the treated group, regardless of the outcome model. The correction term fixes any bias from a misspecified outcome model. Correct answer.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>If both are correct&lt;/strong>, the estimator achieves the &lt;strong>semiparametric efficiency bound&lt;/strong> &amp;mdash; it is the most precise estimator possible given the assumptions. Sant&amp;rsquo;Anna and Zhao proved this formally.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>If both are wrong&lt;/strong>, the estimator can be biased &amp;mdash; double robustness provides one layer of insurance, not two.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-mermaid">graph TD
DY[&amp;quot;&amp;lt;b&amp;gt;Panel data&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;ΔY = Y_post − Y_pre&amp;lt;br/&amp;gt;for each household&amp;quot;]
OR[&amp;quot;&amp;lt;b&amp;gt;Outcome model&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Predict control group's&amp;lt;br/&amp;gt;consumption change&amp;lt;br/&amp;gt;μ̂₀,Δ(X)&amp;quot;]
PS[&amp;quot;&amp;lt;b&amp;gt;Propensity score&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Estimate p(X)&amp;lt;br/&amp;gt;= Pr(D=1 | X)&amp;quot;]
RES[&amp;quot;&amp;lt;b&amp;gt;Residuals&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;ΔY − μ̂₀,Δ(X)&amp;quot;]
IPW_W[&amp;quot;&amp;lt;b&amp;gt;IPW reweighting&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Make controls look&amp;lt;br/&amp;gt;like treated group&amp;quot;]
DRDID[&amp;quot;&amp;lt;b&amp;gt;DR-DiD estimate&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;ATT = weighted average&amp;lt;br/&amp;gt;of residuals&amp;quot;]
DY --&amp;gt; RES
OR --&amp;gt; RES
PS --&amp;gt; IPW_W
RES --&amp;gt; DRDID
IPW_W --&amp;gt; DRDID
style DY fill:#141413,stroke:#00d4c8,color:#fff
style OR fill:#6a9bcc,stroke:#141413,color:#fff
style PS fill:#d97757,stroke:#141413,color:#fff
style RES fill:#6a9bcc,stroke:#141413,color:#fff
style IPW_W fill:#d97757,stroke:#141413,color:#fff
style DRDID fill:#00d4c8,stroke:#141413,color:#141413
&lt;/code>&lt;/pre>
&lt;h4 id="what-drdid-adds-over-basic-did-and-twfe">What DRDID adds over basic DiD and TWFE&lt;/h4>
&lt;p>Sant&amp;rsquo;Anna and Zhao (2020) also showed that the standard two-way fixed effects (TWFE) estimator &amp;mdash; the workhorse of applied economics &amp;mdash; can produce misleading results when treatment effects are heterogeneous across covariates. Specifically, the TWFE estimator implicitly assumes (i) that treatment effects are the same for all covariate values, and (ii) that there are no covariate-specific time trends. When these assumptions fail, &amp;ldquo;the estimand is, in general, different from the ATT, and policy evaluation based on it may be misleading.&amp;rdquo; DRDID avoids both of these pitfalls by allowing for flexible outcome models and covariate-specific trends.&lt;/p>
&lt;h4 id="stata-implementation">Stata implementation&lt;/h4>
&lt;p>The &lt;code>drdid&lt;/code> package (Rios-Avila, Sant&amp;rsquo;Anna, and Callaway) implements the estimators from the paper.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Install the drdid package (only needed once)
ssc install drdid, replace
* Doubly Robust DiD with DRIPW estimator
drdid y c.age c.edu i.female i.poverty, ivar(id) time(year) treatment(treat) dripw
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Doubly robust difference-in-differences estimator
Outcome model : least squares
Treatment model: inverse probability
──────────────────────────────────────────────────────────────────────────────
| Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATET | .1374784 .027387 5.02 0.000 .0838008 .191156
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The DRDID estimate of the ATT is 0.137 (SE = 0.027, p &amp;lt; 0.001, 95% CI [0.084, 0.191]). The &lt;code>dripw&lt;/code> option specifies the Doubly Robust Inverse Probability Weighting estimator, which uses a linear least squares model for the outcome evolution of the control group and a logistic model for the propensity score. The result is slightly higher than basic DiD (0.135) and close to the true effect of 0.12.&lt;/p>
&lt;p>&lt;strong>Alternative: Stata 17+ built-in command.&lt;/strong> Stata 17 and later versions include a built-in doubly robust DiD estimator that does not require installing external packages.&lt;/p>
&lt;pre>&lt;code class="language-stata">xthdidregress aipw (y c.age c.edu i.female i.poverty) ///
(treat_post c.age c.edu i.female i.poverty), group(id)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Heterogeneous-treatment-effects regression Number of obs = 4,000
Number of panels = 2,000
Estimator: Augmented IPW
Panel variable: id
Treatment level: id
Control group: Never treated
(Std. err. adjusted for 2,000 clusters in id)
──────────────────────────────────────────────────────────────────────────────
| Robust
Cohort | ATET std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
year |
2024 | .1374784 .027387 5.02 0.000 .0838008 .191156
──────────────────────────────────────────────────────────────────────────────
Note: ATET computed using covariates.
&lt;/code>&lt;/pre>
&lt;p>The &lt;code>xthdidregress aipw&lt;/code> command produces the same ATT estimate of 0.137 (SE = 0.027, 95% CI [0.084, 0.191]) as the &lt;code>drdid&lt;/code> package &amp;mdash; confirming that both implement the same doubly robust DiD methodology. The output labels the result as &amp;ldquo;Cohort year 2024&amp;rdquo; because &lt;code>xthdidregress&lt;/code> is designed for settings with staggered treatment adoption across multiple cohorts; in our two-period design, there is only one treatment cohort (households treated in 2024). As the Stata manual explains, &amp;ldquo;AIPW models both treatment and outcome. If at least one of the models is correctly specified, it provides consistent estimates, a property called double robustness.&amp;rdquo;&lt;/p>
&lt;p>The agreement between &lt;code>drdid&lt;/code> (community package) and &lt;code>xthdidregress aipw&lt;/code> (built-in) provides a useful robustness check &amp;mdash; researchers can verify their results using both implementations.&lt;/p>
&lt;h4 id="panel-data-vs-repeated-cross-sections">Panel data vs. repeated cross-sections&lt;/h4>
&lt;p>An important result from Sant&amp;rsquo;Anna and Zhao (2020) is that panel data are &lt;strong>strictly more efficient&lt;/strong> than repeated cross-sections for estimating the ATT under the DiD framework. The intuition is straightforward: with panel data, we observe each household&amp;rsquo;s individual change over time ($\Delta Y_i$), which eliminates household-level variation. With repeated cross-sections, we can only compare group averages at different time points, which introduces additional noise. The efficiency gain is larger when the sample sizes in the pre and post periods are more imbalanced.&lt;/p>
&lt;p>In our study, we have a balanced panel (same 2,000 households at baseline and endline), so we benefit from this efficiency advantage.&lt;/p>
&lt;h3 id="95-cross-sectional-vs-panel-comparison">9.5 Cross-sectional vs. panel comparison&lt;/h3>
&lt;p>The table below compares our best cross-sectional estimates with the panel-based DiD estimates.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Approach&lt;/th>
&lt;th>Estimand&lt;/th>
&lt;th>Data Used&lt;/th>
&lt;th style="text-align:center">Estimate&lt;/th>
&lt;th style="text-align:center">SE&lt;/th>
&lt;th style="text-align:center">95% CI&lt;/th>
&lt;th style="text-align:center">Contains 0.12?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Simple regression&lt;/td>
&lt;td>None&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td>Endline only&lt;/td>
&lt;td style="text-align:center">0.116&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.078, 0.154]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RA&lt;/td>
&lt;td>Outcome model&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td>Endline only&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IPW&lt;/td>
&lt;td>Treatment model&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td>Endline only&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>DR (IPWRA)&lt;/td>
&lt;td>Both models&lt;/td>
&lt;td>ATE&lt;/td>
&lt;td>Endline only&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Basic DiD&lt;/td>
&lt;td>Panel FE&lt;/td>
&lt;td>&lt;strong>ATT&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Both waves&lt;/strong>&lt;/td>
&lt;td style="text-align:center">0.135&lt;/td>
&lt;td style="text-align:center">0.027&lt;/td>
&lt;td style="text-align:center">[0.081, 0.188]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>DR-DiD (&lt;code>drdid&lt;/code>)&lt;/td>
&lt;td>Both + Panel&lt;/td>
&lt;td>&lt;strong>ATT&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Both waves&lt;/strong>&lt;/td>
&lt;td style="text-align:center">0.137&lt;/td>
&lt;td style="text-align:center">0.027&lt;/td>
&lt;td style="text-align:center">[0.084, 0.191]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>DR-DiD (&lt;code>xthdidregress&lt;/code>)&lt;/td>
&lt;td>Both + Panel&lt;/td>
&lt;td>&lt;strong>ATT&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Both waves&lt;/strong>&lt;/td>
&lt;td style="text-align:center">0.137&lt;/td>
&lt;td style="text-align:center">0.027&lt;/td>
&lt;td style="text-align:center">[0.084, 0.191]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>True effect&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td style="text-align:center">&lt;strong>0.12&lt;/strong>&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Several important patterns emerge from this comparison. Cross-sectional methods estimate &lt;strong>ATE&lt;/strong> using only endline data, while DiD methods estimate &lt;strong>ATT&lt;/strong> using both survey waves. The two DR-DiD implementations (&lt;code>drdid&lt;/code> and &lt;code>xthdidregress aipw&lt;/code>) produce identical results, confirming methodological consistency. The DiD estimates (0.135&amp;ndash;0.137) are slightly higher than the cross-sectional estimates (0.113), but &lt;strong>all confidence intervals contain the true effect of 0.12&lt;/strong>. DiD&amp;rsquo;s wider standard errors (0.027 vs. 0.019) reflect the additional variability from differencing within households.&lt;/p>
&lt;p>The key value of DiD is &lt;strong>not&lt;/strong> tighter standard errors &amp;mdash; it is &lt;strong>robustness to time-invariant unobservables.&lt;/strong> In observational settings where randomization does not hold, DiD can correct biases that cross-sectional methods cannot address. In this RCT, randomization already handles confounding, so the estimates are similar. DRDID adds doubly robust protection on top of DiD, making it the most robust panel method available.&lt;/p>
&lt;hr>
&lt;h2 id="10-offer-vs-receipt-----endogenous-treatment-advanced">10. Offer vs. receipt &amp;mdash; endogenous treatment (advanced)&lt;/h2>
&lt;blockquote>
&lt;p>&lt;strong>Note:&lt;/strong> This section addresses the advanced topic of imperfect compliance and endogenous treatment. Readers new to causal inference may wish to skip this section on a first reading and return to it later.&lt;/p>
&lt;/blockquote>
&lt;h3 id="101-the-compliance-problem">10.1 The compliance problem&lt;/h3>
&lt;p>All estimates in Sections 8 and 9 measure the effect of &lt;strong>being offered&lt;/strong> the cash transfer (&lt;code>treat&lt;/code>), not the effect of &lt;strong>actually receiving&lt;/strong> it (&lt;code>D&lt;/code>). This is the intent-to-treat (ITT) approach &amp;mdash; it captures the policy-relevant effect of the offer, regardless of whether households complied.&lt;/p>
&lt;p>But what about the effect of actual receipt? This is more complex because compliance is &lt;strong>not random&lt;/strong>. Only 85% of treated households received the transfer, and 5% of control households received it through other channels. The households that chose to take up the program may differ systematically from those that did not &amp;mdash; they may be more motivated, more financially constrained, or better connected. Naively comparing receivers to non-receivers would introduce &lt;strong>selection bias&lt;/strong>.&lt;/p>
&lt;p>The solution is to use the random assignment (&lt;code>treat&lt;/code>) as an &lt;strong>instrumental variable&lt;/strong> for actual receipt (&lt;code>D&lt;/code>). Because &lt;code>treat&lt;/code> was randomly assigned, it is independent of household characteristics and satisfies the requirements for a valid instrument. This allows us to isolate the causal effect of receipt, at least for the subset of households whose receipt was determined by the offer (the &amp;ldquo;compliers&amp;rdquo;).&lt;/p>
&lt;p>&lt;strong>Analogy &amp;mdash; prescriptions and pills.&lt;/strong> Imagine a doctor randomly prescribes a medication to some patients, but not all patients fill their prescription. We cannot simply compare those who took the pill to those who did not, because pill-takers may be more health-conscious. Instead, we use the random prescription (the &amp;ldquo;offer&amp;rdquo;) as a nudge &amp;mdash; it strongly predicts whether you take the pill but does not directly affect your health except through the pill. That is the instrumental variable approach: using the random offer to estimate the causal effect of actual receipt.&lt;/p>
&lt;h3 id="102-endogenous-treatment-regression">10.2 Endogenous treatment regression&lt;/h3>
&lt;p>Stata&amp;rsquo;s &lt;code>etregress&lt;/code> command estimates the effect of an endogenous treatment variable, using the random assignment as an excluded instrument.&lt;/p>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/quarcs-lab/data-open/raw/master/ametrics/dataSIM4RCT.dta&amp;quot;, clear
keep if post==1
* Endogenous treatment regression
etregress y c.age i.female i.poverty c.edu, ///
treat(D = treat c.age i.female i.poverty c.edu) vce(robust)
* Mark estimation sample
gen byte esample = e(sample)
* ATE of receipt
margins r.D if esample==1
* ATT of receipt
margins, predict(cte) subpop(if D==1 &amp;amp; esample==1)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Linear regression with endogenous treatment Number of obs = 2,000
Estimator: Maximum likelihood Wald chi2(5) = 92.23
Log pseudolikelihood = -1797.6297 Prob &amp;gt; chi2 = 0.0000
──────────────────────────────────────────────────────────────────────────────
| Robust
| Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
y |
age | .003187 .0010016 3.18 0.001 .001224 .0051501
1.female | .0801465 .0189552 4.23 0.000 .042995 .117298
1.poverty | -.1030302 .0205984 -5.00 0.000 -.1434023 -.062658
edu | .0182634 .0045243 4.04 0.000 .0093959 .0271308
1.D | .1471 .0246775 5.96 0.000 .0987329 .1954671
_cons | 9.705642 .0694641 139.72 0.000 9.569495 9.841789
─────────────+────────────────────────────────────────────────────────────────
D |
treat | 2.55806 .0802103 31.89 0.000 2.40085 2.715269
_cons | -1.844408 .2847883 -6.48 0.000 -2.402582 -1.286233
─────────────+────────────────────────────────────────────────────────────────
/athrho | -.0060068 .0481062 -0.12 0.901 -.1002933 .0882796
sigma | .4245195 .0066426 .411698 .4377404
──────────────────────────────────────────────────────────────────────────────
Wald test of indep. eqns. (rho = 0): chi2(1) = 0.02 Prob &amp;gt; chi2 = 0.9006
ATE of receipt (margins r.D):
──────────────────────────────────────────────────────────────────────────────
D | Contrast std. err. [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
(1 vs 0) | .1471 .0246775 .0987329 .1954671
──────────────────────────────────────────────────────────────────────────────
ATT of receipt (margins, predict(cte)):
──────────────────────────────────────────────────────────────────────────────
_cons | Margin std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
| .1471 .0246775 5.96 0.000 .0987329 .1954671
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The &lt;code>etregress&lt;/code> output reveals several important findings. The coefficient on &lt;code>D&lt;/code> (receipt) is 0.147 (SE = 0.025, p &amp;lt; 0.001, 95% CI [0.099, 0.195]), which is the estimated effect of actually receiving the cash transfer. This is larger than the offer-based estimates (0.113&amp;ndash;0.116) because not everyone who was offered the program received it &amp;mdash; the per-recipient effect is naturally larger than the per-offer effect. The Wald test of independent equations (rho = 0) has p = 0.901, indicating no evidence of endogeneity &amp;mdash; consistent with a well-designed RCT where unobservable factors do not drive both treatment receipt and consumption. The &lt;code>margins&lt;/code> commands confirm that both the ATE and ATT of receipt are 0.147 (identical in this case because the model assumes a constant treatment effect).&lt;/p>
&lt;h3 id="103-doubly-robust-estimation-of-receipt-effect">10.3 Doubly robust estimation of receipt effect&lt;/h3>
&lt;p>We can also estimate the receipt effect using a doubly robust approach, incorporating the baseline outcome &lt;code>y0&lt;/code> as an additional control variable (an ANCOVA-style adjustment) and including &lt;code>treat&lt;/code> (the random assignment) as a covariate in the treatment model for &lt;code>D&lt;/code>.&lt;/p>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/quarcs-lab/data-open/raw/master/ametrics/dataSIM4RCT.dta&amp;quot;, clear
keep if post==1
* Doubly robust ATE of receipt, controlling for baseline outcome
teffects ipwra (y y0 c.age i.female i.poverty c.edu) ///
(D c.age i.female i.poverty c.edu treat), vce(robust)
* Diagnostic checks
tebalance summarize age edu i.female i.poverty
tebalance summarize, baseline
tebalance density y0
tebalance density age
teffects overlap
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects estimation Number of obs = 2,000
Estimator : IPW regression adjustment
Outcome model : linear
Treatment model: logit
──────────────────────────────────────────────────────────────────────────────
| Robust
y | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
─────────────+────────────────────────────────────────────────────────────────
ATE |
D |
(1 vs 0) | .1172686 .0322495 3.64 0.000 .0540608 .1804764
─────────────+────────────────────────────────────────────────────────────────
POmean |
D |
0 | 10.03361 .0171459 585.19 0.000 10 10.06722
──────────────────────────────────────────────────────────────────────────────
&lt;/code>&lt;/pre>
&lt;p>The doubly robust estimate of the ATE of receipt is 0.117 (SE = 0.032, 95% CI [0.054, 0.180]). This is slightly lower than the &lt;code>etregress&lt;/code> estimate (0.147) and closer to the true effect of 0.12. The wider standard error (0.032 vs. 0.025) reflects the additional flexibility of the doubly robust approach. This specification includes &lt;code>y0&lt;/code> (the baseline outcome) in the outcome model, which controls for pre-treatment differences in consumption levels. The variable &lt;code>treat&lt;/code> appears in the treatment model for &lt;code>D&lt;/code> because random assignment is the strongest predictor of receipt.&lt;/p>
&lt;p>The diagnostic graphs below verify adequate covariate balance and propensity score overlap for the receipt model.&lt;/p>
&lt;p>&lt;img src="stata_rct_density_y0_receipt.png" alt="Density plot of baseline consumption (y0) for receivers and non-receivers, before and after IPWRA weighting.">&lt;/p>
&lt;p>&lt;img src="stata_rct_overlap_receipt.png" alt="Overlap plot showing propensity score distributions for receivers and non-receivers of the cash transfer.">&lt;/p>
&lt;p>The density and overlap plots confirm that the IPWRA weighting achieves good balance between receivers and non-receivers. After weighting, the effective sample sizes are approximately 999 treated and 1,001 control (rebalanced from the raw 923 receivers and 1,077 non-receivers). The weighted covariate means are closely aligned &amp;mdash; for example, the weighted mean age is 35.0 for receivers versus 35.2 for non-receivers, and the weighted poverty rate is 31.1% versus 31.4%. The propensity scores show sufficient overlap for reliable estimation.&lt;/p>
&lt;hr>
&lt;h2 id="11-comparing-all-estimates-----the-big-picture">11. Comparing all estimates &amp;mdash; the big picture&lt;/h2>
&lt;p>The table below brings together all estimates from the tutorial, providing a comprehensive overview of how different methods, estimands, and data structures relate to each other.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>#&lt;/th>
&lt;th>Method&lt;/th>
&lt;th>Approach&lt;/th>
&lt;th>Estimand&lt;/th>
&lt;th>Data&lt;/th>
&lt;th style="text-align:center">Estimate&lt;/th>
&lt;th style="text-align:center">SE&lt;/th>
&lt;th style="text-align:center">95% CI&lt;/th>
&lt;th style="text-align:center">Contains 0.12?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>Simple regression&lt;/td>
&lt;td>None&lt;/td>
&lt;td>ATE (offer)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.116&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.078, 0.154]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>Regression Adjustment&lt;/td>
&lt;td>Outcome model&lt;/td>
&lt;td>ATE (offer)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>Regression Adjustment&lt;/td>
&lt;td>Outcome model&lt;/td>
&lt;td>ATT (offer)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.076, 0.151]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4&lt;/td>
&lt;td>Inverse Prob. Weighting&lt;/td>
&lt;td>Treatment model&lt;/td>
&lt;td>ATE (offer)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5&lt;/td>
&lt;td>Inverse Prob. Weighting&lt;/td>
&lt;td>Treatment model&lt;/td>
&lt;td>ATT (offer)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.076, 0.151]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>6&lt;/td>
&lt;td>IPWRA (Doubly Robust)&lt;/td>
&lt;td>Both models&lt;/td>
&lt;td>ATE (offer)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.075, 0.150]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>7&lt;/td>
&lt;td>IPWRA (Doubly Robust)&lt;/td>
&lt;td>Both models&lt;/td>
&lt;td>ATT (offer)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.113&lt;/td>
&lt;td style="text-align:center">0.019&lt;/td>
&lt;td style="text-align:center">[0.076, 0.151]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>Basic DiD&lt;/td>
&lt;td>Panel FE&lt;/td>
&lt;td>ATT (offer)&lt;/td>
&lt;td>Panel&lt;/td>
&lt;td style="text-align:center">0.135&lt;/td>
&lt;td style="text-align:center">0.027&lt;/td>
&lt;td style="text-align:center">[0.081, 0.188]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>9&lt;/td>
&lt;td>DR-DiD (&lt;code>drdid&lt;/code>)&lt;/td>
&lt;td>Both + Panel&lt;/td>
&lt;td>ATT (offer)&lt;/td>
&lt;td>Panel&lt;/td>
&lt;td style="text-align:center">0.137&lt;/td>
&lt;td style="text-align:center">0.027&lt;/td>
&lt;td style="text-align:center">[0.084, 0.191]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>10&lt;/td>
&lt;td>DR-DiD (&lt;code>xthdidregress&lt;/code>)&lt;/td>
&lt;td>Both + Panel&lt;/td>
&lt;td>ATT (offer)&lt;/td>
&lt;td>Panel&lt;/td>
&lt;td style="text-align:center">0.137&lt;/td>
&lt;td style="text-align:center">0.027&lt;/td>
&lt;td style="text-align:center">[0.084, 0.191]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>11&lt;/td>
&lt;td>Endogenous treatment (&lt;code>etregress&lt;/code>)&lt;/td>
&lt;td>IV&lt;/td>
&lt;td>ATE (receipt)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.147&lt;/td>
&lt;td style="text-align:center">0.025&lt;/td>
&lt;td style="text-align:center">[0.099, 0.195]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>12&lt;/td>
&lt;td>DR receipt (&lt;code>teffects ipwra&lt;/code>)&lt;/td>
&lt;td>Both models&lt;/td>
&lt;td>ATE (receipt)&lt;/td>
&lt;td>Endline&lt;/td>
&lt;td style="text-align:center">0.117&lt;/td>
&lt;td style="text-align:center">0.032&lt;/td>
&lt;td style="text-align:center">[0.054, 0.180]&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;strong>True effect&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td style="text-align:center">&lt;strong>0.12&lt;/strong>&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;td style="text-align:center">&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="four-key-takeaways">Four key takeaways&lt;/h3>
&lt;p>&lt;strong>1. RA vs. IPW vs. DR.&lt;/strong> In this well-designed RCT, all three cross-sectional approaches give remarkably similar results (0.113&amp;ndash;0.116). This convergence occurs because randomization ensures that both the outcome model and the propensity score model are approximately correct. The differences are small &amp;mdash; but in observational studies, where one model might be misspecified, the choice of method matters much more. Doubly robust methods are the safest bet because they remain consistent if either model is correct.&lt;/p>
&lt;p>&lt;strong>2. ATE vs. ATT.&lt;/strong> For all cross-sectional methods, ATE and ATT are nearly identical (0.113&amp;ndash;0.116). This confirms that treatment effects are roughly homogeneous across households in this simulation. When treatment effects are heterogeneous &amp;mdash; for example, if the program benefits poorer households more &amp;mdash; ATE and ATT can diverge. The researcher must choose the estimand that matches their policy question: ATE for scaling decisions, ATT for program evaluation.&lt;/p>
&lt;p>&lt;strong>3. Cross-sectional vs. DiD.&lt;/strong> DiD estimates (0.135&amp;ndash;0.137) are slightly higher than cross-sectional estimates (0.113&amp;ndash;0.116), but all confidence intervals contain the true effect of 0.12. DiD&amp;rsquo;s main advantage is controlling for &lt;strong>time-invariant unobservable&lt;/strong> household characteristics &amp;mdash; less important in an RCT (where randomization handles confounding) but critical in quasi-experimental settings. DRDID extends the doubly robust logic to the panel setting, providing the most robust estimator in our toolkit. DiD inherently estimates the &lt;strong>ATT&lt;/strong> because its counterfactual is constructed specifically for the treated group.&lt;/p>
&lt;p>&lt;strong>4. Offer vs. receipt.&lt;/strong> The effect of actually receiving the cash transfer (0.117&amp;ndash;0.147) is larger than the effect of being offered it (0.113&amp;ndash;0.116), because imperfect compliance dilutes the offer-based estimates. The doubly robust receipt estimate (0.117) is closest to the true effect of 0.12, while the endogenous treatment model (0.147) is slightly higher. All confidence intervals contain 0.12.&lt;/p>
&lt;hr>
&lt;h2 id="12-summary-and-key-takeaways">12. Summary and key takeaways&lt;/h2>
&lt;p>The cash transfer program increased household consumption by approximately &lt;strong>11&amp;ndash;14%&lt;/strong> across all estimation methods, close to the true effect of &lt;strong>12%&lt;/strong>. Every confidence interval contained the true value, demonstrating that all methods successfully recovered the correct answer.&lt;/p>
&lt;h3 id="seven-methodological-lessons">Seven methodological lessons&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Always verify baseline balance&lt;/strong> before estimating treatment effects. Even with randomization, chance imbalances can occur &amp;mdash; as we saw with the gender variable (SMD = 9.3%).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Be explicit about your estimand.&lt;/strong> ATE answers the policymaker&amp;rsquo;s question (&amp;ldquo;What if we scale this up?&amp;quot;), while ATT answers the evaluator&amp;rsquo;s question (&amp;ldquo;Did it help the participants?&amp;quot;). Different methods target different estimands.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Regression adjustment models the outcome; IPW models treatment assignment; doubly robust does both.&lt;/strong> These three approaches represent fundamentally different strategies for causal estimation. Understanding what each models &amp;mdash; and what can go wrong &amp;mdash; is essential for choosing the right method.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>In a well-designed RCT, all three approaches converge.&lt;/strong> But doubly robust methods provide insurance against model misspecification, making them the standard recommendation in modern causal inference.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Panel data controls for time-invariant unobservables&lt;/strong> that cross-sectional methods cannot address. By comparing each household to itself over time, DiD absorbs household fixed effects &amp;mdash; motivation, geography, family culture &amp;mdash; that are invisible to cross-sectional approaches.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>DiD inherently estimates the ATT&lt;/strong> because its counterfactual is specific to the treated group. The control group&amp;rsquo;s time trend provides a counterfactual for what the treated group would have experienced without the program &amp;mdash; but it does not tell us what would happen if the program were given to the untreated.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Doubly robust DiD (DRDID)&lt;/strong> extends the DR logic to the panel setting. It combines the power of DiD (controlling for household fixed effects) with the robustness of doubly robust estimation (protection against model misspecification), making it the most robust panel estimator available.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="limitations">Limitations&lt;/h3>
&lt;ul>
&lt;li>This tutorial uses &lt;strong>simulated data&lt;/strong> with known parameters. Real-world data may exhibit more complex compliance patterns, heterogeneous effects, and missing data.&lt;/li>
&lt;li>The panel has only &lt;strong>two periods&lt;/strong> (baseline and endline), limiting our ability to test for pre-treatment trends or estimate dynamic treatment effects.&lt;/li>
&lt;li>Treatment effects are &lt;strong>homogeneous&lt;/strong> by construction. In practice, researchers should explore heterogeneity across subgroups.&lt;/li>
&lt;/ul>
&lt;h3 id="next-steps">Next steps&lt;/h3>
&lt;ul>
&lt;li>Apply these methods to &lt;strong>real-world RCT data&lt;/strong> from actual cash transfer programs&lt;/li>
&lt;li>Explore &lt;strong>heterogeneous treatment effects&lt;/strong> by gender, poverty status, or education level&lt;/li>
&lt;li>Extend to &lt;strong>multi-period panels&lt;/strong> with staggered treatment adoption, using modern DiD methods (Callaway and Sant&amp;rsquo;Anna, 2021)&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="13-exercises">13. Exercises&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Heterogeneous effects by gender.&lt;/strong> Estimate treatment effects separately for male-headed and female-headed households using IPWRA. Are the effects different? Does ATE still equal ATT when you restrict to subgroups?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Model misspecification.&lt;/strong> Compare the RA, IPW, and DR estimates when you deliberately misspecify the outcome model by omitting &lt;code>edu&lt;/code> and &lt;code>age&lt;/code> from the covariate list. Which method is most robust to this misspecification? What does this tell you about the value of doubly robust estimation?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Basic DiD vs. doubly robust DiD.&lt;/strong> Re-run the DiD analysis using the basic &lt;code>xtdidregress&lt;/code> command (no covariates) and compare it with the &lt;code>drdid&lt;/code> results (with covariates). How much do the estimates differ? What does this tell you about the role of covariate adjustment in DiD?&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="references">References&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://www.stata.com/manuals/teteffects.pdf" target="_blank" rel="noopener">Stata &lt;code>teffects&lt;/code> documentation &amp;mdash; Treatment-effects estimation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1016/j.jeconom.2020.06.003" target="_blank" rel="noopener">Sant&amp;rsquo;Anna, P.H.C. &amp;amp; Zhao, J. (2020). Doubly Robust Difference-in-Differences Estimators. &lt;em>Journal of Econometrics&lt;/em>, 219(1), 101&amp;ndash;122&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1017/CBO9781139025751" target="_blank" rel="noopener">Imbens, G. &amp;amp; Rubin, D. (2015). &lt;em>Causal Inference for Statistics, Social, and Biomedical Sciences&lt;/em>. Cambridge University Press&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://friosavila.github.io/stpackages/drdid.html" target="_blank" rel="noopener">Rios-Avila, F., Sant&amp;rsquo;Anna, P.H.C., &amp;amp; Callaway, B. &lt;code>drdid&lt;/code> &amp;mdash; Doubly Robust DID estimators for Stata&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://dimewiki.worldbank.org/iebaltab" target="_blank" rel="noopener">World Bank &lt;code>ietoolkit&lt;/code> / &lt;code>iebaltab&lt;/code> documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tdmize.github.io/data/" target="_blank" rel="noopener">Mize, T. &lt;code>balanceplot&lt;/code> &amp;mdash; Stata module for covariate balance visualization&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://youtu.be/Gr_fu5deDMk" target="_blank" rel="noopener">RCT Analysis: Cash Transfers, Panel Data, and Doubly Robust Estimation (YouTube)&lt;/a>&lt;/li>
&lt;/ol>
&lt;h4 id="acknowledgements">Acknowledgements&lt;/h4>
&lt;p>AI tools (Claude Code, Gemini, NotebookLM) were used to make the contents of this post more accessible to students. Nevertheless, the content in this post may still have errors. Caution is needed when applying the contents of this post to true research projects.&lt;/p></description></item><item><title>Introduction to Causal Inference: The DoWhy Approach with the Lalonde Dataset</title><link>https://carlos-mendez.org/post/python_dowhy/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/python_dowhy/</guid><description>&lt;div style="background:#0e1545; border-radius:12px; padding:8px;">
&lt;iframe style="border-radius:8px" src="https://open.spotify.com/embed/episode/7h6S9YzEroATdQabvJxi1W?utm_source=generator&amp;theme=0" width="100%" height="152" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy">&lt;/iframe>
&lt;/div>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Does a job training program actually cause participants to earn more, or do people who enroll in training simply differ from those who do not? This is the central challenge of &lt;strong>causal inference&lt;/strong>: distinguishing genuine treatment effects from confounding differences between groups. A simple comparison of average earnings between participants and non-participants can be misleading if the two groups differ in age, education, or prior employment history.&lt;/p>
&lt;p>&lt;strong>&lt;a href="https://www.pywhy.org/dowhy/" target="_blank" rel="noopener">DoWhy&lt;/a>&lt;/strong> is a Python library that provides a principled, end-to-end framework for causal inference. It organizes the analysis into four explicit steps &amp;mdash; &lt;strong>Model, Identify, Estimate, Refute&lt;/strong> &amp;mdash; each of which forces the analyst to state and test causal assumptions rather than hiding them inside a black-box estimator. In this tutorial, we apply DoWhy to the &lt;strong>&lt;a href="https://www.jstor.org/stable/1806062" target="_blank" rel="noopener">Lalonde dataset&lt;/a>&lt;/strong>, a classic dataset from the National Supported Work (NSW) Demonstration program, to estimate how much the job training program increased participants' earnings in 1978.&lt;/p>
&lt;p>&lt;strong>Learning objectives:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Understand DoWhy&amp;rsquo;s four-step causal inference workflow (Model, Identify, Estimate, Refute)&lt;/li>
&lt;li>Define a causal graph that encodes domain knowledge about confounders&lt;/li>
&lt;li>Identify causal estimands from the graph using the backdoor criterion&lt;/li>
&lt;li>Estimate causal effects using multiple methods (regression adjustment, IPW, doubly robust, propensity score stratification, propensity score matching)&lt;/li>
&lt;li>Assess robustness of estimates using refutation tests&lt;/li>
&lt;/ul>
&lt;h2 id="dowhys-four-step-framework">DoWhy&amp;rsquo;s four-step framework&lt;/h2>
&lt;p>Most statistical software lets you jump straight from data to estimates, skipping the hard work of stating assumptions and testing whether the results are trustworthy. DoWhy takes a different approach: it organizes every causal analysis into four explicit steps, each answering a distinct question.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph LR
A[&amp;quot;&amp;lt;b&amp;gt;1. Model&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Define causal&amp;lt;br/&amp;gt;assumptions&amp;quot;] --&amp;gt; B[&amp;quot;&amp;lt;b&amp;gt;2. Identify&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Find the right&amp;lt;br/&amp;gt;formula&amp;quot;]
B --&amp;gt; C[&amp;quot;&amp;lt;b&amp;gt;3. Estimate&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Compute the&amp;lt;br/&amp;gt;causal effect&amp;quot;]
C --&amp;gt; D[&amp;quot;&amp;lt;b&amp;gt;4. Refute&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Stress-test&amp;lt;br/&amp;gt;the result&amp;quot;]
style A fill:#6a9bcc,stroke:#141413,color:#fff
style B fill:#d97757,stroke:#141413,color:#fff
style C fill:#00d4c8,stroke:#141413,color:#fff
style D fill:#8b5cf6,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;p>Each step answers a specific question and builds on the previous one:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Model&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;What are the causal relationships?&amp;quot;&lt;/em> Encode your domain knowledge as a causal graph (a DAG). This is where you declare which variables cause which, making your assumptions explicit and debatable rather than hidden inside a regression.&lt;/li>
&lt;li>&lt;strong>Identify&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;Can we estimate the effect from data?&amp;quot;&lt;/em> Given the graph, DoWhy uses graph theory to determine whether the causal effect is identifiable &amp;mdash; meaning it can be computed from observed data alone &amp;mdash; and returns the mathematical formula (the &lt;em>estimand&lt;/em>) needed to do so.&lt;/li>
&lt;li>&lt;strong>Estimate&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;What is the causal effect?&amp;quot;&lt;/em> Apply one or more statistical methods to compute the actual numeric estimate. DoWhy supports multiple estimators so you can check whether different methods agree.&lt;/li>
&lt;li>&lt;strong>Refute&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;Should we trust the estimate?&amp;quot;&lt;/em> Run automated falsification tests that probe whether the result could be a statistical artifact, whether it is sensitive to unobserved confounders, and whether it is stable across subsamples.&lt;/li>
&lt;/ul>
&lt;p>The ordering is deliberate. You cannot estimate a causal effect without first identifying the correct formula, and you cannot identify the formula without first specifying your causal assumptions. This sequential discipline is DoWhy&amp;rsquo;s key contribution: it prevents the common mistake of running a regression and calling the coefficient &amp;ldquo;causal&amp;rdquo; without ever checking whether the adjustment set is correct or whether the result survives basic robustness checks.&lt;/p>
&lt;h2 id="setup-and-imports">Setup and imports&lt;/h2>
&lt;p>Before running the analysis, install the required package if needed:&lt;/p>
&lt;pre>&lt;code class="language-python">pip install dowhy # https://pypi.org/project/dowhy/
&lt;/code>&lt;/pre>
&lt;p>The following code imports all necessary libraries and sets configuration variables. We define the outcome, treatment, and covariate columns that will be used throughout the analysis.&lt;/p>
&lt;pre>&lt;code class="language-python">import warnings
warnings.filterwarnings(&amp;quot;ignore&amp;quot;)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression, LinearRegression as SklearnLR
from dowhy import CausalModel
from dowhy.datasets import lalonde_dataset
# Reproducibility
RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)
# Configuration
OUTCOME = &amp;quot;re78&amp;quot;
OUTCOME_LABEL = &amp;quot;Earnings in 1978 (USD)&amp;quot;
TREATMENT = &amp;quot;treat&amp;quot;
TREATMENT_LABEL = &amp;quot;Job Training (treat)&amp;quot;
COVARIATES = [&amp;quot;age&amp;quot;, &amp;quot;educ&amp;quot;, &amp;quot;black&amp;quot;, &amp;quot;hisp&amp;quot;, &amp;quot;married&amp;quot;, &amp;quot;nodegr&amp;quot;, &amp;quot;re74&amp;quot;, &amp;quot;re75&amp;quot;]
&lt;/code>&lt;/pre>
&lt;h2 id="data-loading-the-lalonde-dataset">Data loading: The Lalonde Dataset&lt;/h2>
&lt;p>The Lalonde dataset comes from the &lt;strong>National Supported Work (NSW) Demonstration&lt;/strong>, a randomized employment program conducted in the 1970s in the United States. Eligible applicants &amp;mdash; mostly disadvantaged workers with limited employment histories &amp;mdash; were randomly assigned to receive job training (treatment) or not (control). The dataset records each participant&amp;rsquo;s demographics, prior earnings, and post-program earnings in 1978. It has become a benchmark for testing causal inference methods because the random assignment provides a credible ground truth against which observational estimators can be compared.&lt;/p>
&lt;p>DoWhy includes the Lalonde dataset directly, so we can load it with the &lt;a href="https://www.pywhy.org/dowhy/v0.14/example_notebooks/lalonde_pandas_api.html" target="_blank" rel="noopener">&lt;code>lalonde_dataset()&lt;/code>&lt;/a> function.&lt;/p>
&lt;pre>&lt;code class="language-python">df = lalonde_dataset()
# Convert boolean treatment to integer for DoWhy compatibility
df[TREATMENT] = df[TREATMENT].astype(int)
print(f&amp;quot;Dataset shape: {df.shape}&amp;quot;)
print(f&amp;quot;\nTreatment groups:&amp;quot;)
print(df[TREATMENT].value_counts().sort_index().rename({0: &amp;quot;Control&amp;quot;, 1: &amp;quot;Training&amp;quot;}))
print(f&amp;quot;\nOutcome ({OUTCOME}) summary:&amp;quot;)
print(df[OUTCOME].describe().round(2))
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Dataset shape: (445, 12)
Treatment groups:
treat
Control 260
Training 185
Name: count, dtype: int64
Outcome (re78) summary:
count 445.00
mean 5300.76
std 6631.49
min 0.00
25% 0.00
50% 3701.81
75% 8124.72
max 60307.93
Name: re78, dtype: float64
&lt;/code>&lt;/pre>
&lt;p>The dataset contains 445 participants with 12 variables. The treatment is split into 185 individuals who received job training and 260 controls who did not. The outcome variable, real earnings in 1978 (&lt;code>re78&lt;/code>), has a mean of \$5,301 but enormous variation (standard deviation of \$6,631), ranging from \$0 to \$60,308. The median (\$3,702) is well below the mean, indicating a right-skewed distribution &amp;mdash; many participants earned little or nothing while a few earned substantially more.&lt;/p>
&lt;h2 id="exploratory-data-analysis">Exploratory data analysis&lt;/h2>
&lt;h3 id="outcome-distribution-by-treatment-group">Outcome distribution by treatment group&lt;/h3>
&lt;p>Before any causal modeling, we compare the raw earnings distributions between training and control groups. If the training program had an effect, we expect to see higher average earnings in the training group &amp;mdash; but we cannot yet tell whether any difference is truly caused by the program or driven by pre-existing differences between the groups.&lt;/p>
&lt;pre>&lt;code class="language-python">fig, ax = plt.subplots(figsize=(8, 5))
for group, label, color in [(0, &amp;quot;Control&amp;quot;, &amp;quot;#6a9bcc&amp;quot;), (1, &amp;quot;Training&amp;quot;, &amp;quot;#d97757&amp;quot;)]:
subset = df[df[TREATMENT] == group][OUTCOME]
ax.hist(subset, bins=30, alpha=0.6, label=f&amp;quot;{label} (mean=${subset.mean():,.0f})&amp;quot;,
color=color, edgecolor=&amp;quot;white&amp;quot;)
ax.set_xlabel(OUTCOME_LABEL)
ax.set_ylabel(&amp;quot;Count&amp;quot;)
ax.set_title(f&amp;quot;Distribution of {OUTCOME_LABEL} by Treatment Group&amp;quot;)
ax.legend()
plt.savefig(&amp;quot;dowhy_outcome_by_treatment.png&amp;quot;, dpi=300, bbox_inches=&amp;quot;tight&amp;quot;)
plt.show()
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="dowhy_outcome_by_treatment.png" alt="Distribution of 1978 earnings by treatment group. The training group shows a higher mean.">&lt;/p>
&lt;p>Both distributions are heavily right-skewed, with a large spike near zero reflecting participants who had no earnings. The training group has a higher mean (\$6,349) compared to the control group (\$4,555), a raw difference of about \$1,794. However, both distributions overlap substantially, and the spike at zero is present in both groups, indicating that many participants struggled to find employment regardless of training.&lt;/p>
&lt;h3 id="covariate-balance">Covariate balance&lt;/h3>
&lt;p>In a randomized experiment, we expect the covariates to be balanced across treatment and control groups. Under randomization, the naive difference-in-means is &lt;strong>unbiased&lt;/strong> for the ATE in expectation &amp;mdash; but with a finite sample of 445 observations, chance imbalances can still arise and reduce the precision of the estimate. Checking covariate balance helps us assess whether such imbalances exist and whether covariate adjustment could improve efficiency. We first examine the categorical covariates as proportions, then use Standardized Mean Differences to assess balance across all covariates on a common scale.&lt;/p>
&lt;h4 id="categorical-covariates">Categorical covariates&lt;/h4>
&lt;p>The four binary covariates &amp;mdash; &lt;code>black&lt;/code>, &lt;code>hisp&lt;/code>, &lt;code>married&lt;/code>, and &lt;code>nodegr&lt;/code> (no high school degree) &amp;mdash; indicate demographic group membership. Comparing their proportions across treatment and control groups reveals whether random assignment produced balanced groups on these characteristics.&lt;/p>
&lt;pre>&lt;code class="language-python">categorical_vars = [&amp;quot;black&amp;quot;, &amp;quot;hisp&amp;quot;, &amp;quot;married&amp;quot;, &amp;quot;nodegr&amp;quot;]
cat_means = df.groupby(TREATMENT)[categorical_vars].mean()
fig, ax = plt.subplots(figsize=(8, 5))
x = np.arange(len(categorical_vars))
width = 0.35
ax.bar(x - width / 2, cat_means.loc[0], width, label=&amp;quot;Control&amp;quot;,
color=&amp;quot;#6a9bcc&amp;quot;, edgecolor=&amp;quot;white&amp;quot;)
ax.bar(x + width / 2, cat_means.loc[1], width, label=&amp;quot;Training&amp;quot;,
color=&amp;quot;#d97757&amp;quot;, edgecolor=&amp;quot;white&amp;quot;)
ax.set_xticks(x)
ax.set_xticklabels(categorical_vars, rotation=45, ha=&amp;quot;right&amp;quot;)
ax.set_ylabel(&amp;quot;Proportion&amp;quot;)
ax.set_ylim(0, 1)
ax.set_title(&amp;quot;Covariate Balance: Categorical Variables&amp;quot;)
ax.legend()
plt.savefig(&amp;quot;dowhy_covariate_balance_categorical.png&amp;quot;, dpi=300, bbox_inches=&amp;quot;tight&amp;quot;)
plt.show()
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="dowhy_covariate_balance_categorical.png" alt="Proportions of categorical covariates for control and training groups. Both groups show similar demographic composition.">&lt;/p>
&lt;p>The categorical covariates are well balanced across treatment and control groups, consistent with random assignment. The sample is predominantly Black (83%) and has a high rate of lacking a high school diploma (78%), reflecting the disadvantaged population targeted by the NSW program. Hispanic and married proportions are low in both groups (roughly 6% and 16%, respectively), with no meaningful differences between treatment arms.&lt;/p>
&lt;h4 id="covariate-balance-standardized-mean-differences">Covariate balance: Standardized Mean Differences&lt;/h4>
&lt;p>Comparing raw group means can be misleading when covariates are measured on different scales. Suppose the control group earns \$500 more in prior earnings (&lt;code>re74&lt;/code>) than the training group, and is also 1 year older on average. Which imbalance is larger? The raw numbers cannot answer this question &amp;mdash; \$500 sounds like a lot, but prior earnings vary by thousands of dollars across individuals, so a \$500 gap may be trivial relative to the spread. A 1-year age difference sounds small, but if most participants are clustered around age 25, that gap may represent a meaningful shift in the distribution.&lt;/p>
&lt;p>The &lt;strong>Standardized Mean Difference (SMD)&lt;/strong> resolves this by asking: &lt;em>how many standard deviations apart are the treatment and control groups on each covariate?&lt;/em> For each variable, we compute the difference in group means and divide by the pooled standard deviation. This converts every covariate &amp;mdash; whether binary, measured in years, or measured in dollars &amp;mdash; to the same unitless scale, making imbalances directly comparable:&lt;/p>
&lt;p>$$\text{SMD} = \frac{\bar{X}_{treated} - \bar{X}_{control}}{\sqrt{(s^2_{treated} + s^2_{control}) \,/\, 2}}$$&lt;/p>
&lt;p>An absolute SMD below 0.1 is the conventional threshold for &amp;ldquo;good balance&amp;rdquo; (&lt;a href="https://doi.org/10.1002/sim.3697" target="_blank" rel="noopener">Austin, 2011&lt;/a>). Values above 0.1 signal that the groups differ by more than one-tenth of a standard deviation on that variable &amp;mdash; enough to potentially confound the treatment effect estimate. A &lt;a href="https://doi.org/10.1002/sim.3697" target="_blank" rel="noopener">&lt;strong>Love plot&lt;/strong>&lt;/a> displays the absolute SMD for all covariates as horizontal bars, with a dashed line at the 0.1 threshold. Bars in steel blue fall below the threshold (balanced), while bars in warm orange exceed it (imbalanced).&lt;/p>
&lt;pre>&lt;code class="language-python"># Standardized Mean Difference (SMD) for all covariates
treated = df[df[TREATMENT] == 1]
control = df[df[TREATMENT] == 0]
smd_values = {}
for var in COVARIATES:
diff = treated[var].mean() - control[var].mean()
pooled_sd = np.sqrt((treated[var].std()**2 + control[var].std()**2) / 2)
smd_values[var] = diff / pooled_sd
smd_df = pd.DataFrame({&amp;quot;variable&amp;quot;: list(smd_values.keys()),
&amp;quot;smd&amp;quot;: list(smd_values.values())})
smd_df[&amp;quot;abs_smd&amp;quot;] = smd_df[&amp;quot;smd&amp;quot;].abs()
smd_df = smd_df.sort_values(&amp;quot;abs_smd&amp;quot;)
fig, ax = plt.subplots(figsize=(8, 5))
colors = [&amp;quot;#6a9bcc&amp;quot; if v &amp;lt; 0.1 else &amp;quot;#d97757&amp;quot; for v in smd_df[&amp;quot;abs_smd&amp;quot;]]
ax.barh(smd_df[&amp;quot;variable&amp;quot;], smd_df[&amp;quot;abs_smd&amp;quot;], color=colors,
edgecolor=&amp;quot;white&amp;quot;, height=0.6)
ax.axvline(0.1, color=&amp;quot;#141413&amp;quot;, linewidth=1, linestyle=&amp;quot;--&amp;quot;, label=&amp;quot;SMD = 0.1 threshold&amp;quot;)
ax.set_xlabel(&amp;quot;Absolute Standardized Mean Difference&amp;quot;)
ax.set_title(&amp;quot;Covariate Balance: Love Plot (All Covariates)&amp;quot;)
ax.legend(loc=&amp;quot;lower right&amp;quot;)
plt.savefig(&amp;quot;dowhy_covariate_balance_smd.png&amp;quot;, dpi=300, bbox_inches=&amp;quot;tight&amp;quot;)
plt.show()
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="dowhy_covariate_balance_smd.png" alt="Love plot showing standardized mean differences for all eight covariates. Most fall below the 0.1 threshold, indicating good balance.">&lt;/p>
&lt;p>The Love plot reveals a more nuanced picture than raw mean comparisons would suggest. Prior earnings (&lt;code>re74&lt;/code> and &lt;code>re75&lt;/code>) &amp;mdash; which appeared imbalanced when comparing raw means in the thousands &amp;mdash; are actually well balanced on the standardized scale (SMD &amp;lt; 0.1), because their large variances absorb the mean differences. In contrast, &lt;code>nodegr&lt;/code> shows the largest imbalance (SMD ~0.31), followed by &lt;code>hisp&lt;/code> (~0.18) and &lt;code>educ&lt;/code> (~0.14). These imbalances, despite random assignment, reflect the small sample size and the disadvantaged population targeted by NSW. Although the naive difference-in-means remains unbiased under randomization, adjusting for these chance imbalances can &lt;strong>improve the precision&lt;/strong> of the treatment effect estimate &amp;mdash; a well-known result in the experimental design literature (&lt;a href="https://doi.org/10.1214/12-AOAS583" target="_blank" rel="noopener">Lin, 2013&lt;/a>; &lt;a href="https://doi.org/10.1214/08-AOAS171" target="_blank" rel="noopener">Freedman, 2008&lt;/a>).&lt;/p>
&lt;h2 id="the-causal-inference-problem">The causal inference problem&lt;/h2>
&lt;h3 id="ate-vs-att-two-different-causal-questions">ATE vs ATT: Two different causal questions&lt;/h3>
&lt;p>Before estimating the treatment effect, we need to be precise about &lt;em>which&lt;/em> causal question we are asking. There are two distinct estimands, each answering a different policy-relevant question:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Average Treatment Effect (ATE)&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;What would happen if we assigned treatment to a random person from the entire population?&amp;quot;&lt;/em> The ATE averages the treatment effect over &lt;strong>everyone&lt;/strong> &amp;mdash; both the treated and the untreated:&lt;/li>
&lt;/ul>
&lt;p>$$\text{ATE} = E[Y(1) - Y(0)]$$&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Average Treatment Effect on the Treated (ATT)&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;What was the effect of treatment for those who actually received it?&amp;quot;&lt;/em> The ATT averages the treatment effect only over the &lt;strong>treated&lt;/strong> subpopulation:&lt;/li>
&lt;/ul>
&lt;p>$$\text{ATT} = E[Y(1) - Y(0) \mid T = 1]$$&lt;/p>
&lt;p>The distinction matters because the people who receive treatment may differ systematically from those who do not. If the training program helps disadvantaged workers the most, and disadvantaged workers are more likely to enroll, then the ATT (the effect on those who enrolled) will be larger than the ATE (the effect if we enrolled everyone at random). Conversely, if the program is most effective for workers who are &lt;em>least&lt;/em> likely to enroll, the ATE could exceed the ATT.&lt;/p>
&lt;p>&lt;strong>In this tutorial, we estimate the ATE&lt;/strong> &amp;mdash; the average effect of the NSW job training program across the entire study population. This is the natural estimand for a randomized experiment where we want to evaluate the program&amp;rsquo;s overall impact. Four of our five estimation methods (regression adjustment, IPW, AIPW, and propensity score stratification) target the ATE directly. The exception is &lt;strong>propensity score matching&lt;/strong>, which discards unmatched control units and therefore shifts the estimand toward the ATT &amp;mdash; we flag this distinction when we discuss the matching results.&lt;/p>
&lt;h3 id="why-simple-comparisons-can-mislead">Why simple comparisons can mislead&lt;/h3>
&lt;p>A naive approach to estimating the treatment effect is to compute the difference in mean outcomes between the training and control groups. This gives us the &lt;strong>Average Treatment Effect (ATE)&lt;/strong>:&lt;/p>
&lt;p>$$\text{ATE}_{naive} = \bar{Y}_{treated} - \bar{Y}_{control}$$&lt;/p>
&lt;p>While this is a natural starting point and is &lt;strong>unbiased in expectation&lt;/strong> under randomization, it can be imprecise when finite-sample covariate imbalances exist. Adjusting for covariates that predict the outcome can sharpen the estimate. In observational studies, the problem is more severe &amp;mdash; without adjustment, the naive estimator can be genuinely biased by confounding.&lt;/p>
&lt;pre>&lt;code class="language-python">mean_treated = df[df[TREATMENT] == 1][OUTCOME].mean()
mean_control = df[df[TREATMENT] == 0][OUTCOME].mean()
naive_ate = mean_treated - mean_control
print(f&amp;quot;Mean earnings (Training): ${mean_treated:,.2f}&amp;quot;)
print(f&amp;quot;Mean earnings (Control): ${mean_control:,.2f}&amp;quot;)
print(f&amp;quot;Naive ATE (difference): ${naive_ate:,.2f}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Mean earnings (Training): $6,349.14
Mean earnings (Control): $4,554.80
Naive ATE (difference): $1,794.34
&lt;/code>&lt;/pre>
&lt;p>The naive estimate suggests that training increases earnings by \$1,794 on average. Under randomization, this estimate is unbiased in expectation, but the finite-sample covariate imbalances we observed earlier (particularly in &lt;code>nodegr&lt;/code>, &lt;code>hisp&lt;/code>, and &lt;code>educ&lt;/code>) mean that covariate adjustment can sharpen the estimate and account for chance differences between groups. This is where DoWhy&amp;rsquo;s structured framework helps &amp;mdash; it forces us to explicitly model our causal assumptions, identify the correct estimand, apply rigorous estimation methods, and test whether the results hold up under scrutiny.&lt;/p>
&lt;h2 id="step-1-model-----define-the-causal-graph">Step 1: Model &amp;mdash; Define the causal graph&lt;/h2>
&lt;p>The first step in DoWhy&amp;rsquo;s framework is to encode our &lt;strong>domain knowledge&lt;/strong> as a causal graph &amp;mdash; a Directed Acyclic Graph (DAG) that specifies which variables cause which. In our case, the covariates (age, education, race, prior earnings, etc.) are &lt;strong>common causes&lt;/strong> of both treatment assignment and the outcome. Even in a randomized experiment, these covariates predict the outcome and adjusting for them improves precision, so we include them in the model. This also makes the tutorial directly applicable to observational settings where these variables are genuine confounders.&lt;/p>
&lt;h3 id="what-is-a-dag">What is a DAG?&lt;/h3>
&lt;p>A &lt;strong>Directed Acyclic Graph&lt;/strong> is the formal language of causal inference. Each word in the name carries meaning:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Directed&lt;/strong> &amp;mdash; every edge is an arrow pointing from cause to effect. If age affects earnings, we draw an arrow from &lt;code>age&lt;/code> to &lt;code>re78&lt;/code>, never the reverse.&lt;/li>
&lt;li>&lt;strong>Acyclic&lt;/strong> &amp;mdash; there are no feedback loops. You cannot follow the arrows and return to where you started. This rules out simultaneous causation (e.g., &amp;ldquo;A causes B and B causes A at the same time&amp;rdquo;), which requires more advanced models.&lt;/li>
&lt;li>&lt;strong>Graph&lt;/strong> &amp;mdash; variables are &lt;strong>nodes&lt;/strong> (circles or squares) and causal relationships are &lt;strong>edges&lt;/strong> (arrows). The full picture is a map of which variables drive which.&lt;/li>
&lt;/ul>
&lt;p>The DAG is not a statistical model &amp;mdash; it encodes &lt;em>qualitative&lt;/em> assumptions about the data-generating process before we look at a single number. Its power lies in what it tells us about which variables to adjust for and which to leave alone.&lt;/p>
&lt;h3 id="types-of-variables-in-a-causal-graph">Types of variables in a causal graph&lt;/h3>
&lt;p>Not all variables play the same role. Understanding the three fundamental types is essential for deciding what to control for:&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
C[&amp;quot;&amp;lt;b&amp;gt;Confounder&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;(e.g., prior earnings)&amp;quot;] --&amp;gt;|&amp;quot;affects&amp;quot;| T[&amp;quot;Treatment&amp;quot;]
C --&amp;gt;|&amp;quot;affects&amp;quot;| Y[&amp;quot;Outcome&amp;quot;]
T -.-&amp;gt;|&amp;quot;causal effect&amp;quot;| Y
style C fill:#00d4c8,stroke:#141413,color:#fff
style T fill:#6a9bcc,stroke:#141413,color:#fff
style Y fill:#d97757,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>Confounders&lt;/strong> (common causes) &amp;mdash; A variable that affects &lt;em>both&lt;/em> the treatment and the outcome. For example, prior earnings (&lt;code>re74&lt;/code>) may influence whether someone enrolls in training &lt;em>and&lt;/em> how much they earn later. Confounders create a spurious association between treatment and outcome. &lt;strong>You must adjust for confounders&lt;/strong> to isolate the causal effect.&lt;/li>
&lt;/ul>
&lt;pre>&lt;code class="language-mermaid">graph LR
T[&amp;quot;Treatment&amp;quot;] --&amp;gt;|&amp;quot;causes&amp;quot;| M[&amp;quot;&amp;lt;b&amp;gt;Mediator&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;(e.g., skills)&amp;quot;]
M --&amp;gt;|&amp;quot;causes&amp;quot;| Y[&amp;quot;Outcome&amp;quot;]
style T fill:#6a9bcc,stroke:#141413,color:#fff
style M fill:#00d4c8,stroke:#141413,color:#fff
style Y fill:#d97757,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>Mediators&lt;/strong> &amp;mdash; A variable that lies &lt;em>on&lt;/em> the causal path from treatment to outcome. For example, if job training increases skills, and skills increase earnings, then &lt;code>skills&lt;/code> is a mediator. &lt;strong>You should NOT adjust for mediators&lt;/strong> &amp;mdash; doing so would block the very causal pathway you are trying to measure, attenuating or eliminating the estimated effect.&lt;/li>
&lt;/ul>
&lt;pre>&lt;code class="language-mermaid">graph TD
T[&amp;quot;Treatment&amp;quot;] --&amp;gt;|&amp;quot;affects&amp;quot;| Col[&amp;quot;&amp;lt;b&amp;gt;Collider&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;(e.g., in_survey)&amp;quot;]
Y[&amp;quot;Outcome&amp;quot;] --&amp;gt;|&amp;quot;affects&amp;quot;| Col
T -.-&amp;gt;|&amp;quot;causal effect&amp;quot;| Y
style T fill:#6a9bcc,stroke:#141413,color:#fff
style Col fill:#00d4c8,stroke:#141413,color:#fff
style Y fill:#d97757,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>Colliders&lt;/strong> &amp;mdash; A variable that is &lt;em>caused by&lt;/em> both the treatment and the outcome (or by variables on both sides). For example, if both training and high earnings make someone likely to appear in a follow-up survey, then &lt;code>in_survey&lt;/code> is a collider. &lt;strong>You should NOT condition on colliders&lt;/strong> &amp;mdash; doing so can create a spurious association between treatment and outcome even where none exists (a phenomenon called &lt;em>collider bias&lt;/em> or &lt;em>selection bias&lt;/em>).&lt;/li>
&lt;/ul>
&lt;p>In the Lalonde dataset, all eight covariates (age, education, race, marital status, degree status, and prior earnings) are measured &lt;em>before&lt;/em> treatment assignment, so they can only be confounders &amp;mdash; they cannot be mediators or colliders. This makes the graph straightforward: every covariate points to both &lt;code>treat&lt;/code> and &lt;code>re78&lt;/code>.&lt;/p>
&lt;p>The causal structure we assume is:&lt;/p>
&lt;ul>
&lt;li>Each covariate (age, educ, black, hisp, married, nodegr, re74, re75) affects both treatment assignment and earnings&lt;/li>
&lt;li>Treatment (&lt;code>treat&lt;/code>) affects the outcome (&lt;code>re78&lt;/code>)&lt;/li>
&lt;li>No covariate is itself caused by the treatment (pre-treatment variables)&lt;/li>
&lt;/ul>
&lt;p>We now create the &lt;a href="https://www.pywhy.org/dowhy/v0.11.1/dowhy.html#dowhy.causal_model.CausalModel" target="_blank" rel="noopener">&lt;code>CausalModel&lt;/code>&lt;/a> in DoWhy, specifying the treatment, outcome, and common causes. The model object stores the data, the causal graph, and metadata that DoWhy will use in subsequent steps to determine the correct adjustment strategy.&lt;/p>
&lt;pre>&lt;code class="language-python">model = CausalModel(
data=df,
treatment=TREATMENT,
outcome=OUTCOME,
common_causes=COVARIATES,
)
print(&amp;quot;CausalModel created successfully.&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>CausalModel created successfully.
&lt;/code>&lt;/pre>
&lt;p>DoWhy can visualize the causal graph it constructed using the &lt;a href="https://www.pywhy.org/dowhy/v0.11.1/dowhy.html#dowhy.causal_model.CausalModel.view_model" target="_blank" rel="noopener">&lt;code>view_model()&lt;/code>&lt;/a> method, which uses Graphviz to render the DAG automatically from the model&amp;rsquo;s internal graph representation:&lt;/p>
&lt;pre>&lt;code class="language-python"># Visualize the causal graph using DoWhy's built-in method
model.view_model(layout=&amp;quot;dot&amp;quot;)
from IPython.display import Image, display
display(Image(filename=&amp;quot;causal_model.png&amp;quot;))
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="dowhy_causal_graph.png" alt="Causal graph generated by DoWhy showing confounders as common causes of both treatment and outcome.">&lt;/p>
&lt;p>The DAG makes our assumptions explicit: the eight covariates are common causes that affect both treatment assignment (&lt;code>treat&lt;/code>) and earnings (&lt;code>re78&lt;/code>). The arrows encode the direction of causation &amp;mdash; each confounder points to both &lt;code>treat&lt;/code> and &lt;code>re78&lt;/code>, and &lt;code>treat&lt;/code> points to &lt;code>re78&lt;/code> (the causal effect we want to estimate). By stating these assumptions as a graph, DoWhy can automatically determine which variables need to be adjusted for and which estimation strategies are valid.&lt;/p>
&lt;h2 id="step-2-identify-----find-the-causal-estimand">Step 2: Identify &amp;mdash; Find the causal estimand&lt;/h2>
&lt;p>With the causal graph defined, DoWhy&amp;rsquo;s &lt;a href="https://www.pywhy.org/dowhy/v0.11.1/dowhy.html#dowhy.causal_model.CausalModel.identify_effect" target="_blank" rel="noopener">&lt;code>identify_effect()&lt;/code>&lt;/a> method uses graph theory to &lt;strong>identify&lt;/strong> the causal estimand &amp;mdash; the mathematical expression that, if computed correctly, equals the true causal effect. This step determines &lt;em>whether&lt;/em> the effect is identifiable from the data given our assumptions, and &lt;em>what&lt;/em> variables we need to condition on.&lt;/p>
&lt;h3 id="what-does-identification-mean">What does &amp;ldquo;identification&amp;rdquo; mean?&lt;/h3>
&lt;p>In causal inference, &lt;strong>identification&lt;/strong> answers a deceptively simple question: &lt;em>can we compute the causal effect from the data we have, without running a new experiment?&lt;/em> The answer is not always yes. Consider a scenario where an unmeasured variable (say, &amp;ldquo;motivation&amp;rdquo;) affects both whether someone enrolls in training and how much they earn afterward. No amount of data on age, education, and prior earnings can untangle the causal effect of training from the confounding effect of motivation &amp;mdash; the causal effect is &lt;strong>not identified&lt;/strong> without observing motivation.&lt;/p>
&lt;p>Identification is the bridge between &lt;em>causal assumptions&lt;/em> (encoded in the graph) and &lt;em>statistical computation&lt;/em> (what we can actually calculate from data). If the effect is identified, the identification step produces an &lt;strong>estimand&lt;/strong> &amp;mdash; a precise mathematical formula that tells us exactly which conditional expectations or reweightings to compute. If the effect is not identified, no estimation method can produce a credible causal estimate, no matter how sophisticated.&lt;/p>
&lt;h3 id="identification-strategies">Identification strategies&lt;/h3>
&lt;p>DoWhy checks three main strategies, each applicable in different causal structures:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_backdoor.html" target="_blank" rel="noopener">Backdoor criterion&lt;/a>&lt;/strong> &amp;mdash; The most common strategy. It applies when we can observe all confounders between treatment and outcome. By conditioning on these confounders, we &amp;ldquo;block&amp;rdquo; all backdoor paths &amp;mdash; non-causal pathways that create spurious associations. In the Lalonde example, conditioning on the eight covariates satisfies the backdoor criterion because they are the only common causes of &lt;code>treat&lt;/code> and &lt;code>re78&lt;/code>.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_natural_experiments.html" target="_blank" rel="noopener">Instrumental variables (IV)&lt;/a>&lt;/strong> &amp;mdash; Useful when some confounders are &lt;em>unobserved&lt;/em>. An instrument is a variable that affects treatment but has &lt;em>no direct effect&lt;/em> on the outcome except through the treatment itself. For example, draft lottery numbers have been used as instruments for military service: the lottery affects whether someone serves (treatment) but has no direct effect on later earnings (outcome) except through the service itself. IV estimation requires strong assumptions but can identify causal effects when backdoor adjustment is impossible.&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/index.html" target="_blank" rel="noopener">Front-door criterion&lt;/a>&lt;/strong> &amp;mdash; Applies when there is a &lt;strong>mediator&lt;/strong> that fully transmits the treatment effect and is itself unconfounded with the outcome. This strategy is rare in practice but theoretically important: it can identify causal effects even in the presence of unmeasured confounders between treatment and outcome, as long as the mediator pathway is clean.&lt;/li>
&lt;/ul>
&lt;p>A key advantage of DoWhy is that &lt;strong>it automates the identification step&lt;/strong>. Given the causal graph, DoWhy algorithmically checks which strategies are valid and returns the correct estimand. This prevents a common and dangerous mistake in applied work: manually choosing which variables to &amp;ldquo;control for&amp;rdquo; without formally checking whether the chosen adjustment set actually satisfies the conditions for causal identification.&lt;/p>
&lt;pre>&lt;code class="language-python">identified_estimand = model.identify_effect(proceed_when_unidentifiable=True)
print(identified_estimand)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Estimand type: EstimandType.NONPARAMETRIC_ATE
### Estimand : 1
Estimand name: backdoor
Estimand expression:
d
────────(E[re78|educ,black,age,hisp,re75,married,re74,nodegr])
d[treat]
Estimand assumption 1, Unconfoundedness: If U→{treat} and U→re78
then P(re78|treat,educ,black,age,hisp,re75,married,re74,nodegr,U)
= P(re78|treat,educ,black,age,hisp,re75,married,re74,nodegr)
&lt;/code>&lt;/pre>
&lt;p>DoWhy identifies the &lt;strong>backdoor estimand&lt;/strong> as the primary identification strategy, expressing the causal effect as the derivative of the conditional expectation of earnings with respect to treatment, conditioning on all eight covariates. The critical assumption is &lt;strong>unconfoundedness&lt;/strong> &amp;mdash; there are no unmeasured confounders beyond the ones we specified. DoWhy also checks for instrumental variable and front-door estimands but finds none applicable, which is expected given our graph structure.&lt;/p>
&lt;h2 id="step-3-estimate-----compute-the-causal-effect">Step 3: Estimate &amp;mdash; Compute the causal effect&lt;/h2>
&lt;p>With the estimand identified, we now use &lt;a href="https://www.pywhy.org/dowhy/v0.11.1/dowhy.html#dowhy.causal_model.CausalModel.estimate_effect" target="_blank" rel="noopener">&lt;code>estimate_effect()&lt;/code>&lt;/a> to compute the actual causal effect estimate. DoWhy supports multiple estimation methods, each with different assumptions and properties. We compare five approaches to see how robust the estimate is across methods.&lt;/p>
&lt;p>Causal estimation methods fall into &lt;strong>three broad paradigms&lt;/strong>, distinguished by what they model:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Outcome modeling&lt;/strong> (Regression Adjustment) &amp;mdash; directly models the relationship $E[Y \mid X, T]$ between covariates, treatment, and outcome. Its validity depends on correctly specifying this outcome model.&lt;/li>
&lt;li>&lt;strong>Treatment modeling&lt;/strong> (IPW, PS Stratification, PS Matching) &amp;mdash; models the treatment assignment mechanism $P(T \mid X)$ (the propensity score) and uses it to remove confounding. All three methods rely exclusively on the propensity score &amp;mdash; they differ in &lt;em>how&lt;/em> they use it (reweighting, grouping, or pairing observations) but none of them model the outcome. Their validity depends on correctly specifying the propensity score model.&lt;/li>
&lt;li>&lt;strong>Doubly robust&lt;/strong> (AIPW) &amp;mdash; the only true hybrid. It explicitly combines an outcome model $E[Y \mid X, T]$ with a propensity score model $P(T \mid X)$, and is consistent if &lt;em>either&lt;/em> model is correctly specified. This &amp;ldquo;double protection&amp;rdquo; is why it is called doubly robust.&lt;/li>
&lt;/ol>
&lt;p>The following diagram shows how these paradigms relate to the five methods we will apply:&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
Root[&amp;quot;&amp;lt;b&amp;gt;Estimation Methods&amp;lt;/b&amp;gt;&amp;quot;] --&amp;gt; OM[&amp;quot;&amp;lt;b&amp;gt;Outcome Modeling&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Models E[Y | X, T]&amp;lt;/i&amp;gt;&amp;quot;]
Root --&amp;gt; TM[&amp;quot;&amp;lt;b&amp;gt;Treatment Modeling&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Models P(T | X)&amp;lt;/i&amp;gt;&amp;quot;]
Root --&amp;gt; DR_cat[&amp;quot;&amp;lt;b&amp;gt;Doubly Robust&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Models both E[Y | X, T]&amp;lt;br/&amp;gt;and P(T | X)&amp;lt;/i&amp;gt;&amp;quot;]
OM --&amp;gt; RA[&amp;quot;Regression&amp;lt;br/&amp;gt;Adjustment&amp;quot;]
TM --&amp;gt; IPW[&amp;quot;Inverse Probability&amp;lt;br/&amp;gt;Weighting&amp;quot;]
TM --&amp;gt; PSS[&amp;quot;PS&amp;lt;br/&amp;gt;Stratification&amp;quot;]
TM --&amp;gt; PSM[&amp;quot;PS&amp;lt;br/&amp;gt;Matching&amp;quot;]
DR_cat --&amp;gt; DR[&amp;quot;AIPW&amp;quot;]
style Root fill:#141413,stroke:#141413,color:#fff
style OM fill:#6a9bcc,stroke:#141413,color:#fff
style TM fill:#d97757,stroke:#141413,color:#fff
style DR_cat fill:#00d4c8,stroke:#141413,color:#fff
style RA fill:#6a9bcc,stroke:#141413,color:#fff
style IPW fill:#d97757,stroke:#141413,color:#fff
style PSS fill:#d97757,stroke:#141413,color:#fff
style PSM fill:#d97757,stroke:#141413,color:#fff
style DR fill:#00d4c8,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;p>Understanding these paradigms helps clarify why different methods can give somewhat different estimates and why comparing across paradigms is a powerful robustness check. The key trade-offs are:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>What each paradigm models&lt;/strong>: Outcome modeling specifies how covariates relate to earnings ($E[Y \mid X, T]$). Treatment modeling specifies how covariates relate to treatment assignment ($P(T \mid X)$) &amp;mdash; all three PS methods use this same propensity score but differ in how they apply it. Doubly robust specifies both models simultaneously.&lt;/li>
&lt;li>&lt;strong>What each paradigm assumes&lt;/strong>: Regression adjustment requires the outcome model to be correctly specified. All three propensity score methods (IPW, stratification, matching) require the propensity score model to be correctly specified. Doubly robust only requires &lt;em>one&lt;/em> of the two to be correct.&lt;/li>
&lt;li>&lt;strong>Bias-variance characteristics&lt;/strong>: Regression adjustment tends to be low-variance but can be biased if the outcome-covariate relationship is nonlinear. IPW can have high variance when propensity scores are extreme (near 0 or 1). Stratification and matching use the propensity score more conservatively &amp;mdash; by grouping or pairing rather than directly reweighting &amp;mdash; which can reduce variance relative to IPW. Doubly robust balances both concerns but is more complex to implement.&lt;/li>
&lt;/ul>
&lt;p>The three treatment modeling methods differ in &lt;em>how&lt;/em> they use the propensity score to create balanced comparisons:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>IPW&lt;/strong> reweights every observation by the inverse of its propensity score, creating a pseudo-population where treatment is independent of covariates. It uses the full sample but can be unstable when propensity scores are near 0 or 1.&lt;/li>
&lt;li>&lt;strong>PS Stratification&lt;/strong> divides observations into groups (strata) with similar propensity scores, then computes simple mean differences within each stratum. By comparing treated and control units within the same stratum, it approximates a block-randomized experiment.&lt;/li>
&lt;li>&lt;strong>PS Matching&lt;/strong> pairs each treated unit with the control unit that has the most similar propensity score, then computes mean differences within matched pairs. It discards unmatched observations, focusing on the closest comparisons at the cost of reduced sample size.&lt;/li>
&lt;/ul>
&lt;p>None of these methods model the outcome &amp;mdash; they all achieve confounding adjustment purely through the propensity score. If outcome modeling and treatment modeling agree, we can be more confident that neither model is badly misspecified.&lt;/p>
&lt;h3 id="method-1-regression-adjustment">Method 1: Regression Adjustment&lt;/h3>
&lt;p>Regression adjustment is grounded in the &lt;strong>potential outcomes framework&lt;/strong>: each individual has two potential outcomes &amp;mdash; $Y(1)$ if treated and $Y(0)$ if not &amp;mdash; and the causal effect is their difference. Since we only observe one outcome per person, regression adjustment estimates both potential outcomes by modeling $E[Y \mid X, T]$, the conditional expectation of the outcome given covariates and treatment status. The treatment effect is the coefficient on the treatment indicator, which captures the difference in expected outcomes between treated and control units &lt;strong>at the same covariate values&lt;/strong> &amp;mdash; effectively comparing apples to apples.&lt;/p>
&lt;p>The key assumption is that the outcome model must be &lt;strong>correctly specified&lt;/strong>. If the true relationship between covariates and the outcome is nonlinear or includes interactions, a simple linear model will produce biased estimates. In econometrics, this approach is closely related to the &lt;strong>&lt;a href="https://en.wikipedia.org/wiki/Frisch%E2%80%93Waugh%E2%80%93Lovell_theorem" target="_blank" rel="noopener">Frisch-Waugh-Lovell theorem&lt;/a>&lt;/strong>, which shows that the treatment coefficient in a multiple regression is identical to what you would get by first partialing out the covariates from both the treatment and the outcome, then regressing the residuals on each other. This makes regression adjustment the simplest and most transparent baseline estimator.&lt;/p>
&lt;p>We use DoWhy&amp;rsquo;s &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_backdoor.html" target="_blank" rel="noopener">&lt;code>backdoor.linear_regression&lt;/code>&lt;/a> method:&lt;/p>
&lt;pre>&lt;code class="language-python">estimate_ra = model.estimate_effect(
identified_estimand,
method_name=&amp;quot;backdoor.linear_regression&amp;quot;,
confidence_intervals=True,
)
print(f&amp;quot;Estimated ATE (Regression Adjustment): ${estimate_ra.value:,.2f}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Estimated ATE (Regression Adjustment): $1,676.34
&lt;/code>&lt;/pre>
&lt;p>The regression adjustment estimate is \$1,676, slightly lower than the naive difference of \$1,794. The reduction from \$1,794 to \$1,676 reflects the covariate adjustment &amp;mdash; by accounting for finite-sample imbalances in age, education, race, and prior earnings, the estimated treatment effect shrinks by about \$118. In this randomized setting, the adjustment primarily improves precision rather than removing bias, but the same technique is essential in observational studies where confounding is a genuine concern.&lt;/p>
&lt;h3 id="method-2-inverse-probability-weighting-ipw">Method 2: Inverse Probability Weighting (IPW)&lt;/h3>
&lt;p>IPW takes a fundamentally different approach from regression adjustment. Instead of modeling the outcome, it models the &lt;strong>treatment assignment mechanism&lt;/strong>. The central concept is the &lt;strong>propensity score&lt;/strong>, $e(X) = P(T = 1 \mid X)$ &amp;mdash; the probability that a unit receives treatment given its observed covariates. A person with a propensity score of 0.8 has an 80% chance of being treated based on their characteristics; a person with a score of 0.2 has only a 20% chance.&lt;/p>
&lt;p>The key intuition behind inverse weighting is that &lt;strong>units who are unlikely to receive the treatment they actually received carry more information&lt;/strong> about the causal effect. Consider a treated individual with a low propensity score (say 0.1) &amp;mdash; this person was unlikely to be treated, yet was treated. Their outcome is especially informative because they are &amp;ldquo;similar&amp;rdquo; to the control group in all observable respects. IPW upweights such surprising cases by assigning them a weight of $1/e(X) = 10$, while a treated person with $e(X) = 0.9$ receives a weight of only $1/0.9 \approx 1.1$. This reweighting creates a &amp;ldquo;pseudo-population&amp;rdquo; in which treatment assignment is independent of the observed confounders, mimicking what a randomized experiment would look like.&lt;/p>
&lt;p>A critical contrast with regression adjustment: IPW makes &lt;strong>no assumptions about how covariates relate to the outcome&lt;/strong> &amp;mdash; it only requires that the propensity score model is correctly specified. However, IPW has a key vulnerability: when propensity scores are extreme (near 0 or 1), the inverse weights become very large, producing &lt;strong>unstable estimates with high variance&lt;/strong>. This is why practitioners often use weight trimming or stabilized weights in practice.&lt;/p>
&lt;p>The IPW estimator is:&lt;/p>
&lt;p>$$\hat{\tau}_{IPW} = \frac{1}{n} \sum_{i=1}^{n} \left[ \frac{T_i Y_i}{\hat{e}(X_i)} - \frac{(1 - T_i) Y_i}{1 - \hat{e}(X_i)} \right]$$&lt;/p>
&lt;p>where $\hat{e}(X_i)$ is the estimated propensity score for individual $i$.&lt;/p>
&lt;p>We use DoWhy&amp;rsquo;s &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_backdoor.html" target="_blank" rel="noopener">&lt;code>backdoor.propensity_score_weighting&lt;/code>&lt;/a> method, which implements the &lt;a href="https://doi.org/10.1080/01621459.1952.10483446" target="_blank" rel="noopener">Horvitz-Thompson&lt;/a> inverse probability estimator:&lt;/p>
&lt;pre>&lt;code class="language-python">estimate_ipw = model.estimate_effect(
identified_estimand,
method_name=&amp;quot;backdoor.propensity_score_weighting&amp;quot;,
method_params={&amp;quot;weighting_scheme&amp;quot;: &amp;quot;ips_weight&amp;quot;},
)
print(f&amp;quot;Estimated ATE (IPW): ${estimate_ipw.value:,.2f}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Estimated ATE (IPW): $1,559.47
&lt;/code>&lt;/pre>
&lt;p>The IPW estimate of \$1,559 is the lowest among all methods. IPW is sensitive to extreme propensity scores &amp;mdash; when some individuals have very high or very low probabilities of treatment, their weights become large and can dominate the estimate. In this dataset, the estimated propensity scores are reasonably well-behaved (the NSW was a randomized experiment), so the IPW estimate remains in the plausible range. The difference from the regression adjustment (\$1,676 vs \$1,559) reflects the fact that IPW makes no assumptions about the outcome model, relying entirely on correct specification of the propensity score model.&lt;/p>
&lt;h3 id="method-3-doubly-robust-aipw">Method 3: Doubly Robust (AIPW)&lt;/h3>
&lt;p>The &lt;strong>doubly robust&lt;/strong> estimator &amp;mdash; also called &lt;strong>Augmented Inverse Probability Weighting (AIPW)&lt;/strong> &amp;mdash; combines both regression adjustment and IPW into a single estimator. The key advantage is that the estimate is consistent if &lt;em>either&lt;/em> the outcome model &lt;em>or&lt;/em> the propensity score model is correctly specified (hence &amp;ldquo;doubly robust&amp;rdquo;). This provides an important safeguard against model misspecification.&lt;/p>
&lt;p>The intuition is straightforward: AIPW starts with the regression adjustment estimate ($\hat{\mu}_1(X) - \hat{\mu}_0(X)$, the difference in predicted outcomes under treatment and control) and then &lt;strong>adds a correction term&lt;/strong> based on the IPW-weighted prediction errors. If the outcome model is perfectly specified, the prediction errors $Y - \hat{\mu}(X)$ are pure noise and the correction averages to zero &amp;mdash; the regression adjustment alone does the work. If the outcome model is misspecified but the propensity score model is correct, the IPW-weighted correction term exactly compensates for the bias in the outcome predictions. This is why the estimator only needs &lt;strong>one&lt;/strong> of the two models to be correct &amp;mdash; whichever model is right &amp;ldquo;rescues&amp;rdquo; the other.&lt;/p>
&lt;p>Beyond its robustness property, AIPW achieves the &lt;strong>semiparametric efficiency bound&lt;/strong> when both models are correctly specified, meaning no other estimator that makes the same assumptions can have lower variance. This makes it a natural default choice in modern causal inference.&lt;/p>
&lt;p>The AIPW estimator is:&lt;/p>
&lt;p>$$\hat{\tau}_{DR} = \frac{1}{n} \sum_{i=1}^{n} \left[ \hat{\mu}_1(X_i) - \hat{\mu}_0(X_i) + \frac{T_i (Y_i - \hat{\mu}_1(X_i))}{\hat{e}(X_i)} - \frac{(1 - T_i)(Y_i - \hat{\mu}_0(X_i))}{1 - \hat{e}(X_i)} \right]$$&lt;/p>
&lt;p>where $\hat{\mu}_1(X_i)$ and $\hat{\mu}_0(X_i)$ are the predicted outcomes under treatment and control, and $\hat{e}(X_i)$ is the propensity score.&lt;/p>
&lt;p>We implement the AIPW estimator manually rather than using DoWhy&amp;rsquo;s built-in &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/index.html" target="_blank" rel="noopener">&lt;code>backdoor.doubly_robust&lt;/code>&lt;/a> method, which has a known compatibility issue with recent scikit-learn versions. The manual implementation uses &lt;a href="https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html" target="_blank" rel="noopener">&lt;code>LogisticRegression&lt;/code>&lt;/a> for the propensity score model and &lt;a href="https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html" target="_blank" rel="noopener">&lt;code>LinearRegression&lt;/code>&lt;/a> for the outcome model, making the estimator&amp;rsquo;s two-component structure fully transparent.&lt;/p>
&lt;pre>&lt;code class="language-python"># Doubly Robust (AIPW) — manual implementation
ps_model = LogisticRegression(max_iter=1000, random_state=42)
ps_model.fit(df[COVARIATES], df[TREATMENT])
ps = ps_model.predict_proba(df[COVARIATES])[:, 1]
outcome_model_1 = SklearnLR().fit(df[df[TREATMENT] == 1][COVARIATES], df[df[TREATMENT] == 1][OUTCOME])
outcome_model_0 = SklearnLR().fit(df[df[TREATMENT] == 0][COVARIATES], df[df[TREATMENT] == 0][OUTCOME])
mu1 = outcome_model_1.predict(df[COVARIATES])
mu0 = outcome_model_0.predict(df[COVARIATES])
T = df[TREATMENT].values
Y = df[OUTCOME].values
dr_ate = np.mean(
(mu1 - mu0)
+ T * (Y - mu1) / ps
- (1 - T) * (Y - mu0) / (1 - ps)
)
print(f&amp;quot;Estimated ATE (Doubly Robust): ${dr_ate:,.2f}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Estimated ATE (Doubly Robust): $1,620.04
&lt;/code>&lt;/pre>
&lt;p>The doubly robust estimate of \$1,620 falls between the regression adjustment (\$1,676) and IPW (\$1,559) estimates. This reflects how the AIPW estimator works: it uses the outcome model as its primary estimate and adds an IPW-weighted correction based on the prediction residuals. The fact that it is close to both individual estimates suggests that neither model is severely misspecified. In practice, the doubly robust estimator is often the preferred choice because it provides insurance against misspecification of either component model.&lt;/p>
&lt;h3 id="method-4-propensity-score-stratification">Method 4: Propensity Score Stratification&lt;/h3>
&lt;p>Propensity score stratification builds on a powerful result from &lt;a href="https://doi.org/10.1093/biomet/70.1.41" target="_blank" rel="noopener">Rosenbaum and Rubin (1983)&lt;/a>: &lt;strong>conditioning on the scalar propensity score is sufficient to remove all confounding from observed covariates&lt;/strong>, even though the score compresses multiple covariates into a single number. This means that within a group of individuals who all have similar propensity scores, treatment assignment is effectively random with respect to the observed confounders &amp;mdash; just as in a randomized experiment.&lt;/p>
&lt;p>Stratification is a &lt;strong>discrete approximation&lt;/strong> to this idea. Instead of conditioning on the exact propensity score (which would require infinite data), we bin observations into a small number of strata &amp;mdash; typically 5 quintiles. Within each stratum, treated and control individuals have similar propensity scores and are therefore more comparable, so the within-stratum treatment effect is less confounded. The overall ATE is a weighted average of these stratum-specific effects. A classic result from &lt;a href="https://doi.org/10.2307/2528036" target="_blank" rel="noopener">Cochran (1968)&lt;/a> shows that &lt;strong>5 strata typically remove over 90% of the bias&lt;/strong> from observed confounders, making this a surprisingly effective yet simple approach.&lt;/p>
&lt;p>There is a practical trade-off in choosing the number of strata: more strata produce finer covariate balance within each group, reducing bias, but also leave fewer observations per stratum, increasing variance. Five strata is the conventional choice, balancing these considerations well.&lt;/p>
&lt;p>We use DoWhy&amp;rsquo;s &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_backdoor.html" target="_blank" rel="noopener">&lt;code>backdoor.propensity_score_stratification&lt;/code>&lt;/a> method:&lt;/p>
&lt;pre>&lt;code class="language-python">estimate_ps_strat = model.estimate_effect(
identified_estimand,
method_name=&amp;quot;backdoor.propensity_score_stratification&amp;quot;,
method_params={&amp;quot;num_strata&amp;quot;: 5, &amp;quot;clipping_threshold&amp;quot;: 5},
)
print(f&amp;quot;Estimated ATE (PS Stratification): ${estimate_ps_strat.value:,.2f}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Estimated ATE (PS Stratification): $1,617.07
&lt;/code>&lt;/pre>
&lt;p>Propensity score stratification with 5 strata estimates the ATE at \$1,617, very close to the doubly robust estimate (\$1,620). The stratification approach is more flexible than regression adjustment because it does not impose a functional form on the outcome-covariate relationship. The estimate is in the same ballpark as the other adjusted results, which is reassuring &amp;mdash; multiple methods agree that the training effect is in the \$1,550&amp;ndash;\$1,700 range.&lt;/p>
&lt;h3 id="method-5-propensity-score-matching">Method 5: Propensity Score Matching&lt;/h3>
&lt;p>Propensity score matching constructs a comparison group by finding, for each treated individual, the control individual(s) with the most similar propensity score. The treatment effect is then estimated by comparing outcomes within these matched pairs. This is conceptually the most intuitive approach &amp;mdash; it directly mimics what we would see if we could compare individuals who are identical except for their treatment status.&lt;/p>
&lt;p>An important subtlety is that matching typically &lt;strong>discards unmatched control units&lt;/strong> &amp;mdash; those with no treated counterpart nearby in propensity score space. This means the estimand shifts from the &lt;strong>Average Treatment Effect (ATE)&lt;/strong> toward the &lt;strong>Average Treatment Effect on the Treated (ATT)&lt;/strong>, which answers a slightly different question: &amp;ldquo;What was the effect of treatment for those who were actually treated?&amp;rdquo; rather than &amp;ldquo;What would the effect be if we treated everyone?&amp;rdquo;&lt;/p>
&lt;p>Several practical choices affect matching quality. &lt;strong>With-replacement&lt;/strong> matching allows each control to be matched to multiple treated units, reducing bias but increasing variance. &lt;strong>1:k matching&lt;/strong> uses $k$ nearest controls per treated unit, averaging out noise but potentially introducing worse matches. &lt;strong>Caliper restrictions&lt;/strong> discard matches where the propensity score difference exceeds a threshold, preventing poor matches at the cost of losing some treated observations. These choices create a fundamental &lt;strong>bias-variance trade-off&lt;/strong>: tighter matching criteria reduce bias from imperfect comparisons but may discard many observations, increasing the variance of the estimate.&lt;/p>
&lt;p>We use DoWhy&amp;rsquo;s &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_backdoor.html" target="_blank" rel="noopener">&lt;code>backdoor.propensity_score_matching&lt;/code>&lt;/a> method:&lt;/p>
&lt;pre>&lt;code class="language-python">estimate_ps_match = model.estimate_effect(
identified_estimand,
method_name=&amp;quot;backdoor.propensity_score_matching&amp;quot;,
)
print(f&amp;quot;Estimated ATE (PS Matching): ${estimate_ps_match.value:,.2f}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Estimated ATE (PS Matching): $1,735.69
&lt;/code>&lt;/pre>
&lt;p>Propensity score matching estimates the effect at \$1,736, the highest of the five adjusted estimates and closest to the naive difference. Matching tends to give slightly different results because it uses only the closest comparisons rather than the full sample. As noted above, this estimate is closer to the &lt;strong>ATT&lt;/strong> than the ATE, so it answers a slightly different question than the other four methods &amp;mdash; readers should keep this distinction in mind when comparing across estimators. The fact that all five methods produce estimates between \$1,559 and \$1,736 provides strong evidence that the treatment effect is real and robust to the choice of estimation method.&lt;/p>
&lt;h2 id="step-4-refute-----test-robustness">Step 4: Refute &amp;mdash; Test robustness&lt;/h2>
&lt;p>The final and perhaps most valuable step in DoWhy&amp;rsquo;s framework is &lt;strong>refutation&lt;/strong> &amp;mdash; systematically testing whether the estimated causal effect is robust to violations of our assumptions. DoWhy&amp;rsquo;s &lt;a href="https://www.pywhy.org/dowhy/v0.11.1/dowhy.html#dowhy.causal_model.CausalModel.refute_estimate" target="_blank" rel="noopener">&lt;code>refute_estimate()&lt;/code>&lt;/a> method provides several built-in refutation tests, each probing a different potential weakness.&lt;/p>
&lt;h3 id="why-refutation-matters">Why refutation matters&lt;/h3>
&lt;p>Most causal inference workflows stop after estimation: you run a regression, get a coefficient, and report it as the causal effect. DoWhy&amp;rsquo;s refutation step is its key innovation &amp;mdash; it provides &lt;strong>automated falsification tests&lt;/strong> that probe whether the estimate could be an artifact of the model, the data, or violated assumptions. This is the causal inference equivalent of &amp;ldquo;stress testing&amp;rdquo;: if the estimate survives multiple attempts to break it, we can be more confident that it reflects a genuine causal relationship.&lt;/p>
&lt;p>DoWhy&amp;rsquo;s refutation tests fall into three categories, each targeting a different potential weakness:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Placebo tests&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;If the treatment doesn&amp;rsquo;t matter, does the effect disappear?&amp;quot;&lt;/em> These tests replace the real treatment with a fake (randomly permuted) treatment. If the estimated effect drops to near zero, the original result is tied to the actual treatment rather than being a statistical artifact of the model or data structure.&lt;/li>
&lt;li>&lt;strong>Sensitivity tests&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;If we missed a confounder, does the estimate change?&amp;quot;&lt;/em> These tests add a randomly generated variable as an additional confounder. If the estimate barely changes, it suggests the result is not fragile &amp;mdash; adding one more covariate does not destabilize it. This provides indirect evidence (though not proof) that unobserved confounders may not be a major concern.&lt;/li>
&lt;li>&lt;strong>Stability tests&lt;/strong> &amp;mdash; &lt;em>&amp;ldquo;If we use different data, does the estimate hold?&amp;quot;&lt;/em> These tests re-estimate the effect on random subsets of the data. If the estimate fluctuates wildly, it may depend on a few influential observations rather than reflecting a stable population-level effect.&lt;/li>
&lt;/ul>
&lt;p>An important caveat: &lt;strong>passing all refutation tests does not prove causation&lt;/strong>. The tests can only detect certain types of problems &amp;mdash; they cannot rule out every possible source of bias. However, &lt;strong>failing any test is a strong signal that something is wrong&lt;/strong> and warrants further investigation before drawing causal conclusions.&lt;/p>
&lt;h3 id="placebo-treatment-test">Placebo Treatment Test&lt;/h3>
&lt;p>The &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/refuting_causal_estimates/refuting_effect_estimates/placebo_treatment.html" target="_blank" rel="noopener">placebo test&lt;/a> replaces the actual treatment with a randomly permuted version. If our estimate is truly capturing a causal effect, this fake treatment should produce an effect near zero. A large p-value indicates that the placebo effect is not significantly different from zero, confirming that the real treatment drives the original estimate.&lt;/p>
&lt;pre>&lt;code class="language-python">refute_placebo = model.refute_estimate(
identified_estimand,
estimate_ra,
method_name=&amp;quot;placebo_treatment_refuter&amp;quot;,
placebo_type=&amp;quot;permute&amp;quot;,
num_simulations=100,
)
print(refute_placebo)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Refute: Use a Placebo Treatment
Estimated effect:1676.3426437675835
New effect:61.821946542496946
p value:0.92
&lt;/code>&lt;/pre>
&lt;p>The placebo treatment test produces a new effect of approximately \$62, which is close to zero and dramatically smaller than the original estimate of \$1,676. The high p-value (0.92) indicates that the original estimate is well above what we would expect from a random treatment assignment. This is strong evidence that the estimated effect is not an artifact of the model or data structure.&lt;/p>
&lt;h3 id="random-common-cause-test">Random Common Cause Test&lt;/h3>
&lt;p>The &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/refuting_causal_estimates/refuting_effect_estimates/random_common_cause.html" target="_blank" rel="noopener">random common cause test&lt;/a> adds a randomly generated confounder to the model and checks whether the estimate changes. If our model is correctly specified and the estimate is robust, adding a random variable should not significantly alter the result.&lt;/p>
&lt;pre>&lt;code class="language-python">refute_random = model.refute_estimate(
identified_estimand,
estimate_ra,
method_name=&amp;quot;random_common_cause&amp;quot;,
num_simulations=100,
)
print(refute_random)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Refute: Add a random common cause
Estimated effect:1676.3426437675835
New effect:1675.606781672203
p value:0.9
&lt;/code>&lt;/pre>
&lt;p>Adding a random common cause barely changes the estimate: from \$1,676 to \$1,676 &amp;mdash; a difference of less than \$1. The high p-value (0.90) confirms that the original estimate is stable when an additional (irrelevant) confounder is introduced. This suggests that the model is not overly sensitive to the specific set of confounders included.&lt;/p>
&lt;h3 id="data-subset-test">Data Subset Test&lt;/h3>
&lt;p>The &lt;a href="https://www.pywhy.org/dowhy/v0.14/user_guide/refuting_causal_estimates/refuting_effect_estimates/data_subsample.html" target="_blank" rel="noopener">data subset test&lt;/a> re-estimates the effect on random 80% subsamples of the data. If the estimate is robust, it should remain similar across different subsets. Large fluctuations would suggest that the result depends on a few influential observations.&lt;/p>
&lt;pre>&lt;code class="language-python">refute_subset = model.refute_estimate(
identified_estimand,
estimate_ra,
method_name=&amp;quot;data_subset_refuter&amp;quot;,
subset_fraction=0.8,
num_simulations=100,
)
print(refute_subset)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>Refute: Use a subset of data
Estimated effect:1676.3426437675835
New effect:1727.583871150809
p value:0.8
&lt;/code>&lt;/pre>
&lt;p>The data subset refuter produces a mean effect of \$1,728 across 100 random subsamples, close to the full-sample estimate of \$1,676. The high p-value (0.80) indicates that the estimate is stable across subsets and does not depend on a handful of outlier observations. The slight increase in the subsample estimate (\$1,728 vs \$1,676) reflects normal sampling variability.&lt;/p>
&lt;h2 id="comparing-all-estimates">Comparing all estimates&lt;/h2>
&lt;p>To visualize how all estimation approaches compare, we plot the ATE estimates side by side. Consistent estimates across different methods strengthen confidence in the causal conclusion.&lt;/p>
&lt;pre>&lt;code class="language-python">fig, ax = plt.subplots(figsize=(9, 6))
methods = [&amp;quot;Naive\n(Diff. in Means)&amp;quot;, &amp;quot;Regression\nAdjustment&amp;quot;, &amp;quot;IPW&amp;quot;,
&amp;quot;Doubly Robust\n(AIPW)&amp;quot;, &amp;quot;PS\nStratification&amp;quot;, &amp;quot;PS\nMatching&amp;quot;]
estimates = [naive_ate, estimate_ra.value, estimate_ipw.value,
dr_ate, estimate_ps_strat.value, estimate_ps_match.value]
colors = [&amp;quot;#999999&amp;quot;, &amp;quot;#6a9bcc&amp;quot;, &amp;quot;#d97757&amp;quot;, &amp;quot;#00d4c8&amp;quot;, &amp;quot;#e8956a&amp;quot;, &amp;quot;#c4623d&amp;quot;]
bars = ax.barh(methods, estimates, color=colors, edgecolor=&amp;quot;white&amp;quot;, height=0.6)
for bar, val in zip(bars, estimates):
ax.text(val + 50, bar.get_y() + bar.get_height() / 2,
f&amp;quot;${val:,.0f}&amp;quot;, va=&amp;quot;center&amp;quot;, fontsize=10, color=&amp;quot;#141413&amp;quot;)
ax.axvline(0, color=&amp;quot;black&amp;quot;, linewidth=0.5, linestyle=&amp;quot;--&amp;quot;)
ax.set_xlabel(&amp;quot;Estimated Average Treatment Effect (USD)&amp;quot;)
ax.set_title(&amp;quot;Causal Effect Estimates: NSW Job Training on 1978 Earnings&amp;quot;)
plt.savefig(&amp;quot;dowhy_estimate_comparison.png&amp;quot;, dpi=300, bbox_inches=&amp;quot;tight&amp;quot;)
plt.show()
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="dowhy_estimate_comparison.png" alt="Comparison of ATE estimates across six methods.">&lt;/p>
&lt;p>All six methods produce positive estimates between \$1,559 and \$1,794, indicating that the NSW job training program increased participants' 1978 earnings by roughly \$1,550&amp;ndash;\$1,800. The five adjusted methods cluster between \$1,559 and \$1,736, suggesting that about \$58&amp;ndash;\$235 of the naive estimate was due to finite-sample covariate imbalances rather than the treatment. The convergence across fundamentally different estimation strategies &amp;mdash; outcome modeling (regression adjustment), treatment modeling (IPW, stratification, matching), and doubly robust (AIPW) &amp;mdash; is strong evidence that the effect is real.&lt;/p>
&lt;h2 id="summary-table">Summary table&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Estimated ATE&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Naive (Difference in Means)&lt;/td>
&lt;td>\$1,794&lt;/td>
&lt;td>No covariate adjustment&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Regression Adjustment&lt;/td>
&lt;td>\$1,676&lt;/td>
&lt;td>Models outcome, assumes linearity&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IPW&lt;/td>
&lt;td>\$1,559&lt;/td>
&lt;td>Models treatment assignment&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Doubly Robust (AIPW)&lt;/td>
&lt;td>\$1,620&lt;/td>
&lt;td>Models both outcome and treatment&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Propensity Score Stratification&lt;/td>
&lt;td>\$1,617&lt;/td>
&lt;td>5 strata, flexible&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Propensity Score Matching&lt;/td>
&lt;td>\$1,736&lt;/td>
&lt;td>Nearest-neighbor matching (closer to ATT)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Refutation Test&lt;/th>
&lt;th>New Effect&lt;/th>
&lt;th>p-value&lt;/th>
&lt;th>Interpretation&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Placebo Treatment&lt;/td>
&lt;td>\$62&lt;/td>
&lt;td>0.92&lt;/td>
&lt;td>Effect vanishes with fake treatment&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Random Common Cause&lt;/td>
&lt;td>\$1,676&lt;/td>
&lt;td>0.90&lt;/td>
&lt;td>Stable with added confounder&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Data Subset (80%)&lt;/td>
&lt;td>\$1,728&lt;/td>
&lt;td>0.80&lt;/td>
&lt;td>Stable across subsamples&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The summary confirms a consistent causal effect across methods: the NSW job training program increased 1978 earnings by approximately \$1,550&amp;ndash;\$1,800. All five adjusted methods and all three refutation tests support the validity of the estimate. The placebo test is particularly convincing &amp;mdash; when the real treatment is replaced by random noise, the effect drops from \$1,676 to just \$62, confirming that the observed effect is tied to the actual treatment and not a statistical artifact. The doubly robust estimate (\$1,620) provides the most credible point estimate because it is consistent under misspecification of either the outcome model or the propensity score model.&lt;/p>
&lt;h2 id="discussion">Discussion&lt;/h2>
&lt;p>The Lalonde dataset provides a compelling case study for DoWhy&amp;rsquo;s four-step framework. Each step serves a distinct purpose: the &lt;strong>Model&lt;/strong> step forces us to articulate our causal assumptions as a graph, the &lt;strong>Identify&lt;/strong> step uses graph theory to determine the correct adjustment formula, the &lt;strong>Estimate&lt;/strong> step applies statistical methods to compute the effect, and the &lt;strong>Refute&lt;/strong> step probes whether the result withstands scrutiny.&lt;/p>
&lt;p>The estimated ATE ranges from \$1,559 (IPW) to \$1,736 (PS matching), with the doubly robust estimate at \$1,620 providing a credible middle ground. On a base of \$4,555 for the control group, this represents roughly a 34&amp;ndash;38% increase in earnings &amp;mdash; a substantial effect for a disadvantaged population with very low baseline earnings. The three estimation paradigms &amp;mdash; outcome modeling (regression adjustment), treatment modeling (IPW, stratification, matching), and doubly robust (AIPW) &amp;mdash; each bring different strengths, and their convergence strengthens the causal conclusion.&lt;/p>
&lt;p>The key strength of DoWhy over ad-hoc statistical approaches is transparency. The causal graph makes assumptions visible and debatable. The identification step formally checks whether the effect is estimable. Multiple estimation methods let us assess robustness. And refutation tests provide automated sanity checks that would otherwise require expert judgment.&lt;/p>
&lt;h2 id="limitations-and-next-steps">Limitations and next steps&lt;/h2>
&lt;p>This analysis demonstrates DoWhy&amp;rsquo;s workflow on a well-understood dataset, but several limitations apply:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Small sample size&lt;/strong>: With only 445 observations, estimates have high variance and the propensity score methods may suffer from poor overlap in some regions of the covariate space&lt;/li>
&lt;li>&lt;strong>Unconfoundedness assumption&lt;/strong>: The backdoor criterion requires that all confounders are observed. If there are unmeasured factors affecting both training enrollment and earnings, our estimates would be biased&lt;/li>
&lt;li>&lt;strong>Linear outcome model&lt;/strong>: The regression adjustment and doubly robust estimates assume a linear relationship between covariates and earnings, which may be too restrictive for the highly skewed outcome distribution&lt;/li>
&lt;li>&lt;strong>Experimental data&lt;/strong>: The NSW was a randomized experiment, making it the easiest setting for causal inference. DoWhy&amp;rsquo;s advantages are more pronounced in observational studies where confounding is more severe&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Next steps&lt;/strong> could include:&lt;/p>
&lt;ul>
&lt;li>Apply DoWhy to an observational version of the Lalonde dataset (e.g., the PSID or CPS comparison groups) where confounding is much stronger&lt;/li>
&lt;li>Explore DoWhy&amp;rsquo;s instrumental variable and front-door estimators for settings where the backdoor criterion fails&lt;/li>
&lt;li>Investigate heterogeneous treatment effects &amp;mdash; does training help some subgroups more than others?&lt;/li>
&lt;li>Use nonparametric outcome models (e.g., random forests) in the doubly robust estimator for more flexible modeling&lt;/li>
&lt;li>Compare DoWhy&amp;rsquo;s estimates with Double Machine Learning (DoubleML) for a side-by-side comparison of frameworks&lt;/li>
&lt;/ul>
&lt;h2 id="takeaways">Takeaways&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>DoWhy&amp;rsquo;s four-step workflow&lt;/strong> (Model, Identify, Estimate, Refute) makes causal assumptions explicit and testable, rather than hiding them inside a black-box estimator.&lt;/li>
&lt;li>&lt;strong>The NSW job training program increased 1978 earnings by approximately \$1,550&amp;ndash;\$1,800&lt;/strong>, a 34&amp;ndash;38% gain over the control group mean of \$4,555.&lt;/li>
&lt;li>&lt;strong>Five estimation methods&lt;/strong> &amp;mdash; regression adjustment, IPW, doubly robust, PS stratification, and PS matching &amp;mdash; all produce positive, consistent estimates, strengthening confidence in the causal conclusion.&lt;/li>
&lt;li>&lt;strong>The doubly robust (AIPW) estimator&lt;/strong> (\$1,620) is the most credible single estimate because it remains consistent if either the outcome model or the propensity score model is misspecified.&lt;/li>
&lt;li>&lt;strong>IPW and regression adjustment represent two complementary paradigms&lt;/strong>: modeling treatment assignment (\$1,559) vs. modeling the outcome (\$1,676). Their divergence quantifies sensitivity to modeling choices.&lt;/li>
&lt;li>&lt;strong>Refutation tests confirm robustness&lt;/strong> &amp;mdash; the placebo test reduced the effect from \$1,676 to just \$62, ruling out statistical artifacts.&lt;/li>
&lt;li>&lt;strong>Causal graphs encode domain knowledge as testable assumptions&lt;/strong>; the backdoor criterion then determines which variables must be conditioned on for valid causal estimation.&lt;/li>
&lt;li>&lt;strong>Next step&lt;/strong>: apply DoWhy to an observational comparison group (e.g., PSID or CPS) where confounding is stronger and the choice of estimator matters more.&lt;/li>
&lt;/ul>
&lt;h2 id="exercises">Exercises&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Change the number of strata.&lt;/strong> Re-run the propensity score stratification with &lt;code>num_strata=10&lt;/code> and &lt;code>num_strata=20&lt;/code>. How does the ATE estimate change? What are the tradeoffs of using more vs. fewer strata with a sample of only 445 observations?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Add an additional refutation test.&lt;/strong> DoWhy supports a &lt;code>bootstrap_refuter&lt;/code> that re-estimates the effect on bootstrap samples. Implement this refuter and compare its results to the data subset refuter. Are the conclusions similar?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Estimate effects for subgroups.&lt;/strong> Split the dataset by &lt;code>black&lt;/code> (race indicator) and estimate the ATE separately for each subgroup using DoWhy. Does the job training program have a different effect for Black vs. non-Black participants? What might explain any differences you observe?&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="references">References&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://www.pywhy.org/dowhy/" target="_blank" rel="noopener">DoWhy &amp;mdash; Python Library for Causal Inference (PyWhy)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.jstor.org/stable/1806062" target="_blank" rel="noopener">LaLonde, R. (1986). Evaluating the Econometric Evaluations of Training Programs. American Economic Review, 76(4), 604&amp;ndash;620.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1080/01621459.1999.10473858" target="_blank" rel="noopener">Dehejia, R. &amp;amp; Wahba, S. (1999). Causal Effects in Nonexperimental Studies: Reevaluating the Evaluation of Training Programs. JASA, 94(448), 1053&amp;ndash;1062.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://arxiv.org/abs/2011.04216" target="_blank" rel="noopener">Sharma, A. &amp;amp; Kiciman, E. (2020). DoWhy: An End-to-End Library for Causal Inference. arXiv:2011.04216.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1080/01621459.1952.10483446" target="_blank" rel="noopener">Horvitz, D. G. &amp;amp; Thompson, D. J. (1952). A Generalization of Sampling Without Replacement from a Finite Universe. JASA, 47(260), 663&amp;ndash;685.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1080/01621459.1994.10476818" target="_blank" rel="noopener">Robins, J. M., Rotnitzky, A. &amp;amp; Zhao, L. P. (1994). Estimation of Regression Coefficients When Some Regressors Are Not Always Observed. JASA, 89(427), 846&amp;ndash;866.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1093/biomet/70.1.41" target="_blank" rel="noopener">Rosenbaum, P. R. &amp;amp; Rubin, D. B. (1983). The Central Role of the Propensity Score in Observational Studies for Causal Effects. Biometrika, 70(1), 41&amp;ndash;55.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.2307/2528036" target="_blank" rel="noopener">Cochran, W. G. (1968). The Effectiveness of Adjustment by Subclassification in Removing Bias in Observational Studies. Biometrics, 24(2), 295&amp;ndash;313.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://medium.com/@chrisjames.nita/causal-inference-with-python-introduction-to-dowhy-ff5799e48985" target="_blank" rel="noopener">Nita, C. J. Causal Inference with Python &amp;mdash; Introduction to DoWhy. Medium.&lt;/a>&lt;/li>
&lt;/ol>
&lt;h4 id="acknowledgements">Acknowledgements&lt;/h4>
&lt;p>AI tools (Claude Code, Gemini, NotebookLM) were used to make the contents of this post more accessible to students. Nevertheless, the content in this post may still have errors. Caution is needed when applying the contents of this post to true research projects.&lt;/p></description></item></channel></rss>