1. Download Legewie_ESS_02.dta from Absalon, then copy in this starter code. It defines the attack window and prepares the data — keeping only Portuguese mainstream respondents:
pacman::p_load(tidyverse, haven, estimatr, modelsummary)
ESS_raw <- read_dta("Legewie_ESS_02.dta") # from your project folder
event_date <- as.Date("2002-10-13")   # the Bali attack
win_begin  <- as.Date("2002-09-14")
win_end    <- as.Date("2002-10-20")

prepare <- function(data, countries) {
  data %>%
    mutate(
      int_date = as.Date(sprintf("%s-%s-%s", inwyr, inwmm, inwdd)),
      treat = case_when(
        int_date > event_date & int_date <= win_end   ~ "After the attack",
        int_date < event_date & int_date > win_begin  ~ "Before the attack",
        TRUE ~ NA_character_) %>% fct_relevel("Before the attack", "After the attack"),
      # Anti-immigrant index: average 7 items, z-standardise, flip so high = xenophobic
      anti_immi = rowMeans(across(c(imtcjob, imbleco, imbgeco, imueclt,
                                    imwbcnt, imwbcrm, imbghct)), na.rm = TRUE) %>%
        scale() %>% as.numeric(),
      anti_immi = max(anti_immi, na.rm = TRUE) - anti_immi,
      across(c(brncntr, mocntr, facntr), as_factor),
      pspwght = pweight * dweight
    ) %>%
    filter(str_detect(cntry, countries) &                 # <- sample restriction
             brncntr == "yes" & mocntr == "yes" & facntr == "yes") %>%
    select(treat, anti_immi, cntry, pspwght) %>%
    drop_na()
}

ESS <- prepare(ESS_raw, "PT") # Portugal only

Stuck?

  1. Estimate the treatment effect with a weighted OLS of anti_immi on treat. Among the Portuguese, the average causal effect of the Bali attack is standard deviations (two digits; point or comma is fine).
ols_pt <- lm_robust(anti_immi ~ treat, data = ESS, weights = pspwght)
modelsummary(list("Xenophobia (PT)" = ols_pt), stars = TRUE,
             coef_rename = c("treatAfter the attack" = "After the attack"),
             gof_map = c("nobs", "r.squared"))
Xenophobia (PT)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.621***
(0.082)
After the attack 0.330**
(0.116)
Num.Obs. 291
R2 0.032
  1. Its standard error is — less than half the coefficient, so the effect is statistically significant (the t-value clears the usual threshold of ).

  2. Make a coefficient plot of the Portuguese effect (drop the intercept).

plotdata_pt <- ols_pt %>% tidy() %>%
  filter(term != "(Intercept)") %>%
  mutate(term = "After the attack", cntry = "Portugal")

ggplot(plotdata_pt, aes(y = estimate, x = cntry,
                        ymin = conf.low, ymax = conf.high)) +
  geom_hline(yintercept = 0, color = "#901A1E", lty = "dashed") +
  geom_pointrange() + coord_flip() +
  labs(x = "", y = "Change in xenophobia after the attack (SD)") +
  theme_minimal()

  1. Now add Sweden: change the sample restriction to "PT|SE", then estimate the effect for Sweden only (data = ESS2 %>% filter(cntry == "SE")). The Swedish effect is , and it is .
ESS2 <- prepare(ESS_raw, "PT|SE")
ols_se <- lm_robust(anti_immi ~ treat, data = ESS2 %>% filter(cntry == "SE"),
                    weights = pspwght)
modelsummary(list("Xenophobia (SE)" = ols_se), stars = TRUE,
             coef_rename = c("treatAfter the attack" = "After the attack"),
             gof_map = c("nobs", "r.squared"))
Xenophobia (SE)
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001
(Intercept) 3.096***
(0.080)
After the attack 0.011
(0.103)
Num.Obs. 328
R2 0.000
  1. Write one sentence (as a # comment) comparing the two countries, then check:

The Bali attack raised xenophobia by about 0.33 SD in Portugal (significant) but only 0.01 SD in Sweden (not significant) — the same shock produced different reactions in different societies. Look at you: this is exactly the kind of natural-experiment analysis published in the American Journal of Sociology.


Bonus (for the fast): combine both into one coefficient plot with bind_rows(plotdata_pt, plotdata_se, .id = "id") and colour by country.

plotdata_se <- ols_se %>% tidy() %>%
  filter(term != "(Intercept)") %>% mutate(cntry = "Sweden")

bind_rows(plotdata_pt, plotdata_se) %>%
  ggplot(aes(y = estimate, x = reorder(cntry, estimate),
             ymin = conf.low, ymax = conf.high)) +
  geom_hline(yintercept = 0, color = "#901A1E", lty = "dashed") +
  geom_pointrange() + coord_flip() +
  labs(x = "", y = "Change in xenophobia after the attack (SD)") +
  theme_minimal()