11  Treatment effects and matching

Published

January 10, 2019

11.1 Treatment effects in observational studies

Most economic data are observational. With unobserved confounders we need instrumental variables, but instruments are hard to find and harder to justify. Under conditional independence — no unobserved confounders given the covariates — we can proceed without them.

Stata’s teffects suite implements the standard estimators under conditional independence. (The companion eteffects handles endogenous treatment via a control-function approach with instruments.) This chapter walks through how teffects works.

11.1.1 Regression adjustment (RA)

RA allows all covariate effects to differ between treatment and control — equivalent to an outcome model with treatment interacted with all covariates.

Code
clear
webuse bweightex
teffects ra (bweight prenatal1 mmarried mage fbaby) (mbsmoke)
reg bweight i.mbsmoke##c.(prenatal1 mmarried mage fbaby)
margins r.mbsmoke

. clear

. webuse bweightex
(Hypothetical birthweight data)

. teffects ra (bweight prenatal1 mmarried mage fbaby) (mbsmoke)

Iteration 0:  EE criterion = 1.223e-24  
Iteration 1:  EE criterion = 1.792e-25  

Treatment-effects estimation                    Number of obs     =         60
Estimator      : regression adjustment
Outcome model  : linear
Treatment model: none
------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATE          |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |  -389.3099   37.00882   -10.52   0.000    -461.8458   -316.7739
-------------+----------------------------------------------------------------
POmean       |
     mbsmoke |
  Nonsmoker  |   3613.769   29.97438   120.56   0.000     3555.021    3672.518
------------------------------------------------------------------------------

. reg bweight i.mbsmoke##c.(prenatal1 mmarried mage fbaby)

      Source |       SS           df       MS      Number of obs   =        60
-------------+----------------------------------   F(9, 50)        =     18.11
       Model |  1376306.34         9  152922.927   Prob > F        =    0.0000
    Residual |  422241.592        50  8444.83184   R-squared       =    0.7652
-------------+----------------------------------   Adj R-squared   =    0.7230
       Total |  1798547.93        59  30483.8633   Root MSE        =    91.896

------------------------------------------------------------------------------
     bweight | Coefficient  Std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
     mbsmoke |
     Smoker  |  -32.87702   193.2827    -0.17   0.866    -421.0967    355.3426
   prenatal1 |   -23.8431   38.31014    -0.62   0.537    -100.7913    53.10507
    mmarried |  -40.39753   50.21942    -0.80   0.425    -141.2662    60.47114
        mage |    27.9537   7.671423     3.64   0.001     12.54519    43.36221
       fbaby |  -2.030497   40.76029    -0.05   0.960    -83.89995    79.83896
             |
     mbsmoke#|
 c.prenatal1 |
     Smoker  |   8.436558   56.38855     0.15   0.882    -104.8232    121.6963
             |
     mbsmoke#|
  c.mmarried |
     Smoker  |   33.92362   61.34015     0.55   0.583     -89.2817    157.1289
             |
     mbsmoke#|
      c.mage |
     Smoker  |  -15.98608   9.006494    -1.77   0.082    -34.07616    2.103995
             |
     mbsmoke#|
     c.fbaby |
     Smoker  |   14.86782   57.58771     0.26   0.797    -100.8005    130.5362
             |
       _cons |   2976.807   151.0117    19.71   0.000     2673.491    3280.123
------------------------------------------------------------------------------

. margins r.mbsmoke

Contrasts of predictive margins                             Number of obs = 60
Model VCE: OLS

Expression: Linear prediction, predict()

------------------------------------------------
             |         df           F        P>F
-------------+----------------------------------
     mbsmoke |          1      116.06     0.0000
             |
 Denominator |         50
------------------------------------------------

------------------------------------------------------------------------
                       |            Delta-method
                       |   Contrast   std. err.     [95% conf. interval]
-----------------------+------------------------------------------------
               mbsmoke |
(Smoker vs Nonsmoker)  |  -389.3099   36.13675     -461.8927   -316.7271
------------------------------------------------------------------------

The data here are bweightex, a 60-observation extract with birthweight as the outcome and maternal smoking as the treatment.

teffects ra returns the same ATE as margins r.mbsmoke after the interacted regression: both give \(-389.3099\) grams. The standard errors differ slightly, 37.01 against 36.14, because teffects accounts for the estimation of the outcome model differently from margins’ delta method. The point estimate is the same object computed two ways.

For the ATET (average treatment effect on the treated):

Code
clear
webuse bweightex
teffects ra (bweight prenatal1 mmarried mage fbaby) (mbsmoke), atet
reg bweight i.mbsmoke##c.(prenatal1 mmarried mage fbaby)
margins r.mbsmoke, subpop(mbsmoke)

. clear

. webuse bweightex
(Hypothetical birthweight data)

. teffects ra (bweight prenatal1 mmarried mage fbaby) (mbsmoke), atet

Iteration 0:  EE criterion = 1.159e-24  
Iteration 1:  EE criterion = 5.133e-26  

