Code
# source("data_prep.R")
data <- read_rds("assets/ees_petition_data.rds")Mobilizing Europe’s citizens to take action on migration and climate change: Behavioral evidence from 27 EU member states (JEPP 2025)
Dataset from EES 2024 merged with country level indicators.
# source("data_prep.R")
data <- read_rds("assets/ees_petition_data.rds")Labels defined for graphs.
# Treatment group labels in data and for figures
tgg <- c("Climate (left)",
"Climate (right)",
"Migration (left)",
"Migration (right)")
names(tgg) <- tgg
tg <- c("CLI left", "CLI right", "MIG left", "MIG right")
treatment_groups <- as.list(tg)
names(treatment_groups) <- tgg
# Core country covariates
vs <- c(
"grievances",
"protest_participants",
"politicization",
"lfirstp",
"lsecondp",
"gov_lr"
)
vs_labels <- c(
"Grievances",
"Protest participants\n(per million per year)",
"Politicization\n(polarization x salience)",
"Lijphart 1",
"Lijphart 2",
"Left / Right \n(government orientation)"
)
vars <- as.list(vs)
names(vars) <- vs
# Additional country covariates
vs_other <-
c(
"sal",
"pol",
"vulnerability",
"cri",
"ims_abs",
"ims_perc",
"ref_abs",
"ref_perc",
"protest_events",
"protest_intensity"
)
vs_other_labs <-
c(
"Salience",
"Polarization",
"Vulnerability",
"Climate risk index",
"Immigrants (absolute)",
"Immigrants (per capita)",
"Refugees (absolute)",
"Refugees (per capita)",
"Protest events",
"Protest intensity"
)Country level data frame.
country_df <-
data |>
group_by(country, left, topic, treatment_grp) |>
summarize(position = mean(position, na.rm = TRUE),
outcome1b = mean(outcome1b, na.rm = TRUE),
grievances = mean(grievances, na.rm = TRUE),
politicization = mean(politicization, na.rm = TRUE),
protest_participants = mean(protest_participants, na.rm = TRUE)) |>
ungroup() |>
mutate(groups = haven::as_factor(treatment_grp),
Left = 1*(left == "Left"),
Left = Left - mean(Left),
Climate = 1*(topic == "Climate change"),
Climate = Climate - mean(Climate)
)data |>
group_by(Country) |>
summarize(
antimigration = mean(pos_mig, na.rm = TRUE)/10,
anticlimate = mean(pos_cli, na.rm = TRUE)/10) |>
ggplot(aes(antimigration, anticlimate, label = Country)) +
geom_text() +
xlab("average support: right position on migration") +
ylab("average support: right position on climate")data |>
ggplot(aes(position, outcome1b)) +
geom_smooth() +
facet_grid(topic ~ left) + ylim(0, NA)Sample estimation:
# Fit the logistic mixed-effects model
if(run)
rstanarm::stan_glmer(outcome1b ~ 1 + (1 | Country), data = data, family = binomial(link = "logit")) |>
write_rds("saved/M_1.1.rds")
posterior_samples <- read_rds("saved/M_1.1.rds") |> as.data.frame()# Extract the posterior samples of the random effects
# Get the unique country names
Countries <- unique(data$Country)
Countries_list <- as.list(Countries)
names(Countries_list) <- Countries
# Function to calculate predicted probabilities for each country
get_predicted_probs <- function(country, fit = posterior_samples) {
random_intercept <- fit |>
select(starts_with(paste0("b[(Intercept) Country:", country)))
common_intercept <- fit$`(Intercept)`
log_odds <- (common_intercept + random_intercept)[[1]]
# Convert log-odds to probabilities
data.frame(probs = exp(log_odds) / (1 + exp(log_odds)))
}
# Create a dataframe to store the results
results <- lapply(Countries_list, get_predicted_probs) |>
bind_rows(.id = "Country")
# Summarize the predicted probabilities
summary_results <- results %>%
group_by(Country) %>%
summarize(
expectation = mean(probs),
min_prob = min(probs),
max_prob = max(probs)
) |>
left_join(data |> group_by(Country) |> summarize(raw = mean(outcome1b))) |>
mutate(Country = fct_reorder(Country, expectation))
# Plot the results
ggplot(summary_results, aes(x = Country, y = expectation)) +
geom_point() +
geom_errorbar(aes(ymin = min_prob, ymax = max_prob)) +
labs(title = "Predicted Probabilities of Petition Signing by Country",
y = "Predicted Probability",
x = "Country") +
theme_minimal() + coord_flip() +
geom_point(aes(Country, raw), color = "red")# Prep data so that position is in line with the petition content
decomp_model <-
function(g)
rstanarm::stan_glmer(
outcome1b ~ 1 + position + (1 + position | Country),
family = binomial(link = "logit"),
data = data |> filter(groups == g)
)
if(run)
treatment_groups |>
lapply(decomp_model)|>
write_rds("saved/M_1.2.rds")
M_1.2 <- read_rds("saved/M_1.2.rds")
posterior_1.2 <- lapply(M_1.2, as.data.frame)lapply(posterior_1.2,
function(df)
df|> select(b = position, a = `(Intercept)`)) |>
bind_rows(.id = "petition") |>
mutate(y0 = 1/(1+exp(-a)),
y1 = 1/(1+exp(-a-b)),
change = y1 - y0) |>
ggplot() +
geom_histogram(aes(x = y0), fill = "red", alpha = .5) +
geom_histogram(aes(x = y1), fill = "blue", alpha = .5) + facet_wrap(~petition) +
xlab("Signing propensity")Decomposition function:
decomposition <- function(g) {
names(posterior_1.2) <- treatment_groups
posterior <- posterior_1.2[[g]]
c_df <- country_df |> filter(groups == g)
X_diff <- c_df$position - mean(c_df$position)
delta_Y <- {
aj <-
(posterior |> select(starts_with("b[(I")) |> apply(2, mean)) +
(posterior |> select(starts_with("(I")) |> apply(2, mean))
bj <-
(posterior |> select(starts_with("b[po")) |> apply(2, mean)) +
(posterior |> pull(position) |> mean())
Y = aj + bj*c_df$position
Y - mean(Y)
}
delta_X <- {
bj <-
(posterior |> select(starts_with("b[po")) |> apply(2, mean)) +
(posterior |> pull(position) |> mean())
bj*X_diff
}
delta_b <- {
a_diff <- posterior |> select(starts_with("b[(I")) |> apply(2, mean)
b_diff <- posterior |> select(starts_with("b[po")) |> apply(2, mean)
a_diff + b_diff*mean(c_df$position)
}
bind_cols(
country_df |> filter(groups == g),
data.frame(
delta_Y = delta_Y,
delta_X = delta_X,
delta_b = delta_b
)
) |>
select(-left, -topic) |>
mutate(Country = haven::as_factor(country))
}decomps <- lapply(treatment_groups, decomposition)
decomps |> bind_rows(.id = "condition") |>
select(condition, Country, starts_with("delta")) |>
gather(var, val, -condition, - Country, - delta_Y) |>
mutate(var = factor(var, c("delta_b", "delta_X"), c("Difference in average response",
"Difference in average position"))) |>
ggplot(aes(delta_Y, val, label = Country, color = var)) + geom_text()+
xlab("Difference in outcomes") + ylab("contribution") + facet_wrap(~condition) +
theme(legend.position = "bottom")Stan model:
library(rstan)
stan_code <- "
data {
int<lower=1> N; // Number of observations
int<lower=1> m; // Number of countries
int<lower=1,upper=m> Country[N]; // Country indices for each observation
vector[N] y; // Observed data
vector[m] X; // Country level predictor variable
}
parameters {
vector[m] alpha_raw; // Non-centered group-level intercepts
real beta; // Coefficient for X
real<lower=0> sigma; // Error term
real<lower=0> tau; // Standard deviation of group-level intercepts
real mu; // Mean of group-level intercepts
}
transformed parameters {
vector[m] alpha; // Centered group-level intercepts
alpha = mu + tau * alpha_raw; // Non-centered parameterization
}
model {
// Priors
alpha_raw ~ normal(0, 1); // Standard normal for non-centered intercepts
beta ~ normal(0, 2.5); // Prior for beta
sigma ~ normal(0, 1); // Prior for sigma
mu ~ normal(0, 10); // Prior for mu
tau ~ normal(0, 5); // Prior for tau
// Likelihood
for (i in 1:N) {
y[i] ~ normal(alpha[Country[i]] + beta * X[Country[i]], sigma);
}
}
"
f <- function(df, var, y = "outcome1b", ...) {
names(df)[names(df) == var] <- "X"
names(df)[names(df) == y] <- "y"
# Select relevant columns and drop NA
df <- df |>
select(X, y, Country) |> drop_na() |>
mutate(Country = as.integer(factor(Country)))
stan_data <- list(
N = nrow(df),
m = length(unique(df$Country)),
Country = df$Country,
y = df$y,
X = df |> group_by(Country) |> summarize(X = mean(X)) |>
pull(X) |> as.vector()
)
fit <- stan(model_code = stan_code, data = stan_data, chains = 4, ...)
return(fit)
}# Core variables
if(run)
vars |>
lapply(function(v) {
treatment_groups |> lapply(function(g) {
f(data |> filter(groups==g & position > .5), v, iter = 5000)})}) |>
write_rds("saved/M_1.3_by_group.rds")
M_1.3_by <- read_rds("saved/M_1.3_by_group.rds")
sumy <- function(v, g, model_list = M_1.3_by)
model_list[[v]][[g]] |>
as.data.frame() |> summarize(mean = mean(beta), sd = sd(beta),
lower = quantile(beta, probs = .025),
upper = quantile(beta, probs = .975)) |>
mutate(max_rhat = max(rhat(model_list[[v]][[g]])))
fig.1.3.by <-
vars[c(1:3, 6)] |> lapply(function(v)
tgg |>
lapply(function(g) sumy(v,g)) |>
bind_rows(.id = "group")) |>
bind_rows(.id = "var") |>
mutate(var = factor(var, vs, vs_labels)) |>
ggplot(aes(mean, group)) + geom_point() + facet_wrap(~var, scales = "free_x") +
geom_errorbarh(aes(xmin = lower, xmax = upper, height = .2)) +
geom_vline(xintercept = 0, color = "red")
fig.1.3.by vlong <- c("position", "grievances", "politicization", "gov_lr",
"protest_participants", "sal", "pol", "vulnerability", "cri",
"ims_perc", "ref_perc", "protest_events", "protest_intensity",
"outcome1b")
cdf <- data |>
group_by(country, left, topic, treatment_grp) |>
summarize(across(all_of(vlong), \(x) mean(x, na.rm = TRUE)), .groups = 'drop') |>
ungroup() |>
rename(support_petition = outcome1b)
cdf |>
st()| Variable | N | Mean | Std. Dev. | Min | Pctl. 25 | Pctl. 75 | Max |
|---|---|---|---|---|---|---|---|
| left | 108 | ||||||
| ... Right | 54 | 50% | |||||
| ... Left | 54 | 50% | |||||
| topic | 108 | ||||||
| ... Migration | 54 | 50% | |||||
| ... Climate change | 54 | 50% | |||||
| position | 108 | 0.5 | 0.093 | 0.3 | 0.44 | 0.56 | 0.72 |
| grievances | 108 | 0.35 | 0.25 | 0 | 0.15 | 0.53 | 1 |
| politicization | 108 | 0.24 | 0.2 | 0 | 0.1 | 0.34 | 1 |
| gov_lr | 108 | 0.21 | 0.61 | -1 | -0.26 | 0.75 | 1 |
| protest_participants | 104 | 0.24 | 0.28 | 0.0027 | 0.055 | 0.3 | 1.1 |
| sal | 108 | 0.51 | 0.053 | 0.41 | 0.48 | 0.55 | 0.69 |
| pol | 108 | 3 | 0.22 | 2.6 | 2.9 | 3.2 | 3.5 |
| vulnerability | 108 | 0.33 | 0.033 | 0.27 | 0.3 | 0.35 | 0.4 |
| cri | 108 | 76 | 27 | 18 | 58 | 106 | 106 |
| ims_perc | 108 | 13 | 9 | 2.2 | 6.1 | 17 | 48 |
| ref_perc | 108 | 1.8 | 1 | 0.46 | 0.8 | 2.6 | 4.2 |
| protest_events | 104 | 639 | 594 | 38 | 270 | 725 | 2324 |
| protest_intensity | 104 | -0.00048 | 0.87 | -0.85 | -0.56 | 0.069 | 2.4 |
| support_petition | 108 | 0.21 | 0.061 | 0.099 | 0.17 | 0.25 | 0.41 |
country_summary| left | topic | n | signing |
|---|---|---|---|
| Right | Migration | 6456 | 0.259 |
| Right | Climate change | 6569 | 0.216 |
| Left | Migration | 6442 | 0.184 |
| Left | Climate change | 6437 | 0.184 |
text 3
data |> group_by(left, topic) |>
filter(position > .5) |>
summarize(n = n(),
signing = mean(outcome1b),
.groups = "drop") |> ungroup() |>
kable(digits = 3, caption = "Numbers and shares signing by petition type among supporters (position >0.5)")| left | topic | n | signing |
|---|---|---|---|
| Right | Migration | 3021 | 0.312 |
| Right | Climate change | 2030 | 0.253 |
| Left | Migration | 2144 | 0.212 |
| Left | Climate change | 2889 | 0.217 |
av <-
data |>
group_by(Country) |>
summarize(`migration left` = mean(outcome1b[topic =="Migration" & left =="Left"], na.rm = TRUE),
`migration right` = mean(outcome1b[topic =="Migration" & left !="Left"], na.rm = TRUE),
`climate left` = mean(outcome1b[topic !="Migration" & left =="Left"], na.rm = TRUE),
`climate right` = mean(outcome1b[topic !="Migration" & left !="Left"], na.rm = TRUE))
cor(av[,-1]) |> kable(caption = "Country level correlations in signing across petitions", digits = 2)| migration left | migration right | climate left | climate right | |
|---|---|---|---|---|
| migration left | 1.00 | -0.08 | 0.45 | 0.18 |
| migration right | -0.08 | 1.00 | 0.58 | 0.72 |
| climate left | 0.45 | 0.58 | 1.00 | 0.57 |
| climate right | 0.18 | 0.72 | 0.57 | 1.00 |
We provide tables of estimates provided in the text.
summary_results |>
kable(digits = 2, col.names = c("Country", "Predicted mean", "Lower", "Upper", "Raw mean"),
caption = "Table of results used for Figure 4")| Country | Predicted mean | Lower | Upper | Raw mean |
|---|---|---|---|---|
| Austria | 0.19 | 0.15 | 0.23 | 0.18 |
| Belgium | 0.23 | 0.19 | 0.28 | 0.24 |
| Bulgaria | 0.23 | 0.19 | 0.28 | 0.23 |
| Croatia | 0.24 | 0.19 | 0.28 | 0.24 |
| Cyprus | 0.27 | 0.21 | 0.33 | 0.28 |
| Czech | 0.22 | 0.17 | 0.26 | 0.22 |
| Denmark | 0.23 | 0.18 | 0.28 | 0.23 |
| Estonia | 0.20 | 0.16 | 0.25 | 0.20 |
| Finland | 0.13 | 0.10 | 0.17 | 0.12 |
| France | 0.18 | 0.14 | 0.22 | 0.17 |
| Germany | 0.21 | 0.16 | 0.25 | 0.21 |
| Greece | 0.21 | 0.16 | 0.25 | 0.21 |
| Hungary | 0.24 | 0.19 | 0.29 | 0.25 |
| Ireland | 0.21 | 0.17 | 0.25 | 0.21 |
| Italy | 0.17 | 0.13 | 0.21 | 0.17 |
| Latvia | 0.20 | 0.16 | 0.25 | 0.20 |
| Lithuania | 0.20 | 0.16 | 0.24 | 0.20 |
| Luxembourg | 0.18 | 0.13 | 0.23 | 0.17 |
| Malta | 0.28 | 0.22 | 0.36 | 0.30 |
| Netherlands | 0.24 | 0.20 | 0.29 | 0.24 |
| Poland | 0.20 | 0.16 | 0.26 | 0.20 |
| Portugal | 0.22 | 0.18 | 0.27 | 0.22 |
| Romania | 0.26 | 0.20 | 0.30 | 0.26 |
| Slovakia | 0.19 | 0.16 | 0.24 | 0.19 |
| Slovenia | 0.20 | 0.16 | 0.25 | 0.20 |
| Spain | 0.25 | 0.20 | 0.30 | 0.25 |
| Sweden | 0.18 | 0.14 | 0.22 | 0.18 |
lapply(posterior_1.2,
function(df)
df|> select(b = position, a = `(Intercept)`)) |>
bind_rows(.id = "petition") |>
mutate(y0 = 1/(1+exp(-a)),
y1 = 1/(1+exp(-a-b)),
change = y1 - y0) |>
group_by(petition) |>
summarize(y_0 = mean(y0),
sd_0 = sd(y0),
y_1 = mean(y1),
sd_1 = sd(y1),
difference = mean(change),
sd_difference = sd(change), .groups = 'drop') |>
kable(digits = 2, col.names = c("Petition", "Rates | opposed", "sd", "Rates given support", "sd", "Difference", "sd"), align = c("l", "c", "c", "c", "c", "c", "c"),
caption = "Table of results used for Figure 5")| Petition | Rates | opposed | sd | Rates given support | sd | Difference | sd |
|---|---|---|---|---|---|---|
| Climate (left) | 0.13 | 0.01 | 0.24 | 0.01 | 0.12 | 0.02 |
| Climate (right) | 0.20 | 0.01 | 0.25 | 0.01 | 0.05 | 0.02 |
| Migration (left) | 0.16 | 0.01 | 0.22 | 0.02 | 0.07 | 0.02 |
| Migration (right) | 0.19 | 0.01 | 0.33 | 0.02 | 0.14 | 0.02 |
decomps |> bind_rows(.id = "condition") |>
select(Petition = condition, Country, starts_with("delta")) |>
kable(digits = 2, col.names = c("Petition", "Country", "delta Y", "delta X", "delta b"),
caption = "Table of results used for Figure 6")| Petition | Country | delta Y | delta X | delta b |
|---|---|---|---|---|
| Climate (left) | Belgium | 0.09 | -0.02 | 0.11 |
| Climate (left) | Bulgaria | 0.19 | 0.01 | 0.18 |
| Climate (left) | Czech Republic | -0.26 | -0.04 | -0.23 |
| Climate (left) | Denmark | 0.21 | -0.01 | 0.21 |
| Climate (left) | Germany | 0.00 | -0.06 | 0.06 |
| Climate (left) | Estonia | -0.17 | -0.10 | -0.07 |
| Climate (left) | Ireland | 0.05 | -0.04 | 0.08 |
| Climate (left) | Greece | 0.04 | 0.04 | 0.00 |
| Climate (left) | Spain | 0.25 | 0.02 | 0.22 |
| Climate (left) | France | -0.29 | 0.00 | -0.29 |
| Climate (left) | Croatia | -0.02 | 0.08 | -0.10 |
| Climate (left) | Italy | -0.15 | 0.09 | -0.24 |
| Climate (left) | Cyprus | 0.45 | 0.07 | 0.37 |
| Climate (left) | Latvia | -0.09 | -0.08 | -0.01 |
| Climate (left) | Lithuania | -0.23 | -0.05 | -0.18 |
| Climate (left) | Luxembourg | -0.10 | 0.01 | -0.11 |
| Climate (left) | Hungary | 0.18 | 0.06 | 0.12 |
| Climate (left) | Malta | 0.36 | 0.13 | 0.22 |
| Climate (left) | Netherlands | 0.14 | -0.08 | 0.21 |
| Climate (left) | Austria | -0.16 | -0.02 | -0.14 |
| Climate (left) | Poland | -0.20 | -0.05 | -0.15 |
| Climate (left) | Portugal | 0.29 | 0.08 | 0.21 |
| Climate (left) | Romania | 0.27 | 0.00 | 0.27 |
| Climate (left) | Slovenia | -0.15 | 0.00 | -0.15 |
| Climate (left) | Slovakia | -0.18 | -0.04 | -0.14 |
| Climate (left) | Finland | -0.33 | 0.00 | -0.33 |
| Climate (left) | Sweden | -0.16 | 0.00 | -0.16 |
| Climate (right) | Belgium | 0.09 | 0.01 | 0.08 |
| Climate (right) | Bulgaria | 0.12 | 0.00 | 0.12 |
| Climate (right) | Czech Republic | -0.02 | 0.04 | -0.05 |
| Climate (right) | Denmark | 0.01 | 0.00 | 0.00 |
| Climate (right) | Germany | 0.02 | 0.00 | 0.01 |
| Climate (right) | Estonia | 0.04 | 0.04 | 0.00 |
| Climate (right) | Ireland | -0.16 | 0.01 | -0.17 |
| Climate (right) | Greece | -0.05 | 0.00 | -0.04 |
| Climate (right) | Spain | 0.08 | 0.00 | 0.08 |
| Climate (right) | France | -0.02 | 0.00 | -0.01 |
| Climate (right) | Croatia | 0.18 | -0.02 | 0.20 |
| Climate (right) | Italy | -0.30 | -0.03 | -0.26 |
| Climate (right) | Cyprus | 0.05 | -0.02 | 0.08 |
| Climate (right) | Latvia | 0.01 | -0.01 | 0.02 |
| Climate (right) | Lithuania | -0.03 | 0.03 | -0.06 |
| Climate (right) | Luxembourg | -0.05 | -0.02 | -0.03 |
| Climate (right) | Hungary | 0.16 | -0.01 | 0.17 |
| Climate (right) | Malta | 0.21 | 0.00 | 0.21 |
| Climate (right) | Netherlands | 0.02 | 0.01 | 0.01 |
| Climate (right) | Austria | -0.14 | 0.00 | -0.14 |
| Climate (right) | Poland | 0.07 | 0.02 | 0.05 |
| Climate (right) | Portugal | -0.13 | -0.04 | -0.08 |
| Climate (right) | Romania | 0.18 | 0.00 | 0.18 |
| Climate (right) | Slovenia | 0.09 | -0.01 | 0.10 |
| Climate (right) | Slovakia | 0.01 | 0.01 | 0.00 |
| Climate (right) | Finland | -0.30 | 0.01 | -0.31 |
| Climate (right) | Sweden | -0.13 | 0.00 | -0.13 |
| Migration (left) | Belgium | 0.04 | -0.01 | 0.05 |
| Migration (left) | Bulgaria | -0.06 | 0.02 | -0.08 |
| Migration (left) | Czech Republic | -0.28 | 0.03 | -0.31 |
| Migration (left) | Denmark | 0.14 | 0.01 | 0.13 |
| Migration (left) | Germany | 0.10 | 0.06 | 0.04 |
| Migration (left) | Estonia | -0.19 | -0.03 | -0.16 |
| Migration (left) | Ireland | 0.15 | 0.00 | 0.15 |
| Migration (left) | Greece | 0.13 | -0.03 | 0.16 |
| Migration (left) | Spain | 0.24 | 0.01 | 0.23 |
| Migration (left) | France | -0.15 | -0.02 | -0.13 |
| Migration (left) | Croatia | 0.19 | 0.01 | 0.17 |
| Migration (left) | Italy | -0.03 | 0.02 | -0.05 |
| Migration (left) | Cyprus | -0.10 | -0.05 | -0.05 |
| Migration (left) | Latvia | -0.05 | 0.00 | -0.06 |
| Migration (left) | Lithuania | -0.05 | -0.03 | -0.02 |
| Migration (left) | Luxembourg | -0.29 | 0.00 | -0.29 |
| Migration (left) | Hungary | -0.19 | 0.00 | -0.18 |
| Migration (left) | Malta | 0.11 | -0.04 | 0.15 |
| Migration (left) | Netherlands | 0.01 | 0.01 | 0.00 |
| Migration (left) | Austria | 0.19 | 0.01 | 0.18 |
| Migration (left) | Poland | 0.04 | -0.01 | 0.05 |
| Migration (left) | Portugal | 0.16 | -0.01 | 0.17 |
| Migration (left) | Romania | 0.34 | 0.06 | 0.28 |
| Migration (left) | Slovenia | -0.32 | 0.00 | -0.32 |
| Migration (left) | Slovakia | -0.04 | 0.00 | -0.05 |
| Migration (left) | Finland | -0.30 | 0.03 | -0.33 |
| Migration (left) | Sweden | 0.23 | -0.03 | 0.26 |
| Migration (right) | Belgium | 0.14 | -0.02 | 0.16 |
| Migration (right) | Bulgaria | 0.17 | -0.03 | 0.20 |
| Migration (right) | Czech Republic | 0.60 | -0.06 | 0.66 |
| Migration (right) | Denmark | 0.08 | -0.03 | 0.11 |
| Migration (right) | Germany | -0.15 | -0.05 | -0.10 |
| Migration (right) | Estonia | 0.08 | 0.04 | 0.04 |
| Migration (right) | Ireland | -0.20 | 0.05 | -0.25 |
| Migration (right) | Greece | -0.18 | 0.04 | -0.22 |
| Migration (right) | Spain | 0.21 | 0.03 | 0.18 |
| Migration (right) | France | -0.24 | 0.02 | -0.26 |
| Migration (right) | Croatia | 0.13 | -0.02 | 0.16 |
| Migration (right) | Italy | -0.22 | -0.01 | -0.21 |
| Migration (right) | Cyprus | 0.51 | 0.13 | 0.39 |
| Migration (right) | Latvia | -0.04 | 0.01 | -0.04 |
| Migration (right) | Lithuania | 0.00 | 0.07 | -0.06 |
| Migration (right) | Luxembourg | -0.03 | 0.00 | -0.02 |
| Migration (right) | Hungary | 0.41 | -0.01 | 0.43 |
| Migration (right) | Malta | 0.54 | 0.14 | 0.41 |
| Migration (right) | Netherlands | 0.28 | -0.06 | 0.35 |
| Migration (right) | Austria | -0.44 | 0.00 | -0.44 |
| Migration (right) | Poland | -0.15 | 0.01 | -0.15 |
| Migration (right) | Portugal | -0.12 | 0.00 | -0.12 |
| Migration (right) | Romania | 0.05 | -0.06 | 0.11 |
| Migration (right) | Slovenia | 0.03 | 0.00 | 0.03 |
| Migration (right) | Slovakia | -0.20 | -0.07 | -0.12 |
| Migration (right) | Finland | -0.71 | -0.02 | -0.68 |
| Migration (right) | Sweden | -0.55 | -0.01 | -0.54 |
vars[c(1:3, 6)] |> lapply(function(v)
tgg |>
lapply(function(g) sumy(v,g)) |>
bind_rows(.id = "group")) |>
bind_rows(.id = "var") |>
kable(digits = 2,
col.names=c("Covariate", "Petition", "Posterior mean", "Posterior sd", "Cred lower" , "Cred upper", "Rhat"),
caption = "Table of results used for Figure 7. Rhat is a `stan` convergence diagnostic.")| Covariate | Petition | Posterior mean | Posterior sd | Cred lower | Cred upper | Rhat |
|---|---|---|---|---|---|---|
| grievances | Climate (left) | 0.09 | 0.04 | 0.02 | 0.17 | 1 |
| grievances | Climate (right) | 0.06 | 0.04 | -0.03 | 0.14 | 1 |
| grievances | Migration (left) | -0.02 | 0.08 | -0.18 | 0.13 | 1 |
| grievances | Migration (right) | 0.02 | 0.10 | -0.19 | 0.22 | 1 |
| protest_participants | Climate (left) | -0.01 | 0.04 | -0.09 | 0.06 | 1 |
| protest_participants | Climate (right) | -0.03 | 0.04 | -0.11 | 0.04 | 1 |
| protest_participants | Migration (left) | 0.06 | 0.05 | -0.05 | 0.16 | 1 |
| protest_participants | Migration (right) | -0.04 | 0.07 | -0.19 | 0.10 | 1 |
| politicization | Climate (left) | 0.06 | 0.05 | -0.05 | 0.16 | 1 |
| politicization | Climate (right) | 0.07 | 0.05 | -0.04 | 0.17 | 1 |
| politicization | Migration (left) | -0.05 | 0.08 | -0.20 | 0.11 | 1 |
| politicization | Migration (right) | 0.10 | 0.10 | -0.11 | 0.30 | 1 |
| gov_lr | Climate (left) | -0.02 | 0.02 | -0.05 | 0.02 | 1 |
| gov_lr | Climate (right) | -0.01 | 0.02 | -0.05 | 0.02 | 1 |
| gov_lr | Migration (left) | -0.01 | 0.02 | -0.06 | 0.04 | 1 |
| gov_lr | Migration (right) | -0.05 | 0.03 | -0.11 | 0.01 | 1 |
lm_models <-
list(
lm_robust(outcome1b ~ position, data = country_df),
lm_robust(outcome1b ~ Left*Climate, data = country_df),
lm_robust(outcome1b ~ Left*Climate + position, data = country_df),
lm_robust(outcome1b ~ Left*Climate*grievances + position, data = country_df),
lm_robust(outcome1b ~ Left*Climate*politicization + position, data = country_df),
lm_robust(outcome1b ~ Left*Climate*protest_participants + position, data = country_df),
lm_robust(outcome1b ~ Left*Climate*grievances + Left*Climate*politicization + Left*Climate*protest_participants + position, data = country_df)
)
lm_models |> texreg::htmlreg(include.ci = FALSE)| Model 1 | Model 2 | Model 3 | Model 4 | Model 5 | Model 6 | Model 7 | |
|---|---|---|---|---|---|---|---|
| (Intercept) | 0.13*** | 0.21*** | 0.16*** | 0.14** | 0.15** | 0.15** | 0.13** |
| (0.04) | (0.01) | (0.04) | (0.04) | (0.05) | (0.05) | (0.05) | |
| position | 0.17* | 0.11 | 0.11 | 0.09 | 0.13 | 0.11 | |
| (0.07) | (0.09) | (0.09) | (0.09) | (0.09) | (0.09) | ||
| Left | -0.06*** | -0.06*** | -0.06** | -0.04* | -0.06*** | -0.05* | |
| (0.01) | (0.01) | (0.02) | (0.02) | (0.02) | (0.02) | ||
| Climate | -0.02* | -0.02* | -0.06*** | -0.03 | -0.02 | -0.05* | |
| (0.01) | (0.01) | (0.02) | (0.02) | (0.02) | (0.02) | ||
| Left:Climate | 0.05* | 0.02 | 0.03 | -0.01 | 0.03 | -0.01 | |
| (0.02) | (0.03) | (0.04) | (0.03) | (0.03) | (0.05) | ||
| grievances | 0.04 | 0.03 | |||||
| (0.03) | (0.03) | ||||||
| Left:grievances | 0.00 | 0.01 | |||||
| (0.06) | (0.06) | ||||||
| Climate:grievances | 0.10 | 0.08 | |||||
| (0.06) | (0.06) | ||||||
| Left:Climate:grievances | -0.02 | 0.01 | |||||
| (0.12) | (0.12) | ||||||
| politicization | 0.05 | 0.04 | |||||
| (0.03) | (0.03) | ||||||
| Left:politicization | -0.05 | -0.06 | |||||
| (0.05) | (0.06) | ||||||
| Climate:politicization | 0.05 | 0.01 | |||||
| (0.05) | (0.06) | ||||||
| Left:Climate:politicization | 0.13 | 0.14 | |||||
| (0.11) | (0.12) | ||||||
| protest_participants | -0.01 | -0.01 | |||||
| (0.02) | (0.02) | ||||||
| Left:protest_participants | 0.03 | 0.04 | |||||
| (0.04) | (0.04) | ||||||
| Climate:protest_participants | -0.01 | -0.00 | |||||
| (0.04) | (0.04) | ||||||
| Left:Climate:protest_participants | -0.06 | -0.07 | |||||
| (0.08) | (0.08) | ||||||
| R2 | 0.06 | 0.29 | 0.30 | 0.36 | 0.35 | 0.31 | 0.41 |
| Adj. R2 | 0.06 | 0.27 | 0.27 | 0.31 | 0.30 | 0.25 | 0.30 |
| Num. obs. | 108 | 108 | 108 | 108 | 108 | 104 | 104 |
| RMSE | 0.06 | 0.05 | 0.05 | 0.05 | 0.05 | 0.05 | 0.05 |
| ***p < 0.001; **p < 0.01; *p < 0.05 | |||||||
fe_models <-
list(
lm_robust(outcome1b ~ position, fixed_effects = ~country, data = country_df),
lm_robust(outcome1b ~ Left*Climate, fixed_effects = ~country, data = country_df),
lm_robust(outcome1b ~ Left*Climate + position, fixed_effects = ~country, data = country_df),
lm_robust(outcome1b ~ Left*Climate*grievances + position, fixed_effects = ~country, data = country_df),
lm_robust(outcome1b ~ Left*Climate*politicization + position, fixed_effects = ~country, data = country_df),
lm_robust(outcome1b ~ Left*Climate*protest_participants + position, fixed_effects = ~country, data = country_df),
lm_robust(outcome1b ~ Left*Climate*grievances + Left*Climate*politicization + Left*Climate*protest_participants + position, fixed_effects = ~country, data = country_df)
)
fe_models |> texreg::htmlreg(include.ci = FALSE)| Model 1 | Model 2 | Model 3 | Model 4 | Model 5 | Model 6 | Model 7 | |
|---|---|---|---|---|---|---|---|
| position | 0.17** | 0.10 | 0.11 | 0.09 | 0.13* | 0.12 | |
| (0.06) | (0.06) | (0.06) | (0.06) | (0.06) | (0.06) | ||
| Left | -0.06*** | -0.06*** | -0.06*** | -0.04** | -0.06*** | -0.05** | |
| (0.01) | (0.01) | (0.01) | (0.01) | (0.01) | (0.02) | ||
| Climate | -0.02** | -0.02** | -0.03 | -0.02 | -0.02 | -0.02 | |
| (0.01) | (0.01) | (0.03) | (0.01) | (0.01) | (0.04) | ||
| Left:Climate | 0.05** | 0.02 | 0.03 | -0.01 | 0.04 | -0.01 | |
| (0.02) | (0.02) | (0.03) | (0.02) | (0.03) | (0.03) | ||
| grievances | 0.00 | -0.01 | |||||
| (0.02) | (0.03) | ||||||
| Left:grievances | 0.00 | 0.01 | |||||
| (0.04) | (0.05) | ||||||
| Climate:grievances | 0.03 | 0.02 | |||||
| (0.08) | (0.08) | ||||||
| Left:Climate:grievances | -0.02 | 0.01 | |||||
| (0.08) | (0.09) | ||||||
| politicization | 0.03 | 0.04 | |||||
| (0.04) | (0.04) | ||||||
| Left:politicization | -0.05 | -0.06 | |||||
| (0.05) | (0.05) | ||||||
| Climate:politicization | 0.02 | 0.03 | |||||
| (0.05) | (0.05) | ||||||
| Left:Climate:politicization | 0.13 | 0.14 | |||||
| (0.10) | (0.11) | ||||||
| protest_participants | |||||||
| Left:protest_participants | 0.03 | 0.04 | |||||
| (0.02) | (0.02) | ||||||
| Climate:protest_participants | -0.01 | -0.02 | |||||
| (0.02) | (0.03) | ||||||
| Left:Climate:protest_participants | -0.06 | -0.07 | |||||
| (0.04) | (0.05) | ||||||
| R2 | 0.45 | 0.67 | 0.68 | 0.68 | 0.70 | 0.70 | 0.72 |
| Adj. R2 | 0.26 | 0.55 | 0.56 | 0.54 | 0.56 | 0.56 | 0.55 |
| Num. obs. | 108 | 108 | 108 | 108 | 108 | 104 | 104 |
| RMSE | 0.05 | 0.04 | 0.04 | 0.04 | 0.04 | 0.04 | 0.04 |
| ***p < 0.001; **p < 0.01; *p < 0.05 | |||||||
In these models, Left and Migration are centered on zero and so the “main” terms report estimated average effects.
data <- mutate(data, Left = 1*(left == "Left"), Climate = 1*(topic == "Climate change"))
lm_models_i <-
list(
lm_robust(outcome1b ~ position, data = data, cluster = Country, se_type = 'stata'),
lm_robust(outcome1b ~ Left*Climate, data = data, cluster = Country, se_type = 'stata'),
lm_robust(outcome1b ~ Left*Climate + position, data = data, cluster = Country, se_type = 'stata'),
lm_robust(outcome1b ~ Left*Climate*grievances + position, data = data, cluster = Country, se_type = 'stata'),
lm_robust(outcome1b ~ Left*Climate*politicization + position, data = data, cluster = Country, se_type = 'stata'),
lm_robust(outcome1b ~ Left*Climate*protest_participants + position, data = data, cluster = Country, se_type = 'stata'),
lm_robust(outcome1b ~ Left*Climate*grievances + Left*Climate*politicization +
Left*Climate*protest_participants +
position, data = data, cluster = Country, se_type = 'stata')
)
lm_models_i |> texreg::htmlreg(include.ci = FALSE)| Model 1 | Model 2 | Model 3 | Model 4 | Model 5 | Model 6 | Model 7 | |
|---|---|---|---|---|---|---|---|
| (Intercept) | 0.17*** | 0.26*** | 0.21*** | 0.22*** | 0.19*** | 0.22*** | 0.20*** |
| (0.01) | (0.01) | (0.01) | (0.02) | (0.03) | (0.02) | (0.04) | |
| position | 0.10*** | 0.09*** | 0.09*** | 0.09*** | 0.09*** | 0.09*** | |
| (0.01) | (0.01) | (0.01) | (0.01) | (0.01) | (0.01) | ||
| Left | -0.08*** | -0.06*** | -0.08* | -0.03 | -0.08** | -0.06 | |
| (0.02) | (0.02) | (0.03) | (0.03) | (0.02) | (0.03) | ||
| Climate | -0.04*** | -0.03** | -0.08*** | -0.03 | -0.04* | -0.06 | |
| (0.01) | (0.01) | (0.02) | (0.02) | (0.01) | (0.03) | ||
| Left:Climate | 0.04* | 0.02 | 0.04 | -0.01 | 0.04 | 0.01 | |
| (0.02) | (0.02) | (0.03) | (0.03) | (0.02) | (0.04) | ||
| grievances | -0.04 | -0.04 | |||||
| (0.07) | (0.07) | ||||||
| Left:grievances | 0.06 | 0.05 | |||||
| (0.10) | (0.10) | ||||||
| Climate:grievances | 0.13 | 0.09 | |||||
| (0.07) | (0.07) | ||||||
| Left:Climate:grievances | -0.06 | -0.03 | |||||
| (0.11) | (0.11) | ||||||
| politicization | 0.06 | 0.08 | |||||
| (0.09) | (0.09) | ||||||
| Left:politicization | -0.11 | -0.12 | |||||
| (0.11) | (0.11) | ||||||
| Climate:politicization | 0.01 | -0.03 | |||||
| (0.09) | (0.09) | ||||||
| Left:Climate:politicization | 0.11 | 0.11 | |||||
| (0.12) | (0.12) | ||||||
| protest_participants | -0.03 | -0.03 | |||||
| (0.04) | (0.05) | ||||||
| Left:protest_participants | 0.06 | 0.07 | |||||
| (0.04) | (0.04) | ||||||
| Climate:protest_participants | 0.02 | 0.03 | |||||
| (0.03) | (0.04) | ||||||
| Left:Climate:protest_participants | -0.06 | -0.06 | |||||
| (0.05) | (0.05) | ||||||
| R2 | 0.01 | 0.01 | 0.01 | 0.01 | 0.01 | 0.01 | 0.01 |
| Adj. R2 | 0.01 | 0.01 | 0.01 | 0.01 | 0.01 | 0.01 | 0.01 |
| Num. obs. | 24943 | 25904 | 24943 | 24943 | 24943 | 23978 | 23978 |
| RMSE | 0.41 | 0.41 | 0.41 | 0.41 | 0.41 | 0.41 | 0.41 |
| N Clusters | 27 | 27 | 27 | 27 | 27 | 26 | 26 |
| ***p < 0.001; **p < 0.01; *p < 0.05 | |||||||
fe_models_i <-
list(
lm_robust(outcome1b ~ position, data = data, cluster = Country, se_type = 'stata', fixed_effects = ~country),
lm_robust(outcome1b ~ Left*Climate, data = data, cluster = Country, se_type = 'stata', fixed_effects = ~country),
lm_robust(outcome1b ~ Left*Climate + position, data = data, cluster = Country, se_type = 'stata', fixed_effects = ~country),
lm_robust(outcome1b ~ Left*Climate*grievances + position, data = data, cluster = Country, se_type = 'stata', fixed_effects = ~country),
lm_robust(outcome1b ~ Left*Climate*politicization + position, data = data, cluster = Country, se_type = 'stata', fixed_effects = ~country),
lm_robust(outcome1b ~ Left*Climate*protest_participants + position, data = data, cluster = Country, se_type = 'stata', fixed_effects = ~country),
lm_robust(outcome1b ~ Left*Climate*grievances + Left*Climate*politicization +
Left*Climate*protest_participants +
position, data = data, cluster = Country, se_type = 'stata', fixed_effects = ~country)
)
fe_models_i |> texreg::htmlreg(include.ci = FALSE)| Model 1 | Model 2 | Model 3 | Model 4 | Model 5 | Model 6 | Model 7 | |
|---|---|---|---|---|---|---|---|
| position | 0.10*** | 0.09*** | 0.09*** | 0.09*** | 0.09*** | 0.09*** | |
| (0.01) | (0.01) | (0.01) | (0.01) | (0.01) | (0.01) | ||
| Left | -0.08*** | -0.06*** | -0.08* | -0.03 | -0.08** | -0.06 | |
| (0.02) | (0.02) | (0.03) | (0.03) | (0.02) | (0.03) | ||
| Climate | -0.04*** | -0.03** | -0.05 | -0.02 | -0.04** | -0.02 | |
| (0.01) | (0.01) | (0.03) | (0.02) | (0.01) | (0.03) | ||
| Left:Climate | 0.04* | 0.02 | 0.04 | -0.01 | 0.04 | 0.01 | |
| (0.02) | (0.02) | (0.03) | (0.03) | (0.02) | (0.04) | ||
| grievances | -0.04 | -0.04 | |||||
| (0.06) | (0.06) | ||||||
| Left:grievances | 0.06 | 0.05 | |||||
| (0.10) | (0.10) | ||||||
| Climate:grievances | 0.06 | 0.03 | |||||
| (0.08) | (0.08) | ||||||
| Left:Climate:grievances | -0.07 | -0.03 | |||||
| (0.11) | (0.11) | ||||||
| politicization | 0.07 | 0.09 | |||||
| (0.08) | (0.07) | ||||||
| Left:politicization | -0.11 | -0.11 | |||||
| (0.11) | (0.11) | ||||||
| Climate:politicization | -0.04 | -0.04 | |||||
| (0.07) | (0.07) | ||||||
| Left:Climate:politicization | 0.11 | 0.11 | |||||
| (0.12) | (0.12) | ||||||
| protest_participants | |||||||
| Left:protest_participants | 0.06 | 0.07 | |||||
| (0.04) | (0.04) | ||||||
| Climate:protest_participants | 0.02 | 0.01 | |||||
| (0.03) | (0.04) | ||||||
| Left:Climate:protest_participants | -0.06 | -0.06 | |||||
| (0.05) | (0.05) | ||||||
| R2 | 0.01 | 0.01 | 0.02 | 0.02 | 0.02 | 0.02 | 0.02 |
| Adj. R2 | 0.01 | 0.01 | 0.02 | 0.02 | 0.02 | 0.02 | 0.02 |
| Num. obs. | 24943 | 25904 | 24943 | 24943 | 24943 | 23978 | 23978 |
| RMSE | 0.41 | 0.41 | 0.41 | 0.41 | 0.41 | 0.41 | 0.41 |
| N Clusters | 27 | 27 | 27 | 27 | 27 | 26 | 26 |
| ***p < 0.001; **p < 0.01; *p < 0.05 | |||||||
Evidence on whether “speeders” are more or less likely to sign.
data |> mutate(signing = factor(outcome1b)) |>
ggplot(aes(duration, linetype = signing)) +
geom_density() + xlim(0, 2500)The “grievance” measure in the text is generated using a migration measure for migration petitions and climate measure for climate petitions. The “politicization” measure is generated by combining salience and polarization measures.
The below analyses implement Analysis 3 using these subcomponents and close substitute measures.
if(run)
vs_other |>
lapply(function(v) {
treatment_groups |> lapply(function(g) {
f(data |> filter(groups==g & position > .5), v, iter = 5000)})}) |>
write_rds("M_1.3_by_group_other.rds")
M_1.3_by_group_other <- read_rds("saved/M_1.3_by_group_other.rds")
names(M_1.3_by_group_other) <- vs_other
names(vs_other) <- vs_other
fig.1.3.by_appendix_df <-
vs_other |> lapply(function(v)
tgg |>
lapply(function(g) sumy(v,g, model_list = M_1.3_by_group_other)) |>
bind_rows(.id = "group")) |>
bind_rows(.id = "var") |>
mutate(var = factor(var, vs_other, vs_other_labs))
fig.1.3.by_appendix_df |> filter(max_rhat < 1.01) |>
ggplot(aes(mean, group)) + geom_point() + facet_wrap(~var, scales = "free_x") +
geom_errorbarh(aes(xmin = lower, xmax = upper, height = .2)) +
geom_vline(xintercept = 0, color = "red")In response to comments by reviewers, the below analyses implement Analysis 3 using additional measures capturing Lijphart’s distinction between majoritarian and consensus democracy to investigate the effect of political opportunity structures.
fig.1.3.by.extra <-
vars[c(4,5)] |> lapply(function(v)
tgg |>
lapply(function(g) sumy(v,g)) |>
bind_rows(.id = "group")) |>
bind_rows(.id = "var") |>
mutate(var = factor(var, vs, vs_labels)) |>
ggplot(aes(mean, group)) + geom_point() + facet_wrap(~var, scales = "free_x") +
geom_errorbarh(aes(xmin = lower, xmax = upper, height = .2)) +
geom_vline(xintercept = 0, color = "red")
fig.1.3.by.extra