Instrumental Variables & Treatment Effects Skill
This skill covers IV/2SLS estimation and propensity score matching (PSM) for causal inference when treatment is endogenous. It helps identify valid instruments, run 2SLS, test instrument validity, and implement PSM.
When to Use IV vs PSM
| Method |
Use When |
| IV / 2SLS |
Treatment is endogenous; a valid instrument exists |
| PSM |
Selection on observables assumption is credible; rich covariate data |
| OLS + controls |
Selection on observables, limited instruments |
IV / 2SLS Framework
Conditions for a Valid Instrument Z for endogenous X
- Relevance: Cov(Z, X) ≠ 0 — Z must be correlated with the endogenous regressor
- Exclusion restriction: Cov(Z, ε) = 0 — Z affects Y only through X (cannot be tested directly)
- Independence: Z is as-good-as-randomly assigned (exogenous)
Two-Stage Least Squares Procedure
Stage 1: Regress endogenous X on instruments Z and exogenous controls W
- X̂ = γ₀ + γ₁Z + γ₂W + v
- Check F-statistic > 10 (Stock-Yogo rule of thumb); ideally > 16.4 (5% bias threshold)
Stage 2: Regress Y on predicted X̂ and controls W
- Y = β₀ + β₁X̂ + β₂W + ε
- SE must be corrected for the two-stage estimation (done automatically by software)
Quick Code Templates
# Python (linearmodels)
from linearmodels.iv import IV2SLS
# Formula: dependent ~ exogenous [endogenous ~ instruments]
model = IV2SLS.from_formula(
'y ~ 1 + w1 + w2 + [x_endog ~ z1 + z2]', data=df
)
result = model.fit(cov_type='robust')
print(result.summary)
# First-stage diagnostics
print(result.first_stage.diagnostics)
# Check: partial F-stat, Shea partial R²
# R (AER)
library(AER)
iv_model <- ivreg(y ~ x_endog + w1 + w2 | z1 + z2 + w1 + w2, data = df)
summary(iv_model, diagnostics = TRUE)
# Shows: weak instruments F-test, Wu-Hausman endogeneity test, Sargan overID test
* Stata
ivregress 2sls y w1 w2 (x_endog = z1 z2), robust first
estat firststage // First-stage diagnostics
estat endogenous // Wu-Hausman test
estat overid // Sargan-Hansen overidentification test
Key Diagnostic Tests
| Test |
Null Hypothesis |
Interpretation |
| First-stage F-stat |
Instruments are weak |
F > 10 → relevant instruments |
| Wu-Hausman |
X is exogenous (OLS consistent) |
p < 0.05 → endogeneity confirmed, use IV |
| Sargan-Hansen |
All instruments valid (overID only) |
p > 0.05 → instruments pass overID test |
| Anderson-Rubin |
Robust to weak instruments |
Use when F-stat is borderline |
Propensity Score Matching (PSM)
Assumptions
- Conditional independence (unconfoundedness): Treatment T ⊥ Y(0), Y(1) | X
- Common support (overlap): 0 < P(T=1|X) < 1 for all X
PSM Procedure
# Python
from sklearn.linear_model import LogisticRegression
import numpy as np
# Step 1: Estimate propensity scores
lr = LogisticRegression(max_iter=1000)
lr.fit(df[covariates], df['treatment'])
df['pscore'] = lr.predict_proba(df[covariates])[:, 1]
# Step 2: Check common support
import matplotlib.pyplot as plt
df.groupby('treatment')['pscore'].plot.hist(alpha=0.5, bins=30)
# Step 3: Match (nearest neighbor, 1:1 without replacement)
treated = df[df['treatment'] == 1].copy()
control = df[df['treatment'] == 0].copy()
from sklearn.neighbors import NearestNeighbors
nn = NearestNeighbors(n_neighbors=1)
nn.fit(control[['pscore']])
distances, indices = nn.kneighbors(treated[['pscore']])
matched_control = control.iloc[indices.flatten()].copy()
matched_df = pd.concat([treated, matched_control])
# Step 4: Estimate ATT
att = matched_df.groupby('treatment')['y'].mean().diff().iloc[-1]
print(f"ATT: {att:.4f}")
# R (MatchIt)
library(MatchIt)
match_out <- matchit(treatment ~ x1 + x2 + x3, data = df,
method = "nearest", ratio = 1, replace = FALSE)
summary(match_out)
# Covariate balance
plot(match_out, type = "jitter")
plot(summary(match_out))
# Estimate ATT
matched_data <- match.data(match_out)
att_model <- lm(y ~ treatment, data = matched_data, weights = weights)
coeftest(att_model, vcov = vcovCL(att_model, ~subclass))
* Stata (psmatch2 from SSC)
psmatch2 treatment x1 x2 x3, outcome(y) neighbor(1) common
pstest x1 x2 x3
Reporting IV Results
- Always show first-stage results with F-statistic
- Report OLS alongside IV to illustrate endogeneity bias direction
- State the exclusion restriction argument explicitly — this cannot be statistically tested
- Interpret LATE not ATE: IV estimates are local to compliers (those induced by instrument)
- Overidentification test: report Sargan p-value when instruments > endogenous regressors
For weak-instrument robust inference (Anderson-Rubin confidence sets, LIML), control function approach, shift-share (Bartik) instruments, judge/examiner designs, and sensitivity analysis for PSM, see references/iv-reference.md.
Common Pitfalls
- Using 2SLS with weak instruments without robust inference: When F < 10, use LIML or Anderson-Rubin confidence sets instead of 2SLS
- Not arguing for exclusion restriction: The exclusion restriction cannot be tested statistically — you must make a convincing argument
- Confusing LATE with ATE: IV estimates the local average treatment effect for compliers, not the population average
- Clustering SE at the wrong level in Bartik IV: With shift-share instruments, inference should account for the exposure shares structure
- Over-identifying without caution: Adding more instruments improves efficiency but only if all are valid — a significant Sargan test means at least one instrument is invalid
- Using PSM without checking common support: If treated and control propensity score distributions barely overlap, matching is unreliable
Related Skills & Commands
- ols-regression: Compare OLS and IV estimates to illustrate endogeneity bias
- did-analysis: DID is an alternative when you have a natural experiment but no instrument
- matching: For expanded coverage of PSM, CEM, and doubly-robust methods
- ml-causal: DML can handle IV with high-dimensional controls
- /diagnose: Run IV-specific diagnostics (first-stage F, Wu-Hausman, overidentification)
- /robustness: Compare 2SLS, LIML, OLS, and alternative instrument sets
1---2name: iv-estimation3description: Econometrics skill for instrumental variables and treatment effect estimation. Activates when the user asks about: "instrumental variables", "IV estimation", "2SLS", "two-stage least squares", "endogeneity", "weak instruments", "first stage", "Sargan test", "overidentification", "propensity score matching", "PSM", "average treatment effect", "ATT", "LATE", "local average treatment effect", "endogenous regressor", "instrument validity", "工具变量", "两阶段最小二乘", "内生性", "弱工具变量", "倾向得分匹配", "平均处理效应", "处理效应", "局部平均处理效应"4---56# Instrumental Variables & Treatment Effects Skill78This skill covers IV/2SLS estimation and propensity score matching (PSM) for causal inference when treatment is endogenous. It helps identify valid instruments, run 2SLS, test instrument validity, and implement PSM.910## When to Use IV vs PSM1112| Method | Use When |13|--------|----------|14| **IV / 2SLS** | Treatment is endogenous; a valid instrument exists |15| **PSM** | Selection on observables assumption is credible; rich covariate data |16| **OLS + controls** | Selection on observables, limited instruments |1718## IV / 2SLS Framework1920### Conditions for a Valid Instrument Z for endogenous X21221. **Relevance**: Cov(Z, X) ≠ 0 — Z must be correlated with the endogenous regressor232. **Exclusion restriction**: Cov(Z, ε) = 0 — Z affects Y only through X (cannot be tested directly)243. **Independence**: Z is as-good-as-randomly assigned (exogenous)2526### Two-Stage Least Squares Procedure2728**Stage 1**: Regress endogenous X on instruments Z and exogenous controls W29- X̂ = γ₀ + γ₁Z + γ₂W + v30- Check F-statistic > 10 (Stock-Yogo rule of thumb); ideally > 16.4 (5% bias threshold)3132**Stage 2**: Regress Y on predicted X̂ and controls W33- Y = β₀ + β₁X̂ + β₂W + ε34- SE must be corrected for the two-stage estimation (done automatically by software)3536### Quick Code Templates3738```python39# Python (linearmodels)40from linearmodels.iv import IV2SLS4142# Formula: dependent ~ exogenous [endogenous ~ instruments]43model = IV2SLS.from_formula(44 'y ~ 1 + w1 + w2 + [x_endog ~ z1 + z2]', data=df45)46result = model.fit(cov_type='robust')47print(result.summary)4849# First-stage diagnostics50print(result.first_stage.diagnostics)51# Check: partial F-stat, Shea partial R²52```5354```r55# R (AER)56library(AER)57iv_model <- ivreg(y ~ x_endog + w1 + w2 | z1 + z2 + w1 + w2, data = df)58summary(iv_model, diagnostics = TRUE)59# Shows: weak instruments F-test, Wu-Hausman endogeneity test, Sargan overID test60```6162```stata63* Stata64ivregress 2sls y w1 w2 (x_endog = z1 z2), robust first65estat firststage // First-stage diagnostics66estat endogenous // Wu-Hausman test67estat overid // Sargan-Hansen overidentification test68```6970## Key Diagnostic Tests7172| Test | Null Hypothesis | Interpretation |73|------|-----------------|----------------|74| **First-stage F-stat** | Instruments are weak | F > 10 → relevant instruments |75| **Wu-Hausman** | X is exogenous (OLS consistent) | p < 0.05 → endogeneity confirmed, use IV |76| **Sargan-Hansen** | All instruments valid (overID only) | p > 0.05 → instruments pass overID test |77| **Anderson-Rubin** | Robust to weak instruments | Use when F-stat is borderline |7879## Propensity Score Matching (PSM)8081### Assumptions821. **Conditional independence** (unconfoundedness): Treatment T ⊥ Y(0), Y(1) | X832. **Common support** (overlap): 0 < P(T=1|X) < 1 for all X8485### PSM Procedure8687```python88# Python89from sklearn.linear_model import LogisticRegression90import numpy as np9192# Step 1: Estimate propensity scores93lr = LogisticRegression(max_iter=1000)94lr.fit(df[covariates], df['treatment'])95df['pscore'] = lr.predict_proba(df[covariates])[:, 1]9697# Step 2: Check common support98import matplotlib.pyplot as plt99df.groupby('treatment')['pscore'].plot.hist(alpha=0.5, bins=30)100101# Step 3: Match (nearest neighbor, 1:1 without replacement)102treated = df[df['treatment'] == 1].copy()103control = df[df['treatment'] == 0].copy()104105from sklearn.neighbors import NearestNeighbors106nn = NearestNeighbors(n_neighbors=1)107nn.fit(control[['pscore']])108distances, indices = nn.kneighbors(treated[['pscore']])109110matched_control = control.iloc[indices.flatten()].copy()111matched_df = pd.concat([treated, matched_control])112113# Step 4: Estimate ATT114att = matched_df.groupby('treatment')['y'].mean().diff().iloc[-1]115print(f"ATT: {att:.4f}")116```117118```r119# R (MatchIt)120library(MatchIt)121match_out <- matchit(treatment ~ x1 + x2 + x3, data = df,122 method = "nearest", ratio = 1, replace = FALSE)123summary(match_out)124125# Covariate balance126plot(match_out, type = "jitter")127plot(summary(match_out))128129# Estimate ATT130matched_data <- match.data(match_out)131att_model <- lm(y ~ treatment, data = matched_data, weights = weights)132coeftest(att_model, vcov = vcovCL(att_model, ~subclass))133```134135```stata136* Stata (psmatch2 from SSC)137psmatch2 treatment x1 x2 x3, outcome(y) neighbor(1) common138pstest x1 x2 x3139```140141## Reporting IV Results1421431. **Always show first-stage results** with F-statistic1442. **Report OLS alongside IV** to illustrate endogeneity bias direction1453. **State the exclusion restriction** argument explicitly — this cannot be statistically tested1464. **Interpret LATE not ATE**: IV estimates are local to compliers (those induced by instrument)1475. **Overidentification test**: report Sargan p-value when instruments > endogenous regressors148149For weak-instrument robust inference (Anderson-Rubin confidence sets, LIML), control function approach, shift-share (Bartik) instruments, judge/examiner designs, and sensitivity analysis for PSM, see `references/iv-reference.md`.150151## Common Pitfalls152153- **Using 2SLS with weak instruments without robust inference**: When F < 10, use LIML or Anderson-Rubin confidence sets instead of 2SLS154- **Not arguing for exclusion restriction**: The exclusion restriction cannot be tested statistically — you must make a convincing argument155- **Confusing LATE with ATE**: IV estimates the local average treatment effect for compliers, not the population average156- **Clustering SE at the wrong level in Bartik IV**: With shift-share instruments, inference should account for the exposure shares structure157- **Over-identifying without caution**: Adding more instruments improves efficiency but only if all are valid — a significant Sargan test means at least one instrument is invalid158- **Using PSM without checking common support**: If treated and control propensity score distributions barely overlap, matching is unreliable159160## Related Skills & Commands161162- **ols-regression**: Compare OLS and IV estimates to illustrate endogeneity bias163- **did-analysis**: DID is an alternative when you have a natural experiment but no instrument164- **matching**: For expanded coverage of PSM, CEM, and doubly-robust methods165- **ml-causal**: DML can handle IV with high-dimensional controls166- **/diagnose**: Run IV-specific diagnostics (first-stage F, Wu-Hausman, overidentification)167- **/robustness**: Compare 2SLS, LIML, OLS, and alternative instrument sets