library(conflicted)
library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.1
✔ purrr 1.0.2
library(haven)
library(boot)
library(tictoc)
library(beepr)
library(car)
Loading required package: carData
library(marginaleffects)
Registered S3 method overwritten by 'data.table':
method from
print.data.table
dat <- read_dta("https://www.stata-press.com/data/r14/cattaneo2.dta")
Try to replicate the example at:
https://www.stata.com/manuals/teteffectsra.pdf#teteffectsra
ATE is -239.6392, SE = 23.82402, z = -10.06, 95% CI = [-286.3334,
-192.945].
ATT is -223.3017, SE = 22.7422, z = -9.82, 95% CI = [-267.8755,
-178.7278].
{marginaleffects}
Fit the model:
gmod <- lm(bweight ~ mbsmoke * (prenatal1 + mmarried + mage + fbaby), data = dat)
ATE:
avg_comparisons(
gmod,
variables = "mbsmoke",
vcov = "HC1"
) |>
print(style = "data.frame")
The estimate is spot on, the SE is very close to Stata’s 23.82402 (I
think HC1 is the flavour of robust SE provided by Stata).
Check ATT:
avg_comparisons(
gmod,
variables = "mbsmoke",
type = "response",
newdata = subset(dat, mbsmoke == 1),
vcov = "HC1"
) |>
print(style = "data.frame")
Again, the estimate is identical and SE close to Stata’s 22.7422.
Replicate teffects “by hand”
boot_ate <- function(d, i) {
the_dat <- d[i,]
dat0 <- the_dat |> dplyr::filter(mbsmoke == 0)
dat1 <- the_dat |> dplyr::filter(mbsmoke == 1)
lm0 <- lm(bweight ~ prenatal1 + mmarried + mage + fbaby, data = dat0)
lm1 <- lm(bweight ~ prenatal1 + mmarried + mage + fbaby, data = dat1)
dat0 <- dat0 |>
mutate(bweight_0 = bweight,
bweight_1 = predict(lm1, newdata = dat0))
dat1 <- dat1 |>
mutate(bweight_1 = bweight,
bweight_0 = predict(lm0, newdata = dat1))
dat_all <- bind_rows(dat0, dat1) |>
mutate(te = bweight_1 - bweight_0)
res_ATE <- dat_all |>
summarise(ate = mean(te))
res_ATT <- dat_all |>
dplyr::filter(mbsmoke == 1) |>
summarise(att = mean(te))
c(ate = res_ATE$ate, att = res_ATT$att)
}
I’ve packaged it all up a function ready for bootstrapping, which
applied to the original data replicates teffects:
boot_ate(dat, 1:nrow(dat))
ate att
-239.6392 -223.3017
Now bootstrap boot_ate to get SEs:
set.seed(19082024)
tic()
boots <- boot(data = dat, statistic = boot_ate, R = 1499)
toc()
34.71 sec elapsed
beep(2)
res <- summary(boots) |> as.data.frame()
rownames(res) <- names(boots$t0)
resCI <- bind_cols(res, Confint(boots, type = "perc"))
resCI
Stata said:
ATE is -239.6392, SE = 23.82402, 95% CI = [-286.3334, -192.945].
ATT is -223.3017, SE = 22.7422, 95% CI = [-267.8755, -178.7278].
LS0tDQp0aXRsZTogInRlZmZlY3RzIHJhIGluIFIiDQphdXRob3I6ICJBbmRpIEZ1Z2FyZCAoW0BhbmRpQHNjaWVuY2VzLnNvY2lhbF0oaHR0cHM6Ly9zY2llbmNlcy5zb2NpYWwvQGFuZGkpKSINCmRhdGU6ICJMYXN0IGtuaXR0ZWQgYHIgZm9ybWF0KFN5cy5EYXRlKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCi0tLQ0KDQpgYGB7cn0NCmxpYnJhcnkoY29uZmxpY3RlZCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShoYXZlbikNCmxpYnJhcnkoYm9vdCkNCmxpYnJhcnkodGljdG9jKQ0KbGlicmFyeShiZWVwcikNCmxpYnJhcnkoY2FyKQ0KbGlicmFyeShtYXJnaW5hbGVmZmVjdHMpDQpgYGANCg0KYGBge3J9DQpkYXQgPC0gcmVhZF9kdGEoImh0dHBzOi8vd3d3LnN0YXRhLXByZXNzLmNvbS9kYXRhL3IxNC9jYXR0YW5lbzIuZHRhIikNCmBgYA0KDQoNClRyeSB0byByZXBsaWNhdGUgdGhlIGV4YW1wbGUgYXQ6DQoNCmh0dHBzOi8vd3d3LnN0YXRhLmNvbS9tYW51YWxzL3RldGVmZmVjdHNyYS5wZGYjdGV0ZWZmZWN0c3JhDQoNCkFURSBpcyAtMjM5LjYzOTIsIFNFID0gMjMuODI0MDIsIHogPSAtMTAuMDYsIDk1JSBDSSA9IFstMjg2LjMzMzQsIC0xOTIuOTQ1XS4NCg0KQVRUIGlzIC0yMjMuMzAxNywgU0UgPSAyMi43NDIyLCB6ID0gLTkuODIsIDk1JSBDSSA9IFstMjY3Ljg3NTUsIC0xNzguNzI3OF0uDQoNCg0KIyMge21hcmdpbmFsZWZmZWN0c30NCg0KRml0IHRoZSBtb2RlbDoNCg0KYGBge3J9DQpnbW9kIDwtIGxtKGJ3ZWlnaHQgfiBtYnNtb2tlICogKHByZW5hdGFsMSArIG1tYXJyaWVkICsgbWFnZSArIGZiYWJ5KSwgZGF0YSA9IGRhdCkNCmBgYA0KDQpBVEU6DQoNCmBgYHtyfQ0KYXZnX2NvbXBhcmlzb25zKA0KICBnbW9kLA0KICB2YXJpYWJsZXMgPSAibWJzbW9rZSIsDQogIHZjb3YgPSAiSEMxIg0KKSB8Pg0KICBwcmludChzdHlsZSA9ICJkYXRhLmZyYW1lIikNCmBgYA0KDQpUaGUgZXN0aW1hdGUgaXMgc3BvdCBvbiwgdGhlIFNFIGlzIHZlcnkgY2xvc2UgdG8gU3RhdGEncyAyMy44MjQwMiAoSSB0aGluayBIQzEgaXMgdGhlIGZsYXZvdXIgb2Ygcm9idXN0IFNFIHByb3ZpZGVkIGJ5IFN0YXRhKS4NCg0KQ2hlY2sgQVRUOg0KDQpgYGB7cn0NCmF2Z19jb21wYXJpc29ucygNCiAgZ21vZCwNCiAgdmFyaWFibGVzID0gIm1ic21va2UiLA0KICB0eXBlID0gInJlc3BvbnNlIiwNCiAgbmV3ZGF0YSA9IHN1YnNldChkYXQsIG1ic21va2UgPT0gMSksDQogIHZjb3YgPSAiSEMxIg0KKSB8Pg0KICBwcmludChzdHlsZSA9ICJkYXRhLmZyYW1lIikNCmBgYA0KDQpBZ2FpbiwgdGhlIGVzdGltYXRlIGlzIGlkZW50aWNhbCBhbmQgU0UgY2xvc2UgdG8gU3RhdGEncyAyMi43NDIyLg0KDQojIyBSZXBsaWNhdGUgdGVmZmVjdHMgImJ5IGhhbmQiDQoNCmBgYHtyfQ0KYm9vdF9hdGUgPC0gZnVuY3Rpb24oZCwgaSkgew0KICB0aGVfZGF0IDwtIGRbaSxdDQogIGRhdDAgPC0gdGhlX2RhdCB8PiBkcGx5cjo6ZmlsdGVyKG1ic21va2UgPT0gMCkNCiAgZGF0MSA8LSB0aGVfZGF0IHw+IGRwbHlyOjpmaWx0ZXIobWJzbW9rZSA9PSAxKQ0KICBsbTAgPC0gbG0oYndlaWdodCB+IHByZW5hdGFsMSArIG1tYXJyaWVkICsgbWFnZSArIGZiYWJ5LCBkYXRhID0gZGF0MCkNCiAgbG0xIDwtIGxtKGJ3ZWlnaHQgfiBwcmVuYXRhbDEgKyBtbWFycmllZCArIG1hZ2UgKyBmYmFieSwgZGF0YSA9IGRhdDEpDQogIA0KICBkYXQwIDwtIGRhdDAgfD4NCiAgICBtdXRhdGUoYndlaWdodF8wID0gYndlaWdodCwNCiAgICAgICAgICAgYndlaWdodF8xID0gcHJlZGljdChsbTEsIG5ld2RhdGEgPSBkYXQwKSkNCiAgDQogIGRhdDEgPC0gZGF0MSB8Pg0KICAgIG11dGF0ZShid2VpZ2h0XzEgPSBid2VpZ2h0LA0KICAgICAgICAgICBid2VpZ2h0XzAgPSBwcmVkaWN0KGxtMCwgbmV3ZGF0YSA9IGRhdDEpKQ0KICANCiAgZGF0X2FsbCA8LSBiaW5kX3Jvd3MoZGF0MCwgZGF0MSkgfD4NCiAgICBtdXRhdGUodGUgPSBid2VpZ2h0XzEgLSBid2VpZ2h0XzApDQogIA0KICByZXNfQVRFIDwtIGRhdF9hbGwgfD4NCiAgICBzdW1tYXJpc2UoYXRlID0gbWVhbih0ZSkpDQogIA0KICByZXNfQVRUIDwtIGRhdF9hbGwgfD4NCiAgICBkcGx5cjo6ZmlsdGVyKG1ic21va2UgPT0gMSkgfD4NCiAgICBzdW1tYXJpc2UoYXR0ID0gbWVhbih0ZSkpDQogIA0KICBjKGF0ZSA9IHJlc19BVEUkYXRlLCBhdHQgPSByZXNfQVRUJGF0dCkNCn0NCmBgYA0KDQoNCkkndmUgcGFja2FnZWQgaXQgYWxsIHVwIGEgZnVuY3Rpb24gcmVhZHkgZm9yIGJvb3RzdHJhcHBpbmcsIHdoaWNoIGFwcGxpZWQgdG8gdGhlIG9yaWdpbmFsIGRhdGEgcmVwbGljYXRlcyB0ZWZmZWN0czoNCg0KYGBge3J9DQpib290X2F0ZShkYXQsIDE6bnJvdyhkYXQpKQ0KYGBgDQoNCk5vdyBib290c3RyYXAgX2Jvb3RfYXRlXyB0byBnZXQgU0VzOg0KDQpgYGB7cn0NCnNldC5zZWVkKDE5MDgyMDI0KQ0KdGljKCkNCmJvb3RzIDwtIGJvb3QoZGF0YSA9IGRhdCwgc3RhdGlzdGljID0gYm9vdF9hdGUsIFIgPSAxNDk5KQ0KdG9jKCkNCmJlZXAoMikNCmBgYA0KDQoNCmBgYHtyfQ0KcmVzIDwtIHN1bW1hcnkoYm9vdHMpIHw+IGFzLmRhdGEuZnJhbWUoKQ0Kcm93bmFtZXMocmVzKSA8LSBuYW1lcyhib290cyR0MCkNCnJlc0NJIDwtIGJpbmRfY29scyhyZXMsIENvbmZpbnQoYm9vdHMsIHR5cGUgPSAicGVyYyIpKQ0KcmVzQ0kNCmBgYA0KDQpTdGF0YSBzYWlkOg0KDQpBVEUgaXMgLTIzOS42MzkyLCBTRSA9IDIzLjgyNDAyLCA5NSUgQ0kgPSBbLTI4Ni4zMzM0LCAtMTkyLjk0NV0uDQoNCkFUVCBpcyAtMjIzLjMwMTcsIFNFID0gMjIuNzQyMiwgOTUlIENJID0gWy0yNjcuODc1NSwgLTE3OC43Mjc4XS4NCg0KDQo=