Diff-in-diff using Instrumental Variables: How to implement with ivreg? (interaction between endogenous and exogenous variables)

Viewed 173

I'd like to estimate the effect of a treatment on two separate groups, so something of the form

Equation 1

T being the treatment and M the dummy separating the two groups.

The problem is that the treatment is correlated to other variables that affect Y. Luckily, there exists a variable Z that serves as an instrument for T. What I've been able to implement in R was to "manually" run 2SLS, following the stages

Equation 2

and

Equation 3

To provide a reproducible example, first a simulation

n <- 100

set.seed(271)

Z <- runif(n)
e <- rnorm(n, sd = 0.5)
M <- as.integer(runif(n)) # dummy
u <- rnorm(n)

# Treat = 1 + 2*Z + e

alpha_0 <- 1
alpha_1 <- 2

Treat <- alpha_0 + alpha_1*Z + e 
  
# Y = 3 + M + 2*Treat + 3*Treat * M + e + u (ommited vars that determine Treat affect Y)

beta_0 <- 3
beta_1 <- 1
beta_2 <- 2
beta_3 <- 3

Y <- beta_0 + beta_1*M + beta_2*Treat + beta_3 * M*Treat + e + u

The first stage regression

fs <- lm(Treat ~ Z)

stargazer::stargazer(fs, type = "text")

===============================================
                        Dependent variable:    
                    ---------------------------
                               Treat           
-----------------------------------------------
Z                            2.383***          
                              (0.168)          
                                               
Constant                     0.835***          
                              (0.096)          
                                               
-----------------------------------------------
Observations                    100            
R2                             0.671           
Adjusted R2                    0.668           
Residual Std. Error       0.445 (df = 98)      
F Statistic           200.053*** (df = 1; 98)  
===============================================

And second stage

Treat_hat <- fitted(fs)

ss <- lm(Y ~ M + Treat_hat + M:Treat_hat)

stargazer::stargazer(ss, type = "text")

===============================================
                        Dependent variable:    
                    ---------------------------
                                 Y             
-----------------------------------------------
M                              1.230           
                              (1.717)          
                                               
Treat_hat                    2.243***          
                              (0.570)          
                                               
M:Treat_hat                  2.636***          
                              (0.808)          
                                               
Constant                      2.711**          
                              (1.213)          
                                               
-----------------------------------------------
Observations                    100            
R2                             0.727           
Adjusted R2                    0.718           
Residual Std. Error       2.539 (df = 96)      
F Statistic           85.112*** (df = 3; 96)   
===============================================

The problem now is that those Standard Errors aren't adjusted for the first stage, which looks like quite some work to do manually. As I'd do for any other IV regression, I'd prefer to just use AER::ivreg.

But I can't seem to get the same regression going there. Here are many possible iterations, that never quite do the same thing

AER::ivreg(Y ~ M + Treat + M:Treat | Z)
AER::ivreg(Y ~ M + Treat + M:Treat | M + Z)
Warning message:
In ivreg.fit(X, Y, Z, weights, offset, ...) :
  more regressors than instruments

These make sense, I guess

AER::ivreg(Y ~ M + Treat + M:Treat | M + Z + M:Z)
Call:
AER::ivreg(formula = Y ~ M + Treat + M:Treat | M + Z + M:Z)

Coefficients:
(Intercept)            M        Treat      M:Treat  
      2.641        1.450        2.229        2.687  

Surprisingly close, but not quite.

I couldn't find a way to tell ivreg that Treat and M:Treat aren't really two separate endogenous variables, but really just the same endogenous variable moved around and interacted with an exogenous one.

In conclusion,

i) Is there some way to mess with ivreg and make this work?

ii) Is there some other function for 2SLS that can just manually accept 1st and 2nd stage formulas without this sort of restriction, and that adjusts standard errors?

iii) What's the simplest way to get the correct SEs if there are no other alternatives? I didn't come across any direct R code, just a bunch of matrix multiplication formulas (although I didn't dig too deep for this one).

Thank you

1 Answers

Essentially, if Z is a valid a valid instrument for Treat, M:Z should be a valid instrument for M:Treat, so, to me this makes sense:

AER::ivreg(Y ~ M + Treat + M:Treat | M + Z + M:Z)

I actually managed to back out the correct param values for a modified simulation:

n <- 100

set.seed(271)

Z <- runif(n)
e <- rnorm(n, sd = 0.5)
M <- round(runif(n)) # note: I changed from as.integer() to round() in order to get some 1's in the regression
u <- rnorm(n)

# Treat = 1 + 2*Z + e

alpha_0 <- 1
alpha_1 <- 2

Treat <- alpha_0 + alpha_1*Z + e 
beta_0 <- 3
beta_1 <- 1
beta_2 <- 2
beta_3 <- 3

Y <- beta_0 + beta_1*M + beta_2*Treat + beta_3 * M*Treat

Now:

    my_ivreg <- AER::ivreg(Y ~ M + Treat + M:Treat | M + Z + M:Z)

>summary(my_ivreg)
Call:
AER::ivreg(formula = Y ~ M + Treat + M:Treat | M + Z + M:Z)

Residuals:
       Min         1Q     Median         3Q        Max
-1.332e-14 -7.105e-15 -3.553e-15 -8.882e-16  3.553e-15

Coefficients:
             Estimate Std. Error   t value Pr(>|t|)
(Intercept) 3.000e+00  2.728e-15 1.100e+15   <2e-16 ***
M           1.000e+00  3.810e-15 2.625e+14   <2e-16 ***
Treat       2.000e+00  1.255e-15 1.593e+15   <2e-16 ***
M:Treat     3.000e+00  1.792e-15 1.674e+15   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 5.633e-15 on 96 degrees of freedom
Multiple R-Squared:     1,  Adjusted R-squared:     1
Wald test: 1.794e+31 on 3 and 96 DF,  p-value: < 2.2e-16

Which is what we were looking for...

Related