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