Learning Objectives
Following this assignment students should be able to:
- Construct instrumental variables regressions
- Conduct overidentification and weak instrument tests
- Calculate robust standard errors
- Estimate bootstraped standard errors
- Conduct randomization inference
- Apply multiple hypothesis testing corrections
Reading
Topics
- Instrumental variables
- Two-stage least squares
- Weak instruments
- Testing for endogeneity
- First-stage regressions
- Testing for exogeneity
Readings
- Reading (chapter 19): The Effect
Lecture Notes
Exercises
-
Manual 2SLS (10 pts)
In this week’s exercises, you will be using data from Michler et al. (2019), which studies the impact of conservation agriculture (CA) on crop yields in Zimbabwe. CA adoption (
CA) is endogenous — farmers who adopt CA may differ systematically from those who don’t. In the paper, we instrument CA adoption usingwardNGO, the number of other households in the same ward that received NGO support for CA.- Load
"$data/Michler_JEEM.dta"and keep only maize observations (keep if crop == 1). - Run a naive OLS regression of
lnyieldonCAwith the following controls:lnbasal lntop lnseed lnaream2 pdate pdate2 i.year, clustering standard errors at therclevel. Store the results asols. - Run the First Stage: Regress the endogenous variable (
CA) on the instrument (wardNGO) and all the same controls (lnbasal lntop lnseed lnaream2 pdate pdate2 i.year), clustering atrc. Store asfirst. - Generate the predicted fitted values from the first stage using
predict CA_hat, xb. - Run the Second Stage: Regress
lnyieldonCA_hatand the same controls, clustering atrc. Store asmanual.
1. Export a two-column table comparing OLS and Manual 2SLS and put it in your Overleaf file:
* compare ols and manual 2sls esttab ols manual using "$answ/13-iv-manual.tex", replace /// b(3) se(3) /// keep(CA) rename(CA_hat CA) nodepvars /// star(* 0.10 ** 0.05 *** 0.01) /// nomtitles fmt(0 3)) /// stats(N r2, labels("Observations" "R-squared") /// noobs booktabs nonum collabels(none) /// nobaselevels nogaps fragment label /// prehead("\begin{tabular}{l*{2}{c}} " /// "\\[-1.8ex]\hline \hline \\[-1.8ex] " /// "& \multicolumn{1}{c}{OLS} & " /// "\multicolumn{1}{c}{Manual 2SLS} " /// "\\ \midrule") /// postfoot("\hline \hline \\[-1.8ex] " /// "\multicolumn{3}{p{\linewidth}}{\small " /// "\noindent \textit{Note}: Dependent var. " /// "is log maize yield. Standard errors " /// "clustered at household level in " /// "parentheses. Manual 2SLS standard errors " /// "are incorrect. " /// "* p$<$0.10, ** p$<$0.05, *** p$<$0.01.} " /// "\end{tabular}")2. Does the coefficient on CA increase or decrease when you move from OLS to the manual 2SLS? What does this tell you about the direction of the selection bias?
- Load
-
Using `ivreg2` (10 pts)
The manual 2SLS from the previous exercise produces correct coefficients but incorrect standard errors, because Stata doesn’t know that
CA_hatis an estimated variable. Now we’ll use the user-writtenivreg2command, which handles both stages simultaneously, properly adjusts the standard errors, and automatically reports key diagnostic statistics (weak instrument F-stat, overidentification test).- Using
Michler_JEEM.dta(maize only), runivreg2withwardNGOas the instrument forCA, including the same controls as Exercise 1 (lnbasal lntop lnseed lnaream2 pdate pdate2 i.year) and clustering atrc. Use thefirstoption to display the first-stage results. Store asiv.
1. Adapt the table code from Exercise 1 to add a third column for
ivreg2and export to Overleaf.2. Create a
coefplotcomparing theCAcoefficient across OLS, manual 2SLS, andivreg2and export to Overleaf:* coefficient plot comparing OLS, Manual, and IV coefplot (ols, label("OLS")) /// (manual, label("Manual 2SLS")) /// (iv, label("IV (2SLS)")), /// keep(CA CA_hat) xline(0) /// title("Effect of CA on Maize Yield: " /// "OLS vs Manual vs IV") /// xtitle("Coefficient on CA and CA_hat") /// graphregion(color(white)) graph export "$answ/13-iv-coefplot.png", replace3. Is the coefficient on
CAfromivreg2identical to the one you found manually in Exercise 1?4. Are the standard errors the same? Why or why not?
- Using
-
Panel IV with `xtivreg2` (10 pts)
In the previous exercises we estimated the effect of conservation agriculture on maize yields using cross-sectional IV. But the
Michler_JEEM.dtadataset is actually a panel: households (rc) are observed over multiple years. This means we can absorb time-invariant household heterogeneity with fixed effects while instrumenting for CA adoption.For whatever reason,
xtivre2can’t hand factor-variable operators likei.year. So, before running your regressions, use the Stata commandqui tab year, gen(y_)to create dummy variables for each year and include these in your regressions.- Using
Michler_JEEM.dta(maize only), declare the panel structure withxtset rc. - Run a fixed effects regression using
xtregusing the same variables as in the previous exercises but addingfeandcluster(rc). Store asfe. - Run a panel IV regression using
xtivreg2using the same variables as in the previous exercises but addingfeandcluster(rc). Store asfe_iv.
1. Adapt your previous table to add 2 more columns. Order results as
ols,manual,iv,fe,fe_iv. Export and put into Overleaf.2. Adapt your previous
coefplotto include all 5 specifications and export to Overleaf.3. How does the CA coefficient change when you move from pooled OLS to fixed effects? What does this tell you about time-invariant confounders?
4. How does the CA coefficient change when you add IV? What does this tell you about time-varying confounders?
- Using
-
Instrument Diagnostics (10 pts)
Before trusting any IV estimate, we must test whether our instrument is actually valid. In this exercise you will run the standard battery of diagnostic tests on the
Michler_JEEM.dtaconservation agriculture IV specification.- Using
Michler_JEEM.dta(maize only), runxtivreg2with clustered standard errors- Add the
firstoption to display both first- and second-stage results - Add the
endog(CA)option to display the Durbin-Wu-Hausman Test
- Add the
- Does
wardNGOpass the standard rule-of-thumb threshold of $F > 10$ for instrument strength using the Kleibergen-Paap F-statistic? - Based on the endogeneity test, is
CAstatistically endogenous? - We have exactly one instrument (
wardNGO) for one endogenous variable (CA). What does the Stata output say about the Hansen J-statistic?
- Using
-
Robust & Clustered Standard Errors (10 pts)
In this exercise you will compare different approaches to calculating standard errors for the conservation agriculture IV regression from
Michler_JEEM.dta. The base specification is a pooled IV model usingivreg2with household fixed effects.- Run the panel IV regression with default (uncorrected) standard errors.
- Run the same specification but with the
robustoption. - The
bw(1) kernel(tru)options require the data to betsset. Since the data has multiple plots per household-year, generate a unique observation ID (gen obs_id = _n) andtsset obs_idbefore running this specification. - Run the same specification but using the Bell-McCaffrey corrections
bw(1) kernel(tru). - Run the same specification but with the standard error option
clusterat the household (rc) level.
1. Export a four-column comparison table and put it in your Overleaf file:
* compare standard error methods esttab def robust hc2 cluster /// using "$answ/13-se-hc2.tex", replace /// b(3) se(3) keep(CA) nomtitles /// star(* 0.10 ** 0.05 *** 0.01) /// stats(N, labels("Observations") fmt(0)) /// noobs booktabs nonum collabels(none) /// nobaselevels nogaps fragment label /// prehead("\begin{tabular}{l*{4}{c}} " /// "\\[-1.8ex]\hline \hline \\[-1.8ex] " /// "& \multicolumn{1}{c}{Uncorrected} & " /// "\multicolumn{1}{c}{Robust} & " /// "\multicolumn{1}{c}{HC2} & " /// "\multicolumn{1}{c}{Clustered} " /// "\\ \midrule") /// postfoot("\hline \hline \\[-1.8ex] " /// "\multicolumn{5}{p{0.9\linewidth}}{\small " /// "\noindent \textit{Note}: Dependent variable " /// "is log maize yield. CA instrumented with " /// "wardNGO. All models include input controls " /// "and year FE. " /// "* p$<$0.10, ** p$<$0.05, *** p$<$0.01.} " /// "\end{tabular}")1. Compare the standard error on
CAacross HC1, HC2, and the panel FE specifications. Does adjusting for leverage (HC2) increase or decrease the standard error?2. How do the clustered standard errors compare to the robust standard errors? What does this suggest about within-household correlation in this panel?
-
Bootstrap Comparisons (10 pts)
If we aren’t satisfied with analytical approximations of standard errors, we can calculate them empirically through bootstrapping. In this exercise you will compare bootstrap standard errors to the analytical standard errors from the previous exercise.
- Using
Michler_JEEM.dta(maize only), run the pooled IV regression (ivreg2) with bootstrap standard errors. - Set the seed to 5453654.
- Bootstrap the regression 1,000 times.
- Cluster the standard errors at the household (
rc) level.
- Adapt the table code from Exercise 1 to add a fifth column for the bootstrap results and export to Overleaf.
- Are the bootstrapped standard errors closer to the robust or the clustered standard errors from the previous exercise?
- Using
-
Wild Cluster Bootstrap (10 pts)
Clustered standard errors rely on asymptotic theory and require a large number of independent clusters (typically 42+) to be reliable. In this exercise you will test what happens when you cluster at a level with relatively few clusters, and then correct using the Wild Cluster Bootstrap.
- Using
Michler_JEEM.dta(maize only), run the pooled IV regression clustering at the ward level (ward_id) instead of the household level. Useivregress 2slsrather thanivreg2for compatibility withboottest. - Now apply the Wild Cluster Bootstrap via post-estimation to get a more reliable p-value:
boottest CA
- How many unique wards are there in the maize sample? Is this above or below the commonly cited minimum of 40 clusters?
- Compare the analytical cluster-robust p-value with the wild cluster bootstrap p-value. Does the bootstrap make you more or less confident in the significance of the CA effect?
- Using
-
Randomization Inference (20 pts)
If standard errors are theoretically messy or you want a fully non-parametric p-value, you can bypass the standard error calculation entirely using Randomization Inference (RI). RI randomly permutes the treatment variable thousands of times, generating a distribution of placebo coefficients. Your actual coefficient is then compared against this distribution.
- Using
Michler_JEEM.dta(maize only), first run the OLS regression oflnyieldonCAwith the same controls (lnbasal lntop lnseed lnaream2 pdate pdate2 i.year), clustering standard errors atrc, and save the true coefficient onCAin a global calledtrue_b. - Run
ritestto permuteCA1,000 times setting the seed at 0 and then save (saving) the permutation distribution in a file calledri_yield. - Finally, save the p-value from the randomization inference in a global called
ri_p. Use globals rather than locals becauseuse, clearwipes locals from memory.
* save ri p-value matrix pvalues = r(p) global ri_p = pvalues[1,1] global ri_p : di %5.3f ${ri_p}1. Load the saved permutation data and create a kernel density plot of the placebo distribution, marking the true coefficient and the RI p-value and output it to your Overleaf file.
* plot permutation distribution twoway (kdensity _pm_1, lwidth(medthick) /// lcolor(sky) lpattern(dash)), /// ytitle("Density") /// xtitle("Hypothetical treatment effect") /// title("CA effect on yield (t/ha)") /// xscale(range(${true_b})) /// xline(${true_b}, lpattern(solid) /// lwidth(thin) lcolor(sky)) /// text(.18 `=${true_b} - .01' "CA TE", /// color(sky) j(left) size(vsmall) /// place(nw) orient(vertical)) /// text(2.2 `=${true_b} - .01' /// "p-value = ${ri_p}", color(sky) /// j(left) size(vsmall) place(nw) /// orient(vertical)) /// legend(off)2. What is the RI p-value?
3. How does the RI p-value compare to the p-value from the standard
ivreg2regression?
- Using
-
Check That Your Code Runs (10 pts)
Sometimes you think you’re code runs, but it only actually works because of something else you did previously. To make sure it actually runs you should save your work and then run it in a clean environment.
Follow these steps in to make sure your code really runs:
-
Restart Stata by closing the instance of Stata that you have open.
-
When you reopen Stata, type in the command line
clear all. Or, better yet, putclear allat the top of your.dofile, after the preamble but before any commands. -
Rerun your entire homework assignment by clicking the
Executebutton to make sure it runs from start to finish and produces the expected results. -
Make sure that you saved your code with the name of the assignment number (i.e.,
assignment_01). You should see the file in your git repo on your machine. -
Make sure that your code will run on other computers (relevant for all assignments after the first assignment)
- No
cd. Use aproject.dofile instead to set directories - Use only relative paths in files other than the
project.dofile - Use
/not\for paths
- No
-
-
Challenge 13 (Challenge - 20 pts)
In the previous exercises we estimated the effect of conservation agriculture (CA) on maize yields. But the
Michler_JEEM.dtadataset contains five crops. When we test the same hypothesis across five crops, we are conducting multiple hypothesis tests, and the probability of at least one spurious rejection rises sharply. In this exercise you will estimate crop-specific production functions, apply three MHT corrections, and build a summary table comparing all the corrected p-values.Part 1 — Estimate Crop-Specific Production Functions
- Load
"$data/Michler_JEEM.dta". - The
cropvariable takes five values:1= Maize,2= Sorghum,3= Millet,4= Groundnut,5= Cowpea. - Because
eststodoes not support factor-variable operators inside loops, first generate year dummies withtab year, gen(y_). - Loop over each crop, subset the data, and run the OLS production function regression of
lnyieldonCA lnbasal lntop lnseed lnaream2 pdate pdate2 y_*, clustering standard errors at the household (rc) level. Store each set of results with a descriptive name:
* load data use "$data/Michler_JEEM.dta", clear * create year dummies (eststo can't handle i.year inside loops) qui tab year, gen(y_) * estimate crop-specific production functions local crops `" "Maize" "Sorghum" "Millet" "Groundnut" "Cowpea" "' local i = 1 foreach c of local crops { preserve keep if crop == `i' count if r(N) > 0 { reg lnyield CA lnbasal lntop lnseed lnaream2 /// pdate pdate2 y_*, vce(cluster rc) eststo `c' * store the p-value on CA for later local p`i' = r(table)[4,1] } restore local ++i }Part 2 — Bonferroni and Holm Corrections
- We have 5 hypotheses (one per crop). Apply the Bonferroni and Holm corrections by hand, storing the corrected p-values into locals so they can be used later in a table:
* collect the 5 p-values matrix pvals = (`p1', `p2', `p3', `p4', `p5') matrix colnames pvals = Maize Sorghum Millet Groundnut Cowpea * bonferroni: multiply each p-value by 5 local crops `" "Maize" "Sorghum" "Millet" "Groundnut" "Cowpea" "' local j = 1 foreach c of local crops { local p_bonf`j' = min(pvals[1,`j'] * 5, 1) local ++j } * holm: sort p-values and apply step-down penalties preserve clear set obs 5 gen crop_name = "" gen crop_id = . gen pval = . replace crop_name = "Maize" in 1 replace crop_name = "Sorghum" in 2 replace crop_name = "Millet" in 3 replace crop_name = "Groundnut" in 4 replace crop_name = "Cowpea" in 5 forvalues j = 1/5 { replace crop_id = `j' in `j' replace pval = `p`j'' in `j' } sort pval gen rank = _n gen holm_p = min(pval * (5 - rank + 1), 1) * enforce monotonicity (holm adjusted p-values must be non-decreasing) replace holm_p = holm_p[_n-1] if holm_p < holm_p[_n-1] /// & _n > 1 * store holm p-values back into locals by crop id forvalues j = 1/5 { su holm_p if crop_id == `j', meanonly local p_holm`j' = r(mean) } list crop_name pval holm_p, clean noobs restorePart 3 — Westfall-Young Correction
- Use
wyoung(a resampling-based FWER correction) to compute adjusted p-values that account for the correlation structure across the five crops, then store the results into locals:
* westfall-young adjusted p-values use "$data/Michler_JEEM.dta", clear wyoung lnyield, cmd(reg OUTCOMEVAR CA lnbasal lntop /// lnseed lnaream2 pdate pdate2 i.year, /// vce(cluster rc)) cluster(rc) /// familyp(CA) subgroup(crop) /// reps(100) seed(123) * store westfall-young p-values (wyoung returns matrix r(table)) matrix wy = r(table) forvalues j = 1/5 { local p_wy`j' = wy[`j', 4] }(Note: You may need to run
ssc install wyoungfirst.)Part 4 — Summary Table with All p-Values
- Use
estaddto attach the raw and corrected p-values as scalars to each stored estimate, then export a single table. Instead of standard errors, the table shows the CA coefficient followed by a row for each p-value correction:
* attach corrected p-values to each stored estimate local crops `" "Maize" "Sorghum" "Millet" "Groundnut" "Cowpea" "' local j = 1 foreach c of local crops { estimates restore `c' estadd scalar raw_p = `p`j'' estadd scalar bonf_p = `p_bonf`j'' estadd scalar holm_p = `p_holm`j'' estadd scalar wy_p = `p_wy`j'' estimates drop `c' estimates store `c' local ++j } * export summary table esttab Maize Sorghum Millet Groundnut Cowpea /// using "$answ/13-challenge.tex", replace /// b(3) nostar /// keep(CA) nodepvars /// mtitles("Maize" "Sorghum" "Millet" /// "Groundnut" "Cowpea") /// stats(raw_p bonf_p holm_p wy_p N r2, /// labels("Raw \textit{p}-value" /// "Bonferroni \textit{p}" /// "Holm \textit{p}" /// "Westfall-Young \textit{p}" /// "\midrule Observations" /// "R-squared") /// fmt(3 3 3 3 0 3)) /// noobs booktabs nonum collabels(none) /// nobaselevels nogaps fragment label /// prehead("\begin{tabular}{l*{5}{c}} " /// "\\[-1.8ex]\hline \hline \\[-1.8ex] " /// "& \multicolumn{5}{c}{Dependent Variable: " /// "Log Yield} \\ \midrule") /// postfoot("\hline \hline \\[-1.8ex] " /// "\multicolumn{6}{p{\linewidth}}{\small " /// "\noindent \textit{Note}: Each column is a " /// "separate OLS regression for the indicated " /// "crop. Reported \textit{p}-values are for " /// "the null hypothesis that " /// "$\beta_{\text{CA}} = 0$. " /// "Bonferroni and Holm adjust for 5 " /// "hypotheses. Westfall-Young uses 1,000 " /// "bootstrap replications.} " /// "\end{tabular}")Part 5 — Interpretation
- Which crops have a statistically significant effect of CA at the 5% level using the raw p-value? Do any results lose significance under the corrections?
- Why do the Westfall-Young adjusted p-values tend to be less conservative than the Bonferroni correction?
- Load
Assignment submission checklist
Solutions
- Manual 2SLS 1, 2
- Using
ivreg21, 2, 3 - Panel IV with
xtivreg21, 2, 3 - Instrument Diagnostics
- Robust & Clustered Standard Errors 1, 2
- Bootstrap Comparisons 1, 2
- Wild Cluster Bootstrap
- Randomization Inference 1, 2
- Check That Your Code Runs
- Challenge 13