Treatment-effects estimation                    Number of obs     =         60
Estimator      : regression adjustment
Outcome model  : linear
Treatment model: none
------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATET         |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |  -437.4721   53.43513    -8.19   0.000     -542.203   -332.7412
-------------+----------------------------------------------------------------
POmean       |
     mbsmoke |
  Nonsmoker  |   3693.639   53.80537    68.65   0.000     3588.182    3799.095
------------------------------------------------------------------------------

. reg bweight i.mbsmoke##c.(prenatal1 mmarried mage fbaby)

      Source |       SS           df       MS      Number of obs   =        60
-------------+----------------------------------   F(9, 50)        =     18.11
       Model |  1376306.34         9  152922.927   Prob > F        =    0.0000
    Residual |  422241.592        50  8444.83184   R-squared       =    0.7652
-------------+----------------------------------   Adj R-squared   =    0.7230
       Total |  1798547.93        59  30483.8633   Root MSE        =    91.896

------------------------------------------------------------------------------
     bweight | Coefficient  Std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
     mbsmoke |
     Smoker  |  -32.87702   193.2827    -0.17   0.866    -421.0967    355.3426
   prenatal1 |   -23.8431   38.31014    -0.62   0.537    -100.7913    53.10507
    mmarried |  -40.39753   50.21942    -0.80   0.425    -141.2662    60.47114
        mage |    27.9537   7.671423     3.64   0.001     12.54519    43.36221
       fbaby |  -2.030497   40.76029    -0.05   0.960    -83.89995    79.83896
             |
     mbsmoke#|
 c.prenatal1 |
     Smoker  |   8.436558   56.38855     0.15   0.882    -104.8232    121.6963
             |
     mbsmoke#|
  c.mmarried |
     Smoker  |   33.92362   61.34015     0.55   0.583     -89.2817    157.1289
             |
     mbsmoke#|
      c.mage |
     Smoker  |  -15.98608   9.006494    -1.77   0.082    -34.07616    2.103995
             |
     mbsmoke#|
     c.fbaby |
     Smoker  |   14.86782   57.58771     0.26   0.797    -100.8005    130.5362
             |
       _cons |   2976.807   151.0117    19.71   0.000     2673.491    3280.123
------------------------------------------------------------------------------

. margins r.mbsmoke, subpop(mbsmoke)

Contrasts of predictive margins                           Number of obs   = 60
Model VCE: OLS                                            Subpop. no. obs = 30

Expression: Linear prediction, predict()

------------------------------------------------
             |         df           F        P>F
-------------+----------------------------------
     mbsmoke |          1       73.30     0.0000
             |
 Denominator |         50
------------------------------------------------

------------------------------------------------------------------------
                       |            Delta-method
                       |   Contrast   std. err.     [95% conf. interval]
-----------------------+------------------------------------------------
               mbsmoke |
(Smoker vs Nonsmoker)  |  -437.4721   51.09606     -540.1015   -334.8426
------------------------------------------------------------------------

The ATET compares potential outcomes for the treated subpopulation, hence the subpop(mbsmoke) option in margins. Both routes give \(-437.4721\), again identical, and again with slightly different standard errors (53.44 against 51.10). Note the ATET is 48 grams larger in magnitude than the ATE of \(-389.31\): smoking mothers are drawn disproportionately from the groups where smoking does most damage.

11.1.2 Inverse Probability Weighting (IPW)

IPW has two steps. First, estimate the treatment model (typically logit) to obtain the propensity score. Second, use inverse-probability weights to compute the outcome difference between treated and control units.

These steps produce consistent estimates of the effect parameters because the treatment is assumed to be independent of the potential outcomes after conditioning on the covariates.

We can manually conduct the two steps, but the nice thing about using Stata’s teffects is that it takes account of the noise of estimating probability in the first step when calculating standard errors in the second step.

11.1.2.1 example

