Does Exposure to 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 qualitative, case-oriented research design.
It illustrates best practices in motivating a research question, situating it in the literature, developing a theoretical framework, and tracing causal mechanisms with within-case evidence.
For reasons of brevity, the presentation reproduces only a subset of the evidence.
Please check the accompanying paper for the complete analysis. Please also check the original article:
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 different electoral dynamics compared to those that did not?
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 on refugees
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: Make a simple, readable line chart showing average
# Golden Dawn (GD) vote share over time for Exposed vs Not Exposed
# municipalities. This is a *descriptive* plot, not a test.
# ------------------------------------------------------------
# 0) Load packages ------------------------------------------------------------
# ggplot2 → plotting
# dplyr → "verbs" for data wrangling (mutate, group_by, summarize)
library(ggplot2)
library(dplyr)
# 2) Create a clean, small summary table for plotting -------------------------
# - gdvote is assumed to be a proportion (0–1), so multiply by 100.
# - "group" becomes a readable label for the legend.
df_muni_avg <- datamuni %>%
mutate(
gdper = gdvote * 100,
group = ifelse(treatment_group == 1, "Exposed", "Not Exposed")
) %>%
group_by(group, year) %>%
summarize(
gd_mean = mean(gdper, na.rm = TRUE), # average GD% for that group×year
n = dplyr::n() # how many municipalities in this cell
)
# 3) Make the plot ------------------------------------------------------------
ggplot(df_muni_avg, aes(x = year, y = gd_mean, color = group, group = group)) +
geom_line(linewidth = 1.2) + # thicker line for readability
geom_point(size = 3) + # add dots so each year is clear
scale_color_manual(values = c("Exposed" = "red", "Not Exposed" = "blue")) +
labs(
x = "Election Year",
y = "Golden Dawn Vote Share (%)",
color = "Municipalities",
title = "Golden Dawn Support in Exposed vs. Non-Exposed Municipalities",
subtitle = "A simple descriptive comparison over time"
) +
# theme_bw = clean base theme; base_size nudges fonts larger for slides
theme_bw(base_size = 13) +
# Small style tweaks: move legend to bottom, lighten gridlines
theme(
legend.position = "bottom",
panel.grid.minor = element_blank()
)Figure 3
Future Research:
Thank you!
Email: last_name@gmail.com
Website: https://username.github.io
Author (Affiliation): Waking Up the Golden Dawn