Difference-in-differences

library(tidyverse)
library(targets)
library(modelsummary)
library(tinytable)
library(scales)
library(ggdag)
library(dagitty)
library(parameters)

tar_config_set(
  store = here::here('_targets'),
  script = here::here('_targets.R')
)

did <- tar_read(data_did)

invisible(list2env(tar_read(graphic_functions), .GlobalEnv))
set_annotation_fonts()

gof_map <- tribble(
  ~raw,            ~clean,               ~fmt, ~omit,
  "nobs",          "N",                  0,    FALSE,
  "r.squared",     "$R^2$",          2,    FALSE,
  "adj.r.squared", "\\(R^2\\) adjusted", 2,    FALSE
)

Models

model_small <- lm(
  prosocial_index ~ school + factor(year) + school*factor(year),
  data = filter(did, year %in% c(2022, 2023))
)

model_small_controls <- lm(
  prosocial_index ~ school + factor(year) + school*factor(year) +
    social_awareness + age + gpa + income,
  data = filter(did, year %in% c(2022, 2023))
)

model_full <- lm(
  prosocial_index ~ school + factor(year) + I(school == "School B" & year >= 2023),
  data = did
)

model_full_controls <- lm(
  prosocial_index ~ school + factor(year) + I(school == "School B" & year >= 2023) +
    social_awareness + age + gpa + income,
  data = did
)

models <- list(
  "Interaction term only" = model_small,
  "Interaction term + covariates" = model_small_controls,
  "Interaction term only (all years)" = model_full,
  "Interaction term + covariates (all years)" = model_full_controls
)
modelsummary(
  models,
  statistic = "[{conf.low}, {conf.high}]",
  fmt = 2,
  gof_map = gof_map,
  add_columns = tibble("True effect" = c(NA, NA, 10)) |> magrittr::set_attr("position", 2)
)# |> 
True effect Interaction term only Interaction term + covariates Interaction term only (all years) Interaction term + covariates (all years)
(Intercept) 65.63 -4.00 61.82 -7.44
[63.91, 67.34] [-11.07, 3.07] [60.42, 63.21] [-12.08, -2.80]
schoolSchool B 10.00 1.81 1.73 3.02 2.14
[-0.62, 4.24] [0.21, 3.24] [1.63, 4.41] [1.27, 3.01]
factor(year)2023 -0.18 1.09 3.86 4.47
[-2.61, 2.25] [-0.44, 2.61] [1.83, 5.89] [3.21, 5.74]
schoolSchool B × factor(year)2023 10.47 8.30
[7.04, 13.90] [6.14, 10.45]
social_awareness 0.47 0.47
[0.43, 0.51] [0.44, 0.49]
age 0.69 0.64
[0.47, 0.91] [0.50, 0.78]
gpa 3.29 3.82
[2.33, 4.25] [3.18, 4.45]
income 0.00 0.00
[0.00, 0.00] [0.00, 0.00]
factor(year)2021 1.77 1.71
[0.06, 3.47] [0.65, 2.77]
factor(year)2022 3.21 3.15
[1.50, 4.91] [2.08, 4.21]
factor(year)2024 5.25 6.16
[3.22, 7.28] [4.89, 7.43]
I(school == "School B" & year >= 2023)TRUE 8.80 7.76
[6.60, 11.00] [6.39, 9.14]
N 504 504 1260 1260
$R^2$ 0.21 0.70 0.24 0.70
\(R^2\) adjusted 0.21 0.70 0.23 0.70
  # style_tt(
  #   i = 3:4, j = 1:6, 
  #   background = clrs$Prism[6], color = "#ffffff", bold = TRUE
  # )
modelsummary(
  models,
  statistic = "[{conf.low}, {conf.high}]",
  fmt = 2,
  coef_map = c(
    `schoolSchool B:factor(year)2023` = "School B × 2023",
    `I(school == "School B" & year >= 2023)TRUE` = "School B × 2023"
  ),
  gof_map = gof_map,
  add_columns = tibble("True effect" = 10) |> magrittr::set_attr("position", 2)
)
True effect Interaction term only Interaction term + covariates Interaction term only (all years) Interaction term + covariates (all years)
School B × 2023 10.00 10.47 8.30 8.80 7.76
[7.04, 13.90] [6.14, 10.45] [6.60, 11.00] [6.39, 9.14]
N 504 504 1260 1260
$R^2$ 0.21 0.70 0.24 0.70
\(R^2\) adjusted 0.21 0.70 0.23 0.70
treatment_effects <- enframe(models) |> 
  mutate(params = map(value, \(x) model_parameters(x))) |> 
  unnest(params) |> 
  filter(Parameter %in% c("schoolSchool B:factor(year)2023", 'I(school == "School B" & year >= 2023)TRUE')) |> 
  mutate(name = factor(name, levels = names(models)))

ggplot(treatment_effects, aes(x = Coefficient, y = fct_rev(name))) +
  geom_vline(xintercept = 10, color = clrs$Prism[2]) +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high)) +
  labs(x = "Treatment effect", y = NULL) +
  theme_np(base_size = 16) +
  theme(panel.grid.major.y = element_blank())