Code
clear
webuse cattaneo2
teffects ipw (bweight) (mbsmoke mmarried c.mage##c.mage fbaby medu, probit)
probit mbsmoke mmarried c.mage##c.mage fbaby medu
predict ps
replace ps = 1/ps if mbsmoke==1
replace ps = 1/(1-ps) if mbsmoke==0
reg bweight mbsmoke [pweight=ps]

. clear

. webuse cattaneo2
(Excerpt from Cattaneo (2010) Journal of Econometrics 155: 138–154)

. teffects ipw (bweight) (mbsmoke mmarried c.mage##c.mage fbaby medu, probit)

Iteration 0:  EE criterion = 4.622e-21  
Iteration 1:  EE criterion = 8.453e-26  

Treatment-effects estimation                    Number of obs     =      4,642
Estimator      : inverse-probability weights
Outcome model  : weighted mean
Treatment model: probit
------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATE          |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |  -230.6886   25.81524    -8.94   0.000    -281.2856   -180.0917
-------------+----------------------------------------------------------------
POmean       |
     mbsmoke |
  Nonsmoker  |   3403.463   9.571369   355.59   0.000     3384.703    3422.222
------------------------------------------------------------------------------

. probit mbsmoke mmarried c.mage##c.mage fbaby medu

Iteration 0:  Log likelihood = -2230.7484  
Iteration 1:  Log likelihood = -2042.6734  
Iteration 2:  Log likelihood = -2040.5088  
Iteration 3:  Log likelihood = -2040.5061  
Iteration 4:  Log likelihood = -2040.5061  

Probit regression                                       Number of obs =  4,642
                                                        LR chi2(5)    = 380.48
                                                        Prob > chi2   = 0.0000
Log likelihood = -2040.5061                             Pseudo R2     = 0.0853

------------------------------------------------------------------------------
     mbsmoke | Coefficient  Std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |  -.6484821   .0526991   -12.31   0.000    -.7517705   -.5451938
        mage |   .1744327   .0352437     4.95   0.000     .1053562    .2435092
             |
      c.mage#|
      c.mage |  -.0032559   .0006462    -5.04   0.000    -.0045224   -.0019894
             |
       fbaby |  -.2175962   .0491066    -4.43   0.000    -.3138433    -.121349
        medu |  -.0863631   .0098692    -8.75   0.000    -.1057064   -.0670198
       _cons |  -1.558255   .4511589    -3.45   0.001     -2.44251       -.674
------------------------------------------------------------------------------

. predict ps
(option pr assumed; Pr(mbsmoke))

. replace ps = 1/ps if mbsmoke==1
(864 real changes made)

. replace ps = 1/(1-ps) if mbsmoke==0
(3,778 real changes made)

. reg bweight mbsmoke [pweight=ps]
(sum of wgt is 9,193.96990537643)

Linear regression                               Number of obs     =      4,642
                                                F(1, 4640)        =      79.08
                                                Prob > F          =     0.0000
                                                R-squared         =     0.0389
                                                Root MSE          =     573.36

------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
     mbsmoke |  -230.6886   25.94182    -8.89   0.000    -281.5469   -179.8303
       _cons |   3403.463   9.616992   353.90   0.000     3384.609    3422.317
------------------------------------------------------------------------------

We switch here to cattaneo2, the full 4,642-birth dataset, and a richer propensity model (a probit in marital status, a quadratic in maternal age, first-baby status and education).

teffects ipw gives an ATE of \(-230.6886\) with a standard error of 25.82. The manual two-step replication matches the point estimate exactly, \(-230.6886\); the only difference is the standard error (the manual version does not account for first-stage estimation noise). Note this is well short of the \(-389\) found by RA above — but on different data and a different covariate set, so the two are not comparable.

11.1.3 Doubly robust estimators

RA models the outcome; IPW models the treatment assignment. Doubly-robust estimators model both, requiring only one to be correctly specified.

Stata implements the AIPW combination of Robins and Rotnitzky (1995) and the IPWRA combination of Wooldridge (2010).

The AIPW estimator augments the IPW estimator with a correction term built from the outcome model. Double robustness is best stated as a consistency property: AIPW is consistent if either the treatment model or the outcome model is correctly specified (not necessarily both). It is not that the correction term literally “goes to zero” when the treatment model is right and the outcome model is wrong — with a misspecified outcome model the correction term does not generally vanish. Rather, the two pieces are constructed so that the estimator’s bias cancels in the limit as long as one of the two models is right.

The IPWRA estimator uses IPW probability weights when performing RA. The weights do not affect the accuracy of the RA estimator if the treatment model is wrong and the outcome model is correct. The weights correct the RA estimator if the treatment model is correct and the outcome model is wrong.

Here we run the two commands, then manually replicate both as a two-step (“plug-in”) procedure. teffects estimates the treatment and outcome models jointly by GMM/ML, using the joint estimation to get standard errors that account for first-stage estimation noise; the manual two-step version below gets essentially the same point estimate but not the correct standard errors (for the same reason noted above for IPW).

Code
clear
webuse cattaneo2
teffects ipwra (bweight mmarried mage prenatal1 fbaby)  (mbsmoke mmarried mage prenatal1 fbaby)
teffects aipw (bweight mmarried mage prenatal1 fbaby) (mbsmoke mmarried mage prenatal1 fbaby)

. clear

. webuse cattaneo2
(Excerpt from Cattaneo (2010) Journal of Econometrics 155: 138–154)

. teffects ipwra (bweight mmarried mage prenatal1 fbaby)  (mbsmoke mmarried mag
> e prenatal1 fbaby)

Iteration 0:  EE criterion = 9.066e-22  
Iteration 1:  EE criterion = 2.902e-26  

Treatment-effects estimation                    Number of obs     =      4,642
Estimator      : IPW regression adjustment
Outcome model  : linear
Treatment model: logit
------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATE          |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |  -238.7679   24.38353    -9.79   0.000    -286.5587    -190.977
-------------+----------------------------------------------------------------
POmean       |
     mbsmoke |
  Nonsmoker  |   3402.851   9.538741   356.74   0.000     3384.155    3421.546
------------------------------------------------------------------------------

. teffects aipw (bweight mmarried mage prenatal1 fbaby) (mbsmoke mmarried mage 
> prenatal1 fbaby)

Iteration 0:  EE criterion = 2.115e-22  
Iteration 1:  EE criterion = 1.343e-26  

Treatment-effects estimation                    Number of obs     =      4,642
Estimator      : augmented IPW
Outcome model  : linear by ML
Treatment model: logit
------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATE          |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |  -239.0294    24.2524    -9.86   0.000    -286.5632   -191.4955
-------------+----------------------------------------------------------------
POmean       |
     mbsmoke |
  Nonsmoker  |   3402.839   9.538926   356.73   0.000     3384.143    3421.535
------------------------------------------------------------------------------

Manual IPWRA: first estimate the propensity score, then run weighted regression adjustment within each treatment arm, using the inverse probability of the observed treatment status as weights:

Code
clear
webuse cattaneo2
logit mbsmoke mmarried mage prenatal1 fbaby
predict ps

gen wgt1 = mbsmoke/ps
gen wgt0 = (1-mbsmoke)/(1-ps)
reg bweight mmarried mage prenatal1 fbaby if mbsmoke==1 [pweight=wgt1]
predict mu1_ipwra
reg bweight mmarried mage prenatal1 fbaby if mbsmoke==0 [pweight=wgt0]
predict mu0_ipwra
gen tau_ipwra_i = mu1_ipwra - mu0_ipwra
sum tau_ipwra_i

. clear

. webuse cattaneo2
(Excerpt from Cattaneo (2010) Journal of Econometrics 155: 138–154)

. logit mbsmoke mmarried mage prenatal1 fbaby

Iteration 0:  Log likelihood = -2230.7484  
Iteration 1:  Log likelihood = -2089.7838  
Iteration 2:  Log likelihood = -2082.3958  
Iteration 3:  Log likelihood = -2082.3879  
Iteration 4:  Log likelihood = -2082.3879  

Logistic regression                                     Number of obs =  4,642
                                                        LR chi2(4)    = 296.72
                                                        Prob > chi2   = 0.0000
Log likelihood = -2082.3879                             Pseudo R2     = 0.0665

------------------------------------------------------------------------------
     mbsmoke | Coefficient  Std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |   -1.10666    .091279   -12.12   0.000    -1.285564   -.9277569
        mage |  -.0187894   .0082217    -2.29   0.022    -.0349036   -.0026751
   prenatal1 |  -.2721103   .0943067    -2.89   0.004    -.4569481   -.0872725
       fbaby |  -.5428134   .0864561    -6.28   0.000    -.7122642   -.3733625
       _cons |   .1297324   .2095812     0.62   0.536    -.2810391     .540504
------------------------------------------------------------------------------

. predict ps
(option pr assumed; Pr(mbsmoke))

. 
. gen wgt1 = mbsmoke/ps

. gen wgt0 = (1-mbsmoke)/(1-ps)

. reg bweight mmarried mage prenatal1 fbaby if mbsmoke==1 [pweight=wgt1]
(sum of wgt is 4,620.9067401886)

Linear regression                               Number of obs     =        864
                                                F(4, 859)         =       3.12
                                                Prob > F          =     0.0145
                                                R-squared         =     0.0142
                                                Root MSE          =     563.01

------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |   130.5978   39.90558     3.27   0.001     52.27399    208.9217
        mage |  -6.495147    4.95637    -1.31   0.190    -16.22316    3.232866
   prenatal1 |   23.29089   40.04843     0.58   0.561    -55.31335    101.8951
       fbaby |   45.19338   48.48447     0.93   0.352    -49.96852    140.3553
       _cons |   3206.397   126.7646    25.29   0.000     2957.592    3455.202
------------------------------------------------------------------------------

. predict mu1_ipwra
(option xb assumed; fitted values)

. reg bweight mmarried mage prenatal1 fbaby if mbsmoke==0 [pweight=wgt0]
(sum of wgt is 4,643.79376959801)

Linear regression                               Number of obs     =      3,778
                                                F(4, 3773)        =      26.89
                                                Prob > F          =     0.0000
                                                R-squared         =     0.0336
                                                Root MSE          =     567.52

------------------------------------------------------------------------------
             |               Robust
     bweight | Coefficient  std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |   160.9954   26.87191     5.99   0.000     108.3105    213.6803
        mage |   3.061463   2.181356     1.40   0.161    -1.215289    7.338214
   prenatal1 |    62.4539   28.28456     2.21   0.027      6.99938    117.9084
       fbaby |  -67.38469   20.29791    -3.32   0.001    -107.1806   -27.58876
       _cons |   3188.522   56.19956    56.74   0.000     3078.338    3298.707
------------------------------------------------------------------------------

. predict mu0_ipwra
(option xb assumed; fitted values)

. gen tau_ipwra_i = mu1_ipwra - mu0_ipwra

. sum tau_ipwra_i

    Variable |        Obs        Mean    Std. dev.       Min        Max
-------------+---------------------------------------------------------
 tau_ipwra_i |      4,642   -238.7679    97.94189  -481.7334   6.216553

The mean of tau_ipwra_i matches teffects ipwra’s ATE exactly: \(-238.7679\) in both.

Manual AIPW: estimate outcome models \(\hat\mu_1(X)\), \(\hat\mu_0(X)\) (unweighted, one per arm) and the propensity score \(\hat p(X)\), then combine via the augmented-IPW formula \[ \hat\tau_{i}^{aipw} = D_i\frac{Y_i-\hat\mu_1(X_i)}{\hat p(X_i)} + \hat\mu_1(X_i) - (1-D_i)\frac{Y_i-\hat\mu_0(X_i)}{1-\hat p(X_i)} - \hat\mu_0(X_i) \tag{11.1}\]

Code
clear
webuse cattaneo2
logit mbsmoke mmarried mage prenatal1 fbaby
predict ps

reg bweight mmarried mage prenatal1 fbaby if mbsmoke==1
predict mu1_aipw
reg bweight mmarried mage prenatal1 fbaby if mbsmoke==0
predict mu0_aipw
gen aipw_i = mbsmoke*(bweight-mu1_aipw)/ps + mu1_aipw - (1-mbsmoke)*(bweight-mu0_aipw)/(1-ps) - mu0_aipw
sum aipw_i

. clear

. webuse cattaneo2
(Excerpt from Cattaneo (2010) Journal of Econometrics 155: 138–154)

. logit mbsmoke mmarried mage prenatal1 fbaby

Iteration 0:  Log likelihood = -2230.7484  
Iteration 1:  Log likelihood = -2089.7838  
Iteration 2:  Log likelihood = -2082.3958  
Iteration 3:  Log likelihood = -2082.3879  
Iteration 4:  Log likelihood = -2082.3879  

Logistic regression                                     Number of obs =  4,642
                                                        LR chi2(4)    = 296.72
                                                        Prob > chi2   = 0.0000
Log likelihood = -2082.3879                             Pseudo R2     = 0.0665

------------------------------------------------------------------------------
     mbsmoke | Coefficient  Std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |   -1.10666    .091279   -12.12   0.000    -1.285564   -.9277569
        mage |  -.0187894   .0082217    -2.29   0.022    -.0349036   -.0026751
   prenatal1 |  -.2721103   .0943067    -2.89   0.004    -.4569481   -.0872725
       fbaby |  -.5428134   .0864561    -6.28   0.000    -.7122642   -.3733625
       _cons |   .1297324   .2095812     0.62   0.536    -.2810391     .540504
------------------------------------------------------------------------------

. predict ps
(option pr assumed; Pr(mbsmoke))

. 
. reg bweight mmarried mage prenatal1 fbaby if mbsmoke==1

      Source |       SS           df       MS      Number of obs   =       864
-------------+----------------------------------   F(4, 859)       =      3.69
       Model |  4586519.21         4   1146629.8   Prob > F        =    0.0055
    Residual |   266914157       859  310726.609   R-squared       =    0.0169
-------------+----------------------------------   Adj R-squared   =    0.0123
       Total |   271500676       863  314601.015   Root MSE        =    557.43

------------------------------------------------------------------------------
     bweight | Coefficient  Std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |   133.6617   41.33023     3.23   0.001     52.54164    214.7818
        mage |  -7.370881   3.983409    -1.85   0.065    -15.18924    .4474735
   prenatal1 |   25.11133   43.64639     0.58   0.565    -60.55473    110.7774
       fbaby |   41.43991   41.82146     0.99   0.322     -40.6443    123.5241
       _cons |   3227.169   102.3392    31.53   0.000     3026.305    3428.033
------------------------------------------------------------------------------

. predict mu1_aipw
(option xb assumed; fitted values)

. reg bweight mmarried mage prenatal1 fbaby if mbsmoke==0

      Source |       SS           df       MS      Number of obs   =     3,778
-------------+----------------------------------   F(4, 3773)      =     30.00
       Model |    37916015         4  9479003.74   Prob > F        =    0.0000
    Residual |  1.1922e+09     3,773  315979.752   R-squared       =    0.0308
-------------+----------------------------------   Adj R-squared   =    0.0298
       Total |  1.2301e+09     3,777  325683.776   Root MSE        =    562.12

------------------------------------------------------------------------------
     bweight | Coefficient  Std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |   160.9513   24.55483     6.55   0.000     112.8092    209.0933
        mage |   2.546828   1.935504     1.32   0.188    -1.247908    6.341564
   prenatal1 |   64.40859   25.83668     2.49   0.013     13.75338    115.0638
       fbaby |   -71.3286    19.4895    -3.66   0.000    -109.5396   -33.11763
       _cons |   3202.746   51.20076    62.55   0.000     3102.362     3303.13
------------------------------------------------------------------------------

. predict mu0_aipw
(option xb assumed; fitted values)

. gen aipw_i = mbsmoke*(bweight-mu1_aipw)/ps + mu1_aipw - (1-mbsmoke)*(bweight-
> mu0_aipw)/(1-ps) - mu0_aipw

. sum aipw_i

    Variable |        Obs        Mean    Std. dev.       Min        Max
-------------+---------------------------------------------------------
      aipw_i |      4,642   -239.0294     1618.64  -29389.38   16313.36

The mean of aipw_i is \(-239.0294\), matching teffects aipw’s ATE to four decimals. (One might expect a small gap, since teffects estimates the treatment and outcome models jointly while this is a two-step plug-in; here the point estimates coincide, and only the standard errors would differ.)

The two doubly-robust estimators land within 0.3 grams of each other, \(-238.77\) for IPWRA and \(-239.03\) for AIPW, both about 8 grams away from plain IPW’s \(-230.69\).

The unit-level aipw_i values are worth a glance before trusting the mean. They have a standard deviation of 1,618.6 and run from \(-29{,}389\) to \(+16{,}313\) — two orders of magnitude wider than the estimate itself. That spread comes from dividing residuals by \(\hat p\) or \(1-\hat p\) for units with extreme propensity scores. The average is well behaved, but it is an average over some very large individual terms, which is exactly why AIPW can be unstable when overlap is poor.

11.2 Matching

Matching does not solve the endogeneity problem. It addresses selection on observables only; with unobserved confounders it does not help.

What matching does is balance the covariate distributions across treatment and control groups. Regression does this too, but matching makes the lack of overlap visible — a plain regression can extrapolate far outside the support without warning. Running regression on a matched sample combines the strengths of both approaches.

Below we cover three matching methods in Stata: propensity score matching, nearest-neighbor matching, and coarsened exact matching.

11.2.1 Propensity score matching

Exact matching on all covariates is ideal but infeasible in high dimensions. Rosenbaum and Rubin (1983) showed that the propensity score provides a scalar sufficient statistic, reducing the matching problem to one dimension.

Stata’s teffects psmatch and psmatch2 both estimate the propensity score, match, and compute the treatment effect with correct standard errors in one step.

Code
clear
webuse cattaneo2
teffects psmatch (bweight) (mbsmoke mmarried c.mage##c.mage fbaby medu), atet
psmatch2 mbsmoke mmarried c.mage##c.mage fbaby medu, logit ties
reg bweight mbsmoke   [aweight=_weight]

. clear

. webuse cattaneo2
(Excerpt from Cattaneo (2010) Journal of Econometrics 155: 138–154)

. teffects psmatch (bweight) (mbsmoke mmarried c.mage##c.mage fbaby medu), atet

Treatment-effects estimation                   Number of obs      =      4,642
Estimator      : propensity-score matching     Matches: requested =          1
Outcome model  : matching                                     min =          1
Treatment model: logit                                        max =         74
------------------------------------------------------------------------------
             |              AI robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATET         |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |  -236.7848   26.57789    -8.91   0.000    -288.8765    -184.693
------------------------------------------------------------------------------

. psmatch2 mbsmoke mmarried c.mage##c.mage fbaby medu, logit ties

Logistic regression                                     Number of obs =  4,642
                                                        LR chi2(5)    = 375.00
                                                        Prob > chi2   = 0.0000
Log likelihood = -2043.2504                             Pseudo R2     = 0.0841

------------------------------------------------------------------------------
     mbsmoke | Coefficient  Std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
    mmarried |  -1.145706   .0918962   -12.47   0.000     -1.32582    -.965593
        mage |    .321518   .0638472     5.04   0.000     .1963798    .4466563
             |
      c.mage#|
      c.mage |  -.0060368   .0011849    -5.09   0.000    -.0083592   -.0037144
             |
       fbaby |  -.3864258   .0880445    -4.39   0.000    -.5589898   -.2138618
        medu |  -.1420833   .0173215    -8.20   0.000    -.1760328   -.1081338
       _cons |  -2.950915   .8102504    -3.64   0.000    -4.538976   -1.362853
------------------------------------------------------------------------------

. reg bweight mbsmoke   [aweight=_weight]
(sum of wgt is 1,728)

      Source |       SS           df       MS      Number of obs   =     3,671
-------------+----------------------------------   F(1, 3669)      =    151.87
       Model |    51455506         1    51455506   Prob > F        =    0.0000
    Residual |  1.2431e+09     3,669  338808.237   R-squared       =    0.0397
-------------+----------------------------------   Adj R-squared   =    0.0395
       Total |  1.2945e+09     3,670  352736.492   Root MSE        =    582.07

------------------------------------------------------------------------------
     bweight | Coefficient  Std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
     mbsmoke |  -236.7848   19.21387   -12.32   0.000    -274.4557   -199.1138
       _cons |   3374.444   13.58626   248.37   0.000     3347.807    3401.082
------------------------------------------------------------------------------

. 

teffects psmatch gives the ATT of smoking on birthweight, \(-236.7848\) with a standard error of 26.58. The manual replication uses psmatch2 with logit and ties (by default teffects includes all tied matches, while psmatch2 keeps only one), then runs a weighted regression, and gives \(-236.7848\) — the same to four decimals. Point estimates match; standard errors should be taken from teffects. The nearest-neighbour ATT sits close to the two doubly-robust ATEs above, \(-238.8\) and \(-239.0\), though it is a different estimand on a different subpopulation.

11.2.2 Nearest neighbor matching

teffects nnmatch uses Mahalanobis distance by default and can also enforce exact matching on discrete covariates. It is nonparametric.

Code
clear
webuse cattaneo2
teffects nnmatch  (bweight mmarried mage fbaby medu) (mbsmoke) , atet
teffects psmatch (bweight) (mbsmoke mmarried mage fbaby medu), atet

. clear

. webuse cattaneo2
(Excerpt from Cattaneo (2010) Journal of Econometrics 155: 138–154)

. teffects nnmatch  (bweight mmarried mage fbaby medu) (mbsmoke) , atet

Treatment-effects estimation                   Number of obs      =      4,642
Estimator      : nearest-neighbor matching     Matches: requested =          1
Outcome model  : matching                                     min =          1
Distance metric: Mahalanobis                                  max =         74
------------------------------------------------------------------------------
             |              AI robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATET         |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |  -239.2433   25.68524    -9.31   0.000    -289.5854   -188.9011
------------------------------------------------------------------------------

. teffects psmatch (bweight) (mbsmoke mmarried mage fbaby medu), atet

Treatment-effects estimation                   Number of obs      =      4,642
Estimator      : propensity-score matching     Matches: requested =          1
Outcome model  : matching                                     min =          1
Treatment model: logit                                        max =         74
------------------------------------------------------------------------------
             |              AI robust
     bweight | Coefficient  std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
ATET         |
     mbsmoke |
    (Smoker  |
         vs  |
 Nonsmoker)  |   -245.711   26.38675    -9.31   0.000    -297.4281   -193.9939
------------------------------------------------------------------------------

. 

Note the specification difference: nnmatch lists covariates in the outcome parentheses, psmatch lists them in the treatment parentheses.

11.2.3 Coarsened Exact Matching (CEM)

CEM is available as a separate Stata package.

Code
clear
webuse cattaneo2
cem  mmarried mage fbaby medu, treatment(mbsmoke)
reg bweight mbsmoke   [aweight=cem_weights]
* Note: cem creates the variable "cem_weights" (plural). We spell it out
* in full here rather than relying on Stata's automatic variable-name
* abbreviation matching, to keep the reference unambiguous.

. clear

. webuse cattaneo2
(Excerpt from Cattaneo (2010) Journal of Econometrics 155: 138–154)

. cem  mmarried mage fbaby medu, treatment(mbsmoke)

Matching Summary:
-----------------
Number of strata: 274
Number of matched strata: 135

              0     1
      All  3778   864
  Matched  3421   827
Unmatched   357    37


Multivariate L1 distance: .25424705

Univariate imbalance:

               L1     mean      min      25%      50%      75%      max
mmarried  6.4e-15  8.4e-15        0        0        0        0        0
    mage   .04955  -.00887        1        0        1        0       -2
   fbaby  6.1e-15  6.1e-15        0        0        0        0        0
    medu   .03809  -.03316        0        0        0        0        0

. reg bweight mbsmoke   [aweight=cem_weights]
(sum of wgt is 4,248.00000000005)

      Source |       SS           df       MS      Number of obs   =     4,248
-------------+----------------------------------   F(1, 4246)      =    121.12
       Model |  40566772.9         1  40566772.9   Prob > F        =    0.0000
    Residual |  1.4221e+09     4,246  334928.257   R-squared       =    0.0277
-------------+----------------------------------   Adj R-squared   =    0.0275
       Total |  1.4627e+09     4,247   344401.26   Root MSE        =    578.73

------------------------------------------------------------------------------
     bweight | Coefficient  Std. err.      t    P>|t|     [95% conf. interval]
-------------+----------------------------------------------------------------
     mbsmoke |  -246.8017   22.42533   -11.01   0.000    -290.7671   -202.8364
       _cons |   3383.394   9.894625   341.94   0.000     3363.996    3402.793
------------------------------------------------------------------------------

. * Note: cem creates the variable "cem_weights" (plural). We spell it out
. * in full here rather than relying on Stata's automatic variable-name
. * abbreviation matching, to keep the reference unambiguous.
. 

CEM creates exact-match strata on coarsened covariates and generates weights for the final regression. The standard errors do not account for the matching step.

11.3 Modern matching in R: balance diagnostics and entropy weighting

The 2019 vintage of this chapter used Stata’s teffects throughout. R’s MatchIt and WeightIt packages have since become the standard for matching workflows, and cobalt provides publication-quality balance tables and plots that are now expected in applied papers.

11.3.1 Balance checking with cobalt

After any matching or weighting step, always inspect covariate balance. cobalt’s bal.tab() and love.plot() are the go-to tools:

Code
library(MatchIt)
library(cobalt)

# Propensity score matching on the lalonde data
data("lalonde", package = "MatchIt")

m_out <- matchit(treat ~ age + educ + race + married + nodegree +
                   re74 + re75,
                 data = lalonde, method = "nearest", distance = "glm",
                 ratio = 1)

# Balance table: standardized mean differences before and after matching
bal.tab(m_out, stats = c("mean.diffs", "variance.ratios"), thresholds = c(m = 0.1))
Balance Measures
                Type Diff.Adj        M.Threshold V.Ratio.Adj
distance    Distance   0.9739                         0.7566
age          Contin.   0.0718     Balanced, <0.1      0.4568
educ         Contin.  -0.1290 Not Balanced, >0.1      0.5721
race_black    Binary   0.3730 Not Balanced, >0.1           .
race_hispan   Binary  -0.1568 Not Balanced, >0.1           .
race_white    Binary  -0.2162 Not Balanced, >0.1           .
married       Binary  -0.0216     Balanced, <0.1           .
nodegree      Binary   0.0703     Balanced, <0.1           .
re74         Contin.  -0.0505     Balanced, <0.1      1.3289
re75         Contin.  -0.0257     Balanced, <0.1      1.4956

Balance tally for mean differences
                   count
Balanced, <0.1         5
Not Balanced, >0.1     4

Variable with the greatest mean difference
   Variable Diff.Adj        M.Threshold
 race_black    0.373 Not Balanced, >0.1

Sample sizes
          Control Treated
All           429     185
Matched       185     185
Unmatched     244       0
Code
# Love plot: visualize balance improvement
love.plot(m_out, stats = "mean.diffs", threshold = 0.1,
          abs = TRUE, var.order = "unadjusted")

The love plot shows each covariate’s standardized mean difference (SMD) before matching (open circles) and after (filled circles). The vertical line at SMD = 0.1 is the conventional threshold for “balanced.” If post-matching points cross the line, the matching is not improving balance and a different specification is needed.

That is exactly what happens here, which is why it is worth running the diagnostic rather than assuming. 1:1 nearest-neighbour matching on the propensity score leaves four of the nine covariates above the 0.1 threshold, race worst of all (an adjusted SMD of 0.37 for the black indicator), while discarding 244 of the 429 controls. Matching on a propensity score balances the score, not necessarily the covariates that went into it.

11.3.2 Entropy balancing

Propensity score matching discards unmatched units and can reduce effective sample size substantially. Entropy balancing (Hainmueller 2012) keeps all units and reweights the control group so that weighted covariate moments exactly match the treatment group — without discarding anyone.

Code
library(WeightIt)
library(cobalt)

# Entropy balancing: exact balance on means, variances, and covariances
w_out <- weightit(treat ~ age + educ + race + married + nodegree +
                    re74 + re75,
                  data = lalonde, method = "ebal", estimand = "ATT",
                  moments = 1)   # moments=1: balance means only
                                 # moments=2: balance means + variances

summary(w_out)
                  Summary of weights

- Weight ranges:

          Min                                 Max
treated 1.           ||                     1.   
control 0.008 |---------------------------| 4.062

- Units with the 5 most extreme weights by group:
                                                
            NSW5    NSW4    NSW3    NSW2    NSW1
 treated       1       1       1       1       1
         PSID423 PSID196 PSID412 PSID118 PSID226
 control   3.073   3.235    3.45   3.897   4.062

- Weight statistics:

        Coef of Var   MAD Entropy # Zeros
treated       0.    0.      0.          0
control       1.834 1.287   1.101       0

- Effective Sample Sizes:

           Control Treated
Unweighted  429.       185
Weighted     98.46     185
Code
# Check balance
bal.tab(w_out, stats = "mean.diffs", thresholds = c(m = 0.1))
Balance Measures
               Type Diff.Adj    M.Threshold
age         Contin.       -0 Balanced, <0.1
educ        Contin.       -0 Balanced, <0.1
race_black   Binary        0 Balanced, <0.1
race_hispan  Binary        0 Balanced, <0.1
race_white   Binary       -0 Balanced, <0.1
married      Binary       -0 Balanced, <0.1
nodegree     Binary        0 Balanced, <0.1
re74        Contin.       -0 Balanced, <0.1
re75        Contin.       -0 Balanced, <0.1

Balance tally for mean differences
                   count
Balanced, <0.1         9
Not Balanced, >0.1     0

Variable with the greatest mean difference
 Variable Diff.Adj    M.Threshold
     re74       -0 Balanced, <0.1

Effective sample sizes
           Control Treated
Unadjusted  429.       185
Adjusted     98.46     185
Code
love.plot(w_out, threshold = 0.1, abs = TRUE)

Code
# Weighted outcome regression
library(lmtest); library(sandwich)
m_eb <- lm(re78 ~ treat + age + educ + race + married + nodegree +
             re74 + re75,
           data = lalonde, weights = w_out$weights)
coeftest(m_eb, vcov = vcovHC(m_eb, type = "HC3"))["treat", ]
    Estimate   Std. Error      t value     Pr(>|t|) 
1273.2618139  820.1848528    1.5524083    0.1210883 

Entropy balancing delivers on its promise here: the largest absolute standardized mean difference after weighting is exactly zero, since the weights are chosen to satisfy the mean-balance constraints as equalities. All 429 controls are retained.

Method Pros Cons
PSM (nearest neighbor) Intuitive; respects overlap Discards units; balance not guaranteed
CEM Exact balance in bins Requires discretizing continuous vars
Entropy balancing Exact mean balance; keeps all units Weights can be extreme; no variance balance by default
IPW (logistic) Simple; relies only on the treatment model Not doubly robust on its own; extreme weights if overlap is poor
AIPW / IPWRA Doubly robust: consistent if either the treatment or outcome model is correct Needs both models specified; more moving parts

For most applications, entropy balancing or IPW with trimmed weights (via WeightIt’s trim()) is preferred over PSM: better effective sample size, guaranteed balance, and easy integration with doubly-robust estimators.

See also the weighting chapter for IPW estimators and the OLS-ATE chapter for regression-based approaches.


Systematic treatment: R · Julia.