Does Exposure to the Refugees Increase Support for Extreme-Right Parties?
Note
This slide deck is designed as a didactic model for how to structure an academic presentation based on a quantitative research design.
It illustrates best practices in motivating a research question, situating it in the literature, presenting theory and identification strategies, and communicating empirical findings clearly and concisely.
For reasons of brevity, the presentation reproduces only a subset of the full results.
Please check the accompanying paper for the complete analysis and findings. Please also check the original paper:
Dinas E, Matakos K, Xefteris D, Hangartner D. Waking Up the Golden Dawn: Does Exposure to the Refugee Crisis Increase Support for Extreme-Right Parties? *Political Analysis&. 2019;27(2):244-254. doi:10.1017/pan.2018.48
Main question:
Did municipalities in Greece that received sudden refugee arrivals in 2015
experience higher vote shares for Golden Dawn?
Labor migration → far-right support
Competition over jobs/welfare fuels backlash
(Barone et al. 2016; Mendez and Cutillas 2014; Halla, Wagner, and Zweimüller 2015; Brunner and Kuhn 2014; Becker and Fetzer 2016)
Mixed evidence
Gap: Greece
# ------------------------------------------------------------
# Goal: Read election + map data for Greece, aggregate to the
# municipality-year level, compute vote shares/turnout,
# merge with geographies, and plot refugee arrivals per capita.
# This version adds step-by-step comments for R beginners.
# ------------------------------------------------------------
# --- 0) Housekeeping ---------------------------------------------------------
# Remove all objects from memory so the script starts fresh.
rm(list = ls())
# --- 1) Load packages --------------------------------------------------------
# "haven" reads .dta (Stata) files. "dplyr" is for data wrangling.
# "sf" handles spatial (map) data. "ggplot2" makes plots.
# "here" helps build file paths that work on any computer.
# "ggpubr" arranges multiple ggplots in a grid.
library(haven)
library(dplyr)
library(sf)
sf::sf_use_s2(FALSE) # turn off spherical geometry to avoid certain topology errors
library(ggplot2)
library(here)
library(ggpubr)
# --- 2) Read data from the /data_final folder -------------------------------
# NOTE: `here("data_final", "file.ext")` builds a path like
# "<your-project-root>/data_final/file.ext". Make sure your working
# directory is the project root (where your .Rproj lives).
countries <- read_sf(here("data_final", "countries.shp"))
gr_muni <- read_sf(here("data_final", "gr_muni.shp"))
gr_lemnos_island <- read_sf(here("data_final", "gr_lemnos_island.shp"))
gr_limnos_island <- read_sf(here("data_final", "gr_limnos_island.shp"))
all_data <- read_dta(here("data_final", "dinas_data.dta"), encoding = "UTF-8")
# TIP: If you get errors here, check that files exist and that all
# shapefiles share the same CRS (coordinate reference system).
# Use st_crs(object) to inspect and st_transform(...) to convert if needed.
# --- 3) Sort + group + compute totals at municipality-year level ------------
# We first sort the raw data (not strictly necessary, but helpful to read),
# then group by municipality & year so sums are within each municipality-year.
# The column names below (e.g., `valid`, `gd`, `nd`, etc.) must exist in your data.
all_data <- all_data %>%
arrange(township, municipality, perfecture, year) %>% # keep original order logic
group_by(municipality, year) %>% # group for within-muni-year sums
mutate(
sumall = sum(valid, na.rm = TRUE), # total valid votes
sumgd = sum(gd, na.rm = TRUE), # Golden Dawn votes (example)
sumnd = sum(nd, na.rm = TRUE), # New Democracy votes (example)
sumsyriza = sum(syriza, na.rm = TRUE),
sumpsk = sum(pasok, na.rm = TRUE), # PASOK
sumkke = sum(kke, na.rm = TRUE),
sumanel = sum(anel, na.rm = TRUE),
sumreg = sum(registered, na.rm = TRUE) # number registered to vote
)
# --- 4) Create vote shares + turnout ----------------------------------------
# Shares = party_total / all valid votes. Turnout = valid / registered.
# Watch for division by zero; NA/NaN can occur if `sumall` or `sumreg` is 0.
all_data <- all_data %>%
mutate(
gdvote = sumgd / sumall,
ndvote = sumnd / sumall,
syrizavote = sumsyriza / sumall,
pasokvote = sumpsk / sumall,
kkevote = sumkke / sumall,
anelvote = sumanel / sumall,
turnout = sumall / sumreg
)
# --- 5) Keep a single row per municipality-year -----------------------------
# The raw file may have multiple rows per municipality-year (e.g., by township).
# We create a within-group row number and keep the first one (any will do
# once we've already computed muni-year sums above).
datamuni <- all_data %>%
group_by(geo_muni_id, year) %>% # use the stable geocode id for join later
mutate(id = dplyr::row_number()) %>%
filter(id == 1) %>% # keep one row per geo_muni_id-year
arrange(geo_muni_id, year) %>%
group_by(geo_muni_id) %>%
mutate(id2 = dplyr::row_number()) # a running index within each municipality
# --- 6) Focus on the 2016 election ------------------------------------------
datamuni_2016 <- subset(datamuni, year == 2016)
# --- 7) Merge tabular data to the municipality shapes ------------------------
# left_join keeps all geometries from gr_muni and adds the 2016 data columns.
# Keys: `ge_mn_d` in the shapefile == `geo_muni_id` in the data.
greece_merge <- left_join(gr_muni, datamuni_2016, by = c("ge_mn_d" = "geo_muni_id"))
# --- 8) Handle missing + extreme values in arrivals per capita --------------
# Remove rows where `trarrprop` (arrivals per capita) is missing so the map
# has valid values to color. Then cap extreme values to improve readability.
greece_merge <- subset(greece_merge, !is.na(trarrprop))
# Make a working copy for plotting + truncation
greece_merge2 <- greece_merge
# One municipality has a very large value (> 100). Following the paper:
# "We truncate the variable at 5 arrivals per capita (i.e., 5 refugees per
# resident), which corresponds to the 99.7th percentile of the non‑zero distribution."
# Doing this avoids a legend dominated by outliers.
greece_merge2$trarrprop[greece_merge2$trarrprop > 100] <- 5
# --- 9) Build two maps (zoomed-in Aegean and full Greece) -------------------
# NOTE: `geom_sf()` draws map layers. The `fill` aesthetic colors municipalities
# by `trarrprop`. The color scale runs from green (low) to red (high).
# (a) Zoomed map around 24–29 E longitude, 35.8–40.2 N latitude
fig1a1 <- ggplot() +
geom_sf(data = countries, fill = "grey80") +
geom_sf(data = greece_merge2, aes(fill = trarrprop)) +
scale_fill_gradient(
low = "green", high = "red", na.value = "white",
name = "Refugee Arrivals\n(p.c.)\nJan–Sep 2015"
) +
# Draw and label Lemnos/Limnos to match the original figure
geom_sf(data = gr_lemnos_island, color = "black", fill = NA, linewidth = 0.3) +
geom_sf(data = gr_limnos_island, color = "black", fill = NA, linewidth = 0.3) +
geom_sf_label(data = gr_limnos_island, aes(label = name),
size = 3, nudge_x = 0.35, nudge_y = 0.35, alpha = 0.2) +
geom_sf_label(data = gr_lemnos_island, aes(label = name),
size = 3, nudge_x = -0.35, nudge_y = -0.35, alpha = 0.2) +
coord_sf(xlim = c(24, 29), ylim = c(35.8, 40.2)) +
theme_bw() +
theme(
legend.title = element_text(size = 12, face = "bold"),
legend.text = element_text(size = 10),
legend.key = element_rect(fill = "yellow", color = "grey", size = 1),
legend.key.size = unit(1.5, "lines"),
legend.background = element_rect(fill = "white", color = "grey"),
legend.box.background = element_rect(color = "grey"),
# Position legend inside the plotting area at the top-right corner
legend.position = c(1, 1),
legend.justification = c("right", "top"),
axis.title.x = element_blank(),
axis.title.y = element_blank()
)
# (b) Wider map of Greece
fig1a2 <- ggplot() +
geom_sf(data = countries, fill = "grey80") +
geom_sf(data = greece_merge2, aes(fill = trarrprop)) +
scale_fill_gradient(
low = "green", high = "red", na.value = "white",
name = "Refugee Arrivals\n(p.c.)\nJan–Sep 2015"
) +
geom_sf(data = gr_lemnos_island, color = "black", fill = NA, linewidth = 0.3) +
geom_sf(data = gr_limnos_island, color = "black", fill = NA, linewidth = 0.3) +
# Labels are optional here; commented out to reduce clutter
# geom_sf_label(data = gr_limnos_island, aes(label = name), size = 3, nudge_x = 0.35, nudge_y = 0.35, alpha = 0.2) +
# geom_sf_label(data = gr_lemnos_island, aes(label = name), size = 3, nudge_x = -0.35, nudge_y = -0.35, alpha = 0.2) +
coord_sf(xlim = c(20, 29), ylim = c(34.5, 42)) +
theme_bw() +
theme(
legend.title = element_text(size = 12, face = "bold"),
legend.text = element_text(size = 10),
legend.key = element_rect(fill = "yellow", color = "grey", size = 1),
legend.key.size = unit(1.5, "lines"),
legend.background = element_rect(fill = "white", color = "grey"),
legend.box.background = element_rect(color = "grey"),
legend.position = c(1, 1),
legend.justification = c("right", "top"),
axis.title.x = element_blank(),
axis.title.y = element_blank()
)
# --- 10) Arrange the two plots side-by-side ----------------------------------
# `ggarrange()` puts multiple ggplots in a single figure.
# `common.legend = TRUE` puts one shared legend at the bottom.
row1 <- ggarrange(fig1a1, fig1a2, ncol = 2, common.legend = TRUE, legend = "bottom")
# --- 11) Optional: save to file ---------------------------------------------
# ggsave(here("figures", "refugee_arrivals_maps.png"), row1, width = 10, height = 6, dpi = 300)
# Print to the RStudio plotting pane (or your device if using ggsave later)
print(row1)Contact theory → contact reduces prejudice
Conflict theory → competition fuels hostility
Greece: refugees stayed briefly (≈48 hours) →
Argument: exposure alone can increase far-right support
# ------------------------------------------------------------
# Goal: Draw a simple Directed Acyclic Graph (DAG) that represents
# a theory of how exposure to refugees affects Golden Dawn (GD) vote share.
# This DAG uses ggdag + dagitty + ggplot2.
#
# Key idea: We draw *nodes* (circles) for variables and *arrows* for
# hypothesized causal effects. We'll also show how to make
# arrows shorter so they don't touch the nodes.
# ------------------------------------------------------------
# --- 0) Playground: tweak these safely -------------------------------
node_size <- 25 # how big each node (circle) is
node_alpha <- 0.5 # transparency of node fill (0 = invisible, 1 = solid)
arrowhead_pt <- 10 # arrowhead size in points (this is the *tip* triangle)
trim <- 0.20 # how much of the arrow *shaft* to chop off from BOTH ends
# e.g., 0.20 = remove 20% at the start AND 20% at the end
# so the arrow in the middle is 60% of the original length
# --- 1) Load required packages ----------------------------------------------
# ggdag → helper functions to draw DAGs in ggplot2
# dagitty → defines DAG objects and relationships between variables
# ggplot2 → plotting system used to visualize the DAG
library(ggdag)
library(dagitty)
library(ggplot2)
# --- 2) Define the DAG structure --------------------------------------------
# dagify() creates a DAG object where you specify arrows (causal paths).
# The notation A ~ B means "A is caused by B".
# Here:
# GDvote ← threat (Golden Dawn vote share caused by perceived threat)
# threat ← exposure (Threat is caused by exposure to refugees)
# exposure ← distance (Exposure caused by proximity to Turkey)
# Labels: provide readable names for the DAG nodes (with line breaks).
# Coords: x and y positions of each node (for nice plotting layout).
theory_dag <- dagify(
GDvote ~ threat, # DV (Golden Dawn vote share) depends on threat
threat ~ exposure, # Threat depends on exposure
exposure ~ distance, # Exposure depends on distance
labels = c(
GDvote = "Golden Dawn\nVote Share (DV)",
threat = "Perceived Threat\n(Mediator)",
exposure = "Refugee Arrivals",
distance = "Proximity to Turkey"
),
coords = list(
x = c(distance = 0, exposure = 1, threat = 2, GDvote = 3),
y = c(distance = 1, exposure = 1, threat = 1, GDvote = 1)
)
)
# --- 3) Convert DAG into a data frame for ggplot ----------------------------
# tidy_dagitty() extracts coordinates, labels, and edges for plotting.
# as.data.frame() makes it into a tibble/data frame usable by ggplot.
dag_df <- as.data.frame(tidy_dagitty(theory_dag, layout = "auto"))
# Extract plotting limits (so the nodes don’t touch the plot borders).
min_x <- min(dag_df$x); max_x <- max(dag_df$x)
min_y <- min(dag_df$y); max_y <- max(dag_df$y)
error <- 0.2 # small padding around the plot
# --- 3.5) Make arrow *segments* shorter and floating between nodes ----------
# Important: In ggplot, the "arrow()" only controls the arrowhead (the little
# triangle tip). It does NOT control the length of the shaft. Shaft length is
# determined by the start (x, y) and end (xend, yend) coordinates.
#
# To make arrow shafts shorter AND not start at the node centers, we:
# 1) Find all edge rows (they have xend/yend values).
# 2) Move the start point forward by 'trim' proportion along the edge.
# 3) Move the end point backward by 'trim' proportion along the edge.
# The result: the arrow appears between nodes without touching them.
edges_df <- subset(dag_df, !is.na(xend) & !is.na(yend))
# Safety checks: ensure trim is between 0 and 0.49
# (If trim >= 0.5 the start could pass the end!)
trim <- max(min(trim, 0.49), 0.00)
# Compute trimmed start (xstart_trim, ystart_trim) and end (xend_trim, yend_trim)
edges_df$xstart_trim <- edges_df$x + trim * (edges_df$xend - edges_df$x)
edges_df$ystart_trim <- edges_df$y + trim * (edges_df$yend - edges_df$y)
edges_df$xend_trim <- edges_df$xend - trim * (edges_df$xend - edges_df$x)
edges_df$yend_trim <- edges_df$yend - trim * (edges_df$yend - edges_df$y)
# --- 4) Plot the DAG --------------------------------------------------------
# Each node is a big semi-transparent point with a label on top.
# Edges (arrows) are drawn with geom_dag_edges_arc using the *trimmed* endpoints.
ggplot(data = dag_df) +
# Draw big transparent points for each node
geom_dag_point(aes(x = x, y = y), size = node_size, alpha = node_alpha) +
# Add text labels (only once per node → !duplicated(label))
geom_label(
data = subset(dag_df, !duplicated(label)),
aes(x = x, y = y, label = label),
fill = alpha("white", 0.8)
) +
# Draw arrows between nodes (edges of the DAG)
# NOTE: arrow(length = ...) controls ONLY the arrowhead size (the tip).
# The shaft length is controlled by the start/end coordinates we just trimmed.
geom_dag_edges_arc(
data = edges_df,
aes(x = xstart_trim, y = ystart_trim, xend = xend_trim, yend = yend_trim),
curvature = 0.0,
arrow = grid::arrow(length = grid::unit(arrowhead_pt, "pt"), type = "closed")
) +
# Set x- and y-limits to keep everything inside the plot
coord_sf(
xlim = c(min_x - error, max_x + error),
ylim = c(min_y - error, max_y + error)
) +
# Minimal background → removes axes, gridlines, etc.
theme_void()Figure 2
# ------------------------------------------------------------
# Goal: Prepare panel features (trends, lags, diffs, dummies),
# build grouped summaries with CIs, and plot GD vote % over time
# by treatment status.
# ------------------------------------------------------------
# --- 0) Make sure 'year' is numeric -----------------------------------------
# Many imported datasets store years as character or labelled types.
# Converting to integer avoids problems when sorting, lagging, etc.
datamuni$year <- as.integer(datamuni$year)
# --- 1) Municipality-specific linear trends (Model 2, etc.) -----------------
# Some specifications include a separate linear trend for each municipality.
# Your model expects columns named s2trend, s3trend, ..., s94trend explicitly.
# Each trend is "year" for that municipality, and 0 for all others.
# NOTE:
# - This starts at m = 2 to match your model’s expected naming.
# - Requires 'datamuni$muni' to be an integer ID in [1..94].
for (m in 2:94) {
trend_col <- paste0("s", m, "trend")
datamuni[[trend_col]] <- ifelse(datamuni$muni == m, datamuni$year, 0L)
}
# --- 2) Lags (only for GD in percentage terms) -------------------------------
# We create:
# - gdper : Golden Dawn vote share in percent
# - gdlagper : 1-period lag of GD vote share (in percent)
# - gdlag3per : 3-period lag (in percent)
# Ordering by muni, then year, ensures lags are computed in time order.
# IMPORTANT:
# - Lags produce NA in the first available years per municipality.
# - 'gdvote' is assumed to be a proportion (0–1). Multiply by 100 for %.
library(dplyr)
datamuni <- datamuni %>%
arrange(muni, year) %>%
group_by(muni) %>%
mutate(
gdper = gdvote * 100, # main DV in percent
gdlagper = dplyr::lag(gdvote, 1) * 100, # 1-year lag, in percent
gdlag3per = dplyr::lag(gdvote, 3) * 100 # 3-year lag, in percent
) %>%
ungroup()
# --- 3) Differences for Reduced Form / Placebo figures -----------------------
# First difference of GD % (current minus 1-year lag), used in fig4a.
# A "placebo" style difference using 1-year-lag minus 3-year-lag, used in fig4b.
# NOTE: These will be NA where required lags are missing.
datamuni <- datamuni %>%
mutate(
gdperdif = gdper - gdlagper, # ΔGD% (t − t−1)
gdperdif3 = gdlagper - gdlag3per # ΔGD% (t−1 − t−3)
)
# --- 4) Year dummies + interactions (Table S5 / Fig. S4) ---------------------
# Create indicator (dummy) variables for specific years and interact with logdist.
# Here, y1..y4 correspond to years 2012, 2013, 2015, 2016 respectively.
# The interactions (y*dist) let the effect of distance vary by those years.
# Assumes 'logdist' already exists (e.g., log(distance to Turkey)).
datamuni <- datamuni %>%
mutate(
y1 = as.integer(year == 2012),
y2 = as.integer(year == 2013),
y3 = as.integer(year == 2015),
y4 = as.integer(year == 2016),
y1dist = y1 * logdist,
y2dist = y2 * logdist,
y3dist = y3 * logdist,
y4dist = y4 * logdist
)
# --- 5) Other percent levels for appendix summaries --------------------------
# Convert other commonly used variables to percent levels (no lags/diffs here).
# 'ndvote' and 'turnout' are assumed to be proportions (0–1).
datamuni <- datamuni %>%
mutate(
ndper = ndvote * 100, # New Democracy in %
turnper = turnout * 100 # Turnout in %
)
# --- 6) Grouped means, SDs, and 95% CIs by treatment & year ------------------
# We compute:
# - mean GD% (gdper_mean)
# - SD of GD% (gdper_sd)
# - sample size (count_n)
# - standard error (se = sd/sqrt(n))
# - 95% CI ≈ mean ± 1.96 * se (normal approx)
# NOTE:
# - If your data contain NA values, consider mean(..., na.rm = TRUE) / sd(..., na.rm = TRUE).
# - 'treatment_group' is assumed to be coded (e.g., 0 = Control, 1 = Treated).
df_muni_avg2 <- datamuni %>%
mutate(gdper = gdvote * 100) %>%
group_by(treatment_group, year) %>%
summarize(
gdper_mean = mean(gdper), # add na.rm = TRUE if needed
gdper_sd = sd(gdper), # add na.rm = TRUE if needed
count_n = n(),
.groups = "drop"
) %>%
mutate(
se_gdper = gdper_sd / sqrt(count_n),
gdcilo = gdper_mean - 1.96 * se_gdper, # lower 95% bound
gdcihi = gdper_mean + 1.96 * se_gdper # upper 95% bound
)
# --- 7) Plot: GD% over time by treatment group with 95% CIs ------------------
# - Lines/points show average GD% over time within each group.
# - Error bars visualize 95% confidence intervals.
# - Colors: blue = Control, red = Treated (per your manual scale).
# - Y-axis fixed to [2, 11] with 1-pt ticks for readability (match your figure).
fig2a <- ggplot(
df_muni_avg2,
aes(x = year, y = gdper_mean,
color = as.factor(treatment_group),
group = treatment_group)
) +
geom_line(size = 1, position = position_dodge(width = 0.2)) +
geom_point(size = 2.5, position = position_dodge(width = 0.2)) +
geom_errorbar(aes(ymin = gdcilo, ymax = gdcihi),
width = 0.2,
position = position_dodge(width = 0.2)) +
scale_color_manual(
values = c("blue", "red"),
labels = c("Control", "Treated"),
name = "Group"
) +
theme_bw() +
scale_y_continuous(
limits = c(2, 11),
breaks = seq(2, 11, by = 1)
) +
theme(
legend.position = c(0.00, 1.00), # place legend at top-left inside plot
legend.justification = c("left", "top"),
legend.background = element_rect(fill = "white", color = "black")
)
# --- (Optional) Save the figure to disk --------------------------------------
# ggsave(here::here("figures", "gd_trends_by_treatment.png"),
# fig2a, width = 7, height = 4.5, dpi = 300)
# Display the figure
print(fig2a)Figure 3
Exposure → ~2 % increase in GD vote share (≈ 44% increase relative to baseline)
Robust to municipality trends, placebo tests
# ------------------------------------------------------------
# Goal: Estimate fixed effects OLS models of Golden Dawn vote share
# using different treatment variables (binary vs. arrivals proportion),
# with/without municipality-specific trends, and report results in a table.
# ------------------------------------------------------------
# --- 1) Load packages --------------------------------------------------------
# fixest → fast and flexible fixed effects regressions
# broom → convert model output to a tidy data frame
# dplyr → for filtering results
# modelsummary → for producing nice regression tables
library(fixest)
library(broom)
library(dplyr)
library(modelsummary)
# --- 2) Municipality-level models -------------------------------------------
# Dataset: datamuni (panel of municipalities × years)
# Outcomes:
# - gdper : GD vote share in % (DV)
# - gdlagper : lagged GD vote share in %
# Main explanatory variables:
# - treatment : binary treatment indicator
# - trarrprop : refugee arrivals per capita (continuous treatment)
# FE structure:
# - | muni → municipality fixed effects
# Time dummies:
# - i(year) or factor(year)
# SEs:
# - cluster = ~muni (robust at municipality level)
###########################
# Models 1, 2, 3, 4, 9, 10, 11, 12
###########################
# Model 1: GD% on binary treatment + year FE + muni FE; cluster by muni
model1 <- feols(gdper ~ i(year) + treatment | muni,
data = datamuni, cluster = ~muni)
coef1 <- tidy(model1) %>% filter(term == "treatment")
# Model 2: Add municipality-specific linear trends (s2trend ... s94trend)
trend_vars <- paste0("s", 2:94, "trend")
fmla <- as.formula(
paste("gdper ~ i(year) + treatment +",
paste(trend_vars, collapse = " + "),
"| muni")
)
model2 <- feols(fmla, data = datamuni, cluster = ~muni)
coef2 <- tidy(model2) %>% filter(term == "treatment")
# Model 3: Lagged DV (gdlagper) on treatment + FE
model3 <- feols(gdlagper ~ i(year) + treatment | muni,
data = datamuni, cluster = ~muni)
coef3 <- tidy(model3) %>% filter(term == "treatment")
# Model 4: Lagged DV + treatment + muni trends
fmla <- as.formula(
paste("gdlagper ~ i(year) + treatment +",
paste(trend_vars, collapse = " + "),
"| muni")
)
model4 <- feols(fmla, data = datamuni, cluster = ~muni)
coef4 <- tidy(model4) %>% filter(term == "treatment")
# Model 9: Replace treatment with arrivals per capita (trarrprop)
model9 <- feols(gdper ~ i(year) + trarrprop | muni,
data = datamuni, cluster = ~muni)
coef9 <- tidy(model9) %>% filter(term == "trarrprop")
# Model 10: Arrivals per capita + muni trends
fmla2 <- as.formula(
paste("gdper ~ trarrprop + i(year) +",
paste(trend_vars, collapse = " + "),
"| muni")
)
model10 <- feols(fmla2, data = datamuni, cluster = ~muni)
coef10 <- tidy(model10) %>% filter(term == "trarrprop")
# Model 11: Lagged DV (gdlagper) + arrivals per capita
model11 <- feols(gdlagper ~ i(year) + trarrprop | muni,
data = datamuni, cluster = ~muni)
coef11 <- tidy(model11) %>% filter(term == "trarrprop")
# Model 12: Lagged DV + arrivals per capita + muni trends
fmla <- as.formula(
paste("gdlagper ~ factor(year) + trarrprop +",
paste(trend_vars, collapse = " + "),
"| muni")
)
model12 <- feols(fmla, data = datamuni, cluster = ~muni)
coef12 <- tidy(model12) %>% filter(term == "trarrprop")
# --- 3) Notes ---------------------------------------------------------------
# - i(year) and factor(year) both add year dummies (fixed effects for time).
# - tidy() + filter(term == "...") pulls out only the coefficient of interest.
# - Clustered SEs should generally match the FE unit (here: municipality).
# - Trends s2trend...s94trend were created earlier.
# --- 4) Produce regression table --------------------------------------------
# Create a named list of models to display side by side
models <- list(
"(1)" = model1, "(2)" = model2, "(3)" = model3, "(4)" = model4,
"(9)" = model9, "(10)" = model10, "(11)" = model11, "(12)" = model12
)
# Use modelsummary to display results
modelsummary(
models,
coef_map = c(
treatment = "Binary treatment",
trarrprop = "Arrivals per capita"
),
estimate = "{estimate}{stars}", # show estimate with significance stars
statistic = "({std.error})", # show robust SEs in parentheses
stars = TRUE, # add stars for p < 0.1, 0.05, 0.01
gof_omit = "AIC|BIC|Log|R2", # omit some goodness-of-fit stats
output = "html" # can also use "latex", "markdown", etc.
)| (1) | (2) | (3) | (4) | (9) | (10) | (11) | (12) | |
|---|---|---|---|---|---|---|---|---|
| Binary treatment | 2.079*** | 2.112** | −0.040 | −0.055 | ||||
| (0.351) | (0.674) | (0.392) | (0.713) | |||||
| Arrivals per capita | 0.006 | 0.002 | 0.001 | −0.009*** | ||||
| (0.004) | (0.005) | (0.001) | (0.002) | |||||
| Num.Obs. | 380 | 380 | 285 | 285 | 379 | 379 | 284 | 284 |
| RMSE | 0.95 | 0.61 | 0.95 | 0.44 | 1.00 | 0.64 | 0.95 | 0.43 |
| Std.Errors | by: muni | by: muni | by: muni | by: muni | by: muni | by: muni | by: muni | by: muni |
| FE: muni | X | X | X | X | X | X | X | X |
# ------------------------------------------------------------
# Goal: Plot reduced-form and placebo relationships between
# logged distance to Turkey and changes in Golden Dawn vote share.
# ------------------------------------------------------------
# --- 1) Subset the data to 2016 only ----------------------------------------
# We focus on 2016 (the election of interest).
# 'datamuni' must already contain:
# - logdist : logged distance to Turkey
# - gdperdif : ΔGD% (current − lagged, Jan–Sep 2015 → main reduced form)
# - gdperdif3 : placebo difference (lagged − 3-lagged)
red <- subset(datamuni, year == 2016)
# --- 2) Reduced Form Figure -------------------------------------------------
# Scatterplot: x = log distance, y = change in GD vote share
# Add linear fit (stat_smooth with method = "lm")
# Limit axes so the figure matches published version (y: −5..5, x: 0.2..6.3)
fig4a <- ggplot(red, aes(x = logdist, y = gdperdif)) +
geom_point(shape = 1) + # open circles
ylim(-5, 5) + # y-axis limits
xlim(0.2, 6.3) + # x-axis limits
stat_smooth(method = "lm") + # add OLS fit line
xlab("Logged Distance in Km") + # x-axis label
ylab("Change in GD vote: Jan–Sep 2015") + # y-axis label
theme(legend.position = "none") + # no legend needed
ggtitle("Reduced Form") + # title
theme_bw() # clean theme
# --- 3) Placebo Figure ------------------------------------------------------
# Same plot setup, but outcome is gdperdif3 (placebo difference).
# The placebo test checks whether “fake” changes line up with distance.
fig4b <- ggplot(red, aes(x = logdist, y = gdperdif3)) +
geom_point(shape = 1) +
ylim(-5, 5) +
xlim(0.2, 6.3) +
stat_smooth(method = "lm") +
xlab("Logged Distance in Km") +
ylab("Change in GD vote: Jan–Sep 2015") +
theme(legend.position = "none") +
ggtitle("Placebo") +
theme_bw()
# --- 4) Arrange the two figures side by side --------------------------------
# ggarrange() puts both figures into one row with two columns.
ggarrange(fig4a, fig4b, ncol = 2)Figure 4
Exposure was symbolic + perceptual shock
Even without contact/competition:
Refugees function as distinct political shocks
Even brief exposure can reshape politics
Reframes debates beyond labor migration → focus on flash potential of crises
Exposure alone → causal rise in Golden Dawn support
Greece illustrates fragility of political systems to demographic shocks
Future research: - Cross-national tests - Media framing + survey data - Long-term consequences
Thank you!
Email: last_name@gmail.com
Website: https://username.github.io
Author (Affiliation): Waking Up the Golden Dawn