Code
# source("data_prep.R")
<- read_rds("assets/ees_petition_data.rds") data
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")
<- read_rds("assets/ees_petition_data.rds") data
Labels defined for graphs.
# Treatment group labels in data and for figures
<- c("Climate (left)",
tgg "Climate (right)",
"Migration (left)",
"Migration (right)")
names(tgg) <- tgg
<- c("CLI left", "CLI right", "MIG left", "MIG right")
tg <- as.list(tg)
treatment_groups names(treatment_groups) <- tgg
# Core country covariates
<- c(
vs "grievances",
"protest_participants",
"politicization",
"lfirstp",
"lsecondp",
"gov_lr"
)<- c(
vs_labels "Grievances",
"Protest participants\n(per million per year)",
"Politicization\n(polarization x salience)",
"Lijphart 1",
"Lijphart 2",
"Left / Right \n(government orientation)"
)
<- as.list(vs)
vars 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)
::stan_glmer(outcome1b ~ 1 + (1 | Country), data = data, family = binomial(link = "logit")) |>
rstanarmwrite_rds("saved/M_1.1.rds")
<- read_rds("saved/M_1.1.rds") |> as.data.frame() posterior_samples
# Extract the posterior samples of the random effects
# Get the unique country names
<- unique(data$Country)
Countries <- as.list(Countries)
Countries_list names(Countries_list) <- Countries
# Function to calculate predicted probabilities for each country
<- function(country, fit = posterior_samples) {
get_predicted_probs <- fit |>
random_intercept select(starts_with(paste0("b[(Intercept) Country:", country)))
<- fit$`(Intercept)`
common_intercept <- (common_intercept + random_intercept)[[1]]
log_odds # Convert log-odds to probabilities
data.frame(probs = exp(log_odds) / (1 + exp(log_odds)))
}
# Create a dataframe to store the results
<- lapply(Countries_list, get_predicted_probs) |>
results bind_rows(.id = "Country")
# Summarize the predicted probabilities
<- results %>%
summary_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)
::stan_glmer(
rstanarm~ 1 + position + (1 + position | Country),
outcome1b family = binomial(link = "logit"),
data = data |> filter(groups == g)
)
if(run)
|>
treatment_groups lapply(decomp_model)|>
write_rds("saved/M_1.2.rds")
.2 <- read_rds("saved/M_1.2.rds")
M_1
.2 <- lapply(M_1.2, as.data.frame) posterior_1
lapply(posterior_1.2,
function(df)
|> select(b = position, a = `(Intercept)`)) |>
dfbind_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:
<- function(g) {
decomposition
names(posterior_1.2) <- treatment_groups
<- posterior_1.2[[g]]
posterior
<- country_df |> filter(groups == g)
c_df
<- c_df$position - mean(c_df$position)
X_diff
<- {
delta_Y
<-
aj |> select(starts_with("b[(I")) |> apply(2, mean)) +
(posterior |> select(starts_with("(I")) |> apply(2, mean))
(posterior
<-
bj |> select(starts_with("b[po")) |> apply(2, mean)) +
(posterior |> pull(position) |> mean())
(posterior
= aj + bj*c_df$position
Y
- mean(Y)
Y
}
<- {
delta_X
<-
bj |> select(starts_with("b[po")) |> apply(2, mean)) +
(posterior |> pull(position) |> mean())
(posterior
*X_diff
bj
}
<- {
delta_b <- posterior |> select(starts_with("b[(I")) |> apply(2, mean)
a_diff <- posterior |> select(starts_with("b[po")) |> apply(2, mean)
b_diff + b_diff*mean(c_df$position)
a_diff
}
bind_cols(
|> filter(groups == g),
country_df data.frame(
delta_Y = delta_Y,
delta_X = delta_X,
delta_b = delta_b
)|>
) select(-left, -topic) |>
mutate(Country = haven::as_factor(country))
}
<- lapply(treatment_groups, decomposition)
decomps
|> bind_rows(.id = "condition") |>
decomps 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);
}
}
"
<- function(df, var, y = "outcome1b", ...) {
f
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)))
<- list(
stan_data 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()
)
<- stan(model_code = stan_code, data = stan_data, chains = 4, ...)
fit
return(fit)
}
# Core variables
if(run)
|>
vars lapply(function(v) {
|> lapply(function(g) {
treatment_groups f(data |> filter(groups==g & position > .5), v, iter = 5000)})}) |>
write_rds("saved/M_1.3_by_group.rds")
.3_by <- read_rds("saved/M_1.3_by_group.rds")
M_1
<- function(v, g, model_list = M_1.3_by)
sumy |>
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]])))
1.3.by <-
fig.c(1:3, 6)] |> lapply(function(v)
vars[|>
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")
1.3.by fig.
<- c("position", "grievances", "politicization", "gov_lr",
vlong "protest_participants", "sal", "pol", "vulnerability", "cri",
"ims_perc", "ref_perc", "protest_events", "protest_intensity",
"outcome1b")
<- data |>
cdf 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
|> group_by(left, topic) |>
data 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)
|> select(b = position, a = `(Intercept)`)) |>
dfbind_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 |
|> bind_rows(.id = "condition") |>
decomps 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 |
c(1:3, 6)] |> lapply(function(v)
vars[|>
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)
)
|> texreg::htmlreg(include.ci = FALSE) lm_models
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)
)|> texreg::htmlreg(include.ci = FALSE) fe_models
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.
<- mutate(data, Left = 1*(left == "Left"), Climate = 1*(topic == "Climate change"))
data
<-
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 +
*Climate*protest_participants +
Leftdata = data, cluster = Country, se_type = 'stata')
position,
)
|> texreg::htmlreg(include.ci = FALSE) lm_models_i
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 +
*Climate*protest_participants +
Leftdata = data, cluster = Country, se_type = 'stata', fixed_effects = ~country)
position,
)
|> texreg::htmlreg(include.ci = FALSE) fe_models_i
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.
|> mutate(signing = factor(outcome1b)) |>
data 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) {
|> lapply(function(g) {
treatment_groups f(data |> filter(groups==g & position > .5), v, iter = 5000)})}) |>
write_rds("M_1.3_by_group_other.rds")
.3_by_group_other <- read_rds("saved/M_1.3_by_group_other.rds")
M_1names(M_1.3_by_group_other) <- vs_other
names(vs_other) <- vs_other
1.3.by_appendix_df <-
fig.|> lapply(function(v)
vs_other |>
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))
1.3.by_appendix_df |> filter(max_rhat < 1.01) |>
fig.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.
1.3.by.extra <-
fig.c(4,5)] |> lapply(function(v)
vars[|>
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")
1.3.by.extra fig.