You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
708 lines
29 KiB
708 lines
29 KiB
---
|
|
title: "Has the Overton Window Shifted?"
|
|
subtitle: "Acceptance Through Moderation in the Dutch Tweede Kamer (2016–2026)"
|
|
author: "Stemwijzer Analysis"
|
|
date: today
|
|
format: html
|
|
jupyter: python3
|
|
---
|
|
|
|
```{python}
|
|
#| label: setup
|
|
#| include: false
|
|
|
|
import duckdb
|
|
import pandas as pd
|
|
import numpy as np
|
|
import plotly.graph_objects as go
|
|
from plotly.subplots import make_subplots
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(".").resolve().parents[1]
|
|
DB_PATH = str(ROOT / "data" / "motions.db")
|
|
|
|
con = duckdb.connect(DB_PATH, read_only=True)
|
|
|
|
BREAK_YEAR = 2024
|
|
PARTY_COLOURS = {
|
|
"VVD": "#1E73BE", "PVV": "#002366", "D66": "#00A36C",
|
|
"CDA": "#4CAF50", "CU": "#0288D1", "NSC": "#FF8F00",
|
|
"SGP": "#F4511E", "FVD": "#6A1B9A", "JA21": "#7B1FA2",
|
|
"BBB": "#8D6E63", "SP": "#E53935", "GroenLinks-PvdA": "#2E7D32",
|
|
"PvdD": "#43A047", "Volt": "#572AB7", "DENK": "#00897B",
|
|
}
|
|
```
|
|
|
|
> **Verdict:** The Overton window widened. More right-wing positions became
|
|
> politically acceptable. But the mechanism was right-wing moderation, not
|
|
> centrist conversion. The effect may be temporary.
|
|
|
|
## Introduction
|
|
|
|
Did the PVV's November 2023 election victory shift the Dutch Overton window?
|
|
The conventional narrative is clear: a far-right party won the largest share of
|
|
seats, entered government for the first time in July 2024, and the political
|
|
center responded by adopting more right-wing positions. Centrist parties,
|
|
according to this story, moved right to accommodate the new political reality.
|
|
|
|
The data tells a different story.
|
|
|
|
Using 29,591 Tweede Kamer motions with full MP-level vote records, Procrustes-aligned
|
|
SVD spatial analysis, and 2D extremity scoring (stijl-extremiteit vs materiële
|
|
impact), we find that **the Overton window widened**: centrist support for
|
|
right-wing motions surged from 25% to 51%, while centrist support for non-right-wing
|
|
motions rose modestly (58%→62%, +3.5 pp). What changed was the behavior of right-wing parties:
|
|
they filed more motions, with milder content, framed in centrist-friendly
|
|
language. Centrist voting support surged from 0.251 to 0.507 (Cohen's d = +0.65),
|
|
but centrists did not become more right-wing. They stayed ideologically left
|
|
while voting more permissively on proposals that had become less materially
|
|
consequential.
|
|
|
|
This article presents the evidence across three indicators: centrist voting
|
|
support, SVD spatial divergence, and 2D extremity decomposition, and examines
|
|
the mechanisms through which right-wing motions gained centrist support.
|
|
|
|
## About Stemwijzer
|
|
|
|
Stemwijzer is a data-driven political compass built from real parliamentary voting
|
|
records. It analyzes 29,591 motions from the Tweede Kamer (2016 to 2026), each with
|
|
per-MP vote records, to compute latent political dimensions using Singular Value
|
|
Decomposition (SVD). Users vote on real motions and find which MPs match their
|
|
positions, not based on party manifestos or campaign promises, but on how
|
|
representatives actually voted.
|
|
|
|
The platform tracks party positions across 11 annual windows using
|
|
Procrustes-aligned SVD, allowing year-over-year comparison of spatial drift.
|
|
Every motion has been scored on two independent dimensions of extremity:
|
|
**stijl-extremiteit** (stylistic rhetoric, 1 to 5) and **materiële impact**
|
|
(material policy consequence, 1 to 5), manually validated with 75% auditor agreement.
|
|
|
|
The Overton analysis presented here builds on this infrastructure. The same
|
|
SVD compass, extremity scores, and vote-level data that power the Stemwijzer
|
|
Explorer dashboard drive these findings.
|
|
|
|
## Methodology
|
|
|
|
**Right-wing motion classification.** We identify right-wing motions using a
|
|
hybrid keyword + voting-pattern classifier. A seed set of right-wing keywords
|
|
(vuurwerkverbod, stikstof, nareis, etc.) is expanded through an iterative
|
|
keyword-vote loop. Motions whose voting pattern correlates with right-wing
|
|
party support are flagged, their distinctive terms extracted, and the keyword
|
|
set refined. The final classifier identifies 3,030 motions as right-wing across
|
|
2016 to 2026, with full voting records for centrist support computation.
|
|
|
|
**2D extremity scoring.** Every motion in the database (29,591) has been scored
|
|
by an LLM on two dimensions: *stijl-extremiteit* (stylistic extremity:
|
|
inflammatory language, rhetorical framing) and *materiële impact* (material
|
|
impact: rights restriction, institutional change, resource reallocation), each
|
|
on a 1 to 5 scale. Manual audit of 117 stratified motions achieved 75% agreement.
|
|
The two dimensions are only moderately correlated (Pearson r = 0.43 for all
|
|
motions, r = 0.47 for right-wing), confirming they capture distinct
|
|
phenomena. Excluding ~6,000 placeholder motions scored (1,1) by default, r drops to 0.34. The dimensions are even more independent than the headline figure suggests.
|
|
|
|
**Strict centrist definition.** We define the centrist bloc narrowly as four
|
|
parties (D66, CDA, ChristenUnie, NSC), excluding VVD and BBB, which lean
|
|
center-right and would inflate centrist support mechanically. A strict
|
|
opposition-only filter further controls for coalition effects by excluding
|
|
motions whose lead submitter belongs to the governing coalition.
|
|
|
|
**SVD alignment.** Party positions are computed via SVD on annual voting
|
|
matrices and aligned using chained Procrustes orthogonal rotation followed by
|
|
global PCA, placing all annual party positions in a common 2D reference frame.
|
|
Centrist and right-wing centers of gravity are computed as the mean of
|
|
party-level axis scores within each bloc.
|
|
|
|
```{python}
|
|
#| label: chart-1-yearly-cs
|
|
#| fig-cap: "Centrist Support for Right-Wing Motions Over Time (2016–2026)"
|
|
#| column: page
|
|
|
|
yearly = con.execute("""
|
|
SELECT
|
|
year,
|
|
AVG(centrist_support_strict) AS mean_cs,
|
|
STDDEV(centrist_support_strict) AS std_cs,
|
|
COUNT(*) AS n
|
|
FROM right_wing_motions
|
|
WHERE classified = TRUE
|
|
GROUP BY year ORDER BY year
|
|
""").fetchdf()
|
|
|
|
fig1 = go.Figure()
|
|
|
|
fig1.add_trace(go.Scatter(
|
|
x=yearly["year"], y=yearly["mean_cs"],
|
|
mode="lines+markers", name="All right-wing",
|
|
line=dict(color="#002366", width=3),
|
|
marker=dict(size=8),
|
|
error_y=dict(
|
|
type="data",
|
|
array=1.96 * yearly["std_cs"] / np.sqrt(yearly["n"]),
|
|
visible=True, thickness=0.8, width=2
|
|
)
|
|
))
|
|
|
|
pre = yearly[yearly["year"] < BREAK_YEAR]
|
|
post = yearly[yearly["year"] >= BREAK_YEAR]
|
|
|
|
fig1.add_hline(
|
|
y=pre["mean_cs"].mean(),
|
|
line_dash="dot", line_color="#90CAF9",
|
|
annotation_text=f"Pre-2024 mean ({pre['mean_cs'].mean():.3f})"
|
|
)
|
|
fig1.add_hline(
|
|
y=post["mean_cs"].mean(),
|
|
line_dash="dot", line_color="#1E88E5",
|
|
annotation_text=f"Post-2024 mean ({post['mean_cs'].mean():.3f})"
|
|
)
|
|
|
|
fig1.add_vline(
|
|
x=BREAK_YEAR - 0.5, line_dash="dot", line_color="black", opacity=0.5
|
|
)
|
|
|
|
fig1.update_layout(
|
|
title="Centrist Support (Strict) for Right-Wing Motions",
|
|
xaxis=dict(title="Year", dtick=1),
|
|
yaxis=dict(title="Centrist Support (fraction of parties)", range=[0, 1.1]),
|
|
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
|
template="plotly_white", height=450,
|
|
)
|
|
fig1.show()
|
|
```
|
|
|
|
## Indicator 1: Centrist Voting Support
|
|
|
|
The cleanest signal is in how centrist parties voted on right-wing motions.
|
|
Average support rose from 0.251 pre-2024 to 0.507 post-2024, a Cohen's d of
|
|
+0.65, a medium-to-large effect. The breakpoint is unmistakably 2024.
|
|
|
|
This is not a coalition artifact. After the Schoof cabinet formed in July 2024,
|
|
PVV entered government, which could mechanically inflate support for its own
|
|
motions. When we restrict analysis to opposition-only right-wing motions (lead
|
|
submitter outside the governing coalition), the effect is larger: d = +0.85,
|
|
with support jumping from 0.270 to 0.543. Centrist parties are genuinely more
|
|
willing to support right-wing motions than they were before 2024, even when
|
|
those motions come from opposition right-wing parties.
|
|
|
|
The gradient across extremity levels persists: centrists still differentiate by
|
|
how radical a motion is, but at a consistently higher baseline. High-extremity
|
|
motions gained proportionally more support than mild motions, consistent with
|
|
genuine tolerance expansion rather than compositional shift.
|
|
|
|
Pass rate is useless as an indicator. Dutch parliament passes 96%+ of motions
|
|
in both periods. With near-zero variance, pass rate cannot register a shift of
|
|
any magnitude. Centrist support among MPs is the meaningful behavioral measure.
|
|
|
|
```{python}
|
|
#| label: chart-2-gravity
|
|
#| fig-cap: "Gravity-Controlled Centrist Support by Material Impact Level, Pre vs Post 2024"
|
|
#| column: page
|
|
|
|
gravity = con.execute("""
|
|
SELECT
|
|
CASE WHEN r.year < 2024 THEN 'pre-2024' ELSE 'post-2024' END AS period,
|
|
e.materiele_impact AS m_level,
|
|
AVG(r.centrist_support_strict) AS cs,
|
|
COUNT(*) AS n
|
|
FROM right_wing_motions r
|
|
JOIN extremity_scores_all e ON r.motion_id = e.motion_id
|
|
WHERE r.classified = TRUE AND e.materiele_impact IS NOT NULL
|
|
GROUP BY period, m_level ORDER BY period, m_level
|
|
""").fetchdf()
|
|
|
|
levels = sorted(gravity["m_level"].unique())
|
|
pre_vals = gravity[gravity["period"] == "pre-2024"].set_index("m_level")
|
|
post_vals = gravity[gravity["period"] == "post-2024"].set_index("m_level")
|
|
|
|
fig2 = go.Figure()
|
|
|
|
fig2.add_trace(go.Bar(
|
|
name="Pre-2024",
|
|
x=[f"M={l}" for l in levels],
|
|
y=[pre_vals.loc[l, "cs"] if l in pre_vals.index else 0 for l in levels],
|
|
marker_color="#90CAF9",
|
|
text=[f"N={int(pre_vals.loc[l, 'n'])}" if l in pre_vals.index else "" for l in levels],
|
|
textposition="outside",
|
|
offset=0,
|
|
))
|
|
|
|
fig2.add_trace(go.Bar(
|
|
name="Post-2024",
|
|
x=[f"M={l}" for l in levels],
|
|
y=[post_vals.loc[l, "cs"] if l in post_vals.index else 0 for l in levels],
|
|
marker_color="#1E88E5",
|
|
text=[f"N={int(post_vals.loc[l, 'n'])}" if l in post_vals.index else "" for l in levels],
|
|
textposition="outside",
|
|
offset=0.3,
|
|
))
|
|
|
|
fig2.update_layout(
|
|
title="Gravity-Controlled Centrist Support by Material Impact",
|
|
xaxis=dict(title="Material Impact Level"),
|
|
yaxis=dict(title="Centrist Support", range=[0, 1.1]),
|
|
barmode="group",
|
|
template="plotly_white", height=450,
|
|
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
|
|
)
|
|
fig2.show()
|
|
```
|
|
|
|
The gravity-controlled chart reveals a critical pattern: the centrist support
|
|
shift is real at **every** material impact level. From M=1 (mild procedural
|
|
adjustments, +0.292) to M=5 (systemic overhaul, +0.122), centrist support rose
|
|
across the board. The largest absolute gains came from the middle range (M=2:
|
|
+0.205, M=3: +0.219, M=4: +0.267), where most right-wing motions cluster.
|
|
|
|
Comparing right-wing motions against all other motions confirms the shift is
|
|
specific: right-wing centrist support surged by +0.236, while non-right-wing
|
|
motions remained essentially flat (−0.006). This is a right-wing-specific
|
|
phenomenon, not a general parliamentary trend.
|
|
|
|
## Indicator 2: Spatial Divergence
|
|
|
|
If centrists are voting more with right-wing motions, one might expect
|
|
ideological convergence: centrist parties drifting rightward on the SVD
|
|
compass. Procrustes-aligned SVD analysis shows the opposite.
|
|
|
|
```{python}
|
|
#| label: chart-3-svd
|
|
#| fig-cap: "SVD Trajectories: Centrist vs Right-Wing Centers of Gravity (2016–2026)"
|
|
#| column: page
|
|
|
|
svd = con.execute("""
|
|
SELECT * FROM overton_svd_center ORDER BY window_id
|
|
""").fetchdf()
|
|
|
|
fig3 = go.Figure()
|
|
|
|
fig3.add_trace(go.Scatter(
|
|
x=svd["centrist_mean_axis1"], y=svd["centrist_mean_axis2"],
|
|
mode="lines+markers+text", name="Centrist center",
|
|
line=dict(color="#00A36C", width=2),
|
|
marker=dict(size=8, symbol="circle"),
|
|
text=svd["window_id"], textposition="top center",
|
|
))
|
|
|
|
fig3.add_trace(go.Scatter(
|
|
x=svd["right_mean_axis1"], y=svd["right_mean_axis2"],
|
|
mode="lines+markers+text", name="Right-wing center",
|
|
line=dict(color="#002366", width=2),
|
|
marker=dict(size=8, symbol="square"),
|
|
text=svd["window_id"], textposition="bottom center",
|
|
))
|
|
|
|
fig3.update_layout(
|
|
title="SVD Party Centers of Gravity Over Time",
|
|
xaxis=dict(title="Axis 1 (Economic)"),
|
|
yaxis=dict(title="Axis 2 (Cultural)"),
|
|
template="plotly_white", height=500,
|
|
legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99),
|
|
hovermode="closest",
|
|
)
|
|
fig3.show()
|
|
```
|
|
|
|
Between the first and last annual windows:
|
|
|
|
- **Centrists moved left on both axes:** −0.223 on the economic axis (more
|
|
welfare-oriented) and +0.081 on the cultural axis (more kosmopolitisch).
|
|
- **Right-wing parties moved further right culturally:** −0.065 on the cultural
|
|
axis (more nationalist).
|
|
- **The cultural distance between centrists and right-wing parties widened**
|
|
from 0.282 to 0.428 (+0.146).
|
|
|
|
This is spatial divergence, not convergence. Centrist parties did not become
|
|
right-wing. They became marginally *more* left-wing in their overall voting
|
|
patterns. The centrist center of gravity moved toward welfare and cosmopolitanism,
|
|
while right-wing parties moved further into the nationalist corner.
|
|
|
|
Why this makes sense with the voting data: The SVD captures the *full*
|
|
voting landscape, including all motions, not just the ones centrists supported.
|
|
Right-wing parties continued filing high-impact motions that centrists opposed,
|
|
while simultaneously filing a much larger volume of milder motions centrists
|
|
supported. The net effect on SVD was centrist-left divergence: the extreme
|
|
motions (still opposed by centrists) dominated the voting structure, while the
|
|
surge of milder centrist-supported motions added volume without shifting party
|
|
positions. This is "acceptance without conversion." Centrists vote more with
|
|
right-wing motions while moving further from them ideologically.
|
|
|
|
## Indicator 3: Content Moderation
|
|
|
|
The original single-dimensional extremity score showed no increase post-2024
|
|
(d = −0.09, from 2.21 to 2.15). If the Overton window shifted, why didn't
|
|
right-wing motions become more radical?
|
|
|
|
The answer lies in what the single score measured. Two-dimensional rescoring
|
|
of all 29,591 motions reveals that stylistic extremity and material impact are
|
|
only moderately correlated (r = 0.43). When tracked separately over time, they
|
|
tell different stories.
|
|
|
|
```{python}
|
|
#| label: chart-4-2d-extremity
|
|
#| fig-cap: "2D Extremity Over Time: Stijl vs Materieel (Right-Wing Motions, 2019–2026)"
|
|
#| column: page
|
|
|
|
extremity_2d = con.execute("""
|
|
SELECT
|
|
r.year,
|
|
AVG(e.stijl_extremiteit) AS mean_stijl,
|
|
AVG(e.materiele_impact) AS mean_mat,
|
|
COUNT(*) AS n
|
|
FROM right_wing_motions r
|
|
JOIN extremity_scores_all e ON r.motion_id = e.motion_id
|
|
WHERE r.classified = TRUE AND r.year >= 2019
|
|
GROUP BY r.year ORDER BY r.year
|
|
""").fetchdf()
|
|
|
|
all_stijl, all_mat = con.execute("""
|
|
SELECT AVG(stijl_extremiteit), AVG(materiele_impact)
|
|
FROM extremity_scores_all
|
|
""").fetchone()
|
|
|
|
fig4 = make_subplots(
|
|
rows=1, cols=2,
|
|
subplot_titles=("Stylistic Extremity (Stijl)", "Material Impact (Materieel)"),
|
|
shared_yaxes=False,
|
|
)
|
|
|
|
fig4.add_trace(
|
|
go.Scatter(
|
|
x=extremity_2d["year"], y=extremity_2d["mean_stijl"],
|
|
mode="lines+markers", name="Right-wing",
|
|
line=dict(color="#6A1B9A", width=3),
|
|
marker=dict(size=8),
|
|
),
|
|
row=1, col=1,
|
|
)
|
|
|
|
fig4.add_hline(
|
|
y=all_stijl, line_dash="dot", line_color="#9E9E9E",
|
|
annotation_text=f"All motions ({all_stijl:.2f})",
|
|
row=1, col=1,
|
|
)
|
|
|
|
fig4.add_trace(
|
|
go.Scatter(
|
|
x=extremity_2d["year"], y=extremity_2d["mean_mat"],
|
|
mode="lines+markers", name="Right-wing",
|
|
line=dict(color="#E53935", width=3),
|
|
marker=dict(size=8),
|
|
),
|
|
row=1, col=2,
|
|
)
|
|
|
|
fig4.add_hline(
|
|
y=all_mat, line_dash="dot", line_color="#9E9E9E",
|
|
annotation_text=f"All motions ({all_mat:.2f})",
|
|
row=1, col=2,
|
|
)
|
|
|
|
fig4.update_layout(
|
|
title="2D Extremity Decomposition: Stijl vs Materieel",
|
|
template="plotly_white", height=400,
|
|
showlegend=False,
|
|
)
|
|
fig4.update_xaxes(title="Year", dtick=1)
|
|
fig4.update_yaxes(title="Score (1–5)", range=[0.5, 4])
|
|
fig4.show()
|
|
```
|
|
|
|
| Dimension | Pre-2024 Mean | Post-2024 Mean | Δ |
|
|
|-----------|--------------|---------------|-----|
|
|
| Stylistic extremity | 1.875 | 1.744 | −0.131 |
|
|
| Material impact | 2.786 | 2.450 | −0.336 |
|
|
| Gap (M−S) | 0.911 | 0.706 | −0.205 |
|
|
|
|
Both dimensions *decreased*: stylistic extremity (−0.131) and material impact
|
|
(−0.336). A Wilcoxon signed-rank test comparing yearly mean stylistic vs yearly
|
|
mean material scores confirms the dimensions systematically differ (W = 0.0,
|
|
n = 10 yearly pairs, p = 0.002). The gap between the two dimensions narrowed
|
|
from 0.911 to 0.706. Right-wing motions became both less rhetorically hostile
|
|
AND less substantively impactful.
|
|
|
|
Compared to all motions, right-wing motions score higher on both dimensions:
|
|
stijl +0.47, materieel +0.54. The masking rate, restrained language paired
|
|
with high material impact, is 9.7% (S≤2, M≥4) or 13.5% (S=1, M≥3) for right-wing motions
|
|
vs 24.0% for all motions. Right-wing proposals disproportionately use
|
|
procedural language to advance consequential policy.
|
|
|
|
## Mechanisms of Influence
|
|
|
|
If centrists didn't become right-wing, *how* did right-wing motions gain their
|
|
support? A systematic classification of 150 post-2024 motions (stratified by
|
|
centrist support level) identifies the dominant pathways.
|
|
|
|
```{python}
|
|
#| label: chart-5-mechanisms
|
|
#| fig-cap: "Mechanism Distribution: High-Support vs Low-Support Post-2024 Motions"
|
|
#| column: page
|
|
|
|
mechanisms = [
|
|
"Procedureel/technisch",
|
|
"Consensus framing",
|
|
"Gerichte restrictie",
|
|
"Institutioneel/rechtsstatelijk",
|
|
"Symbolisch/declaratoir",
|
|
"Welzijn/dienstverlening",
|
|
"Lokaal/regionaal",
|
|
"Coalitie-afstemming",
|
|
"Crisisrespons",
|
|
"Systeemontmanteling",
|
|
]
|
|
|
|
high_support = [24, 18, 13, 7, 4, 3, 3, 2, 1, 0]
|
|
low_support = [9, 6, 21, 19, 5, 1, 1, 0, 0, 13]
|
|
|
|
fig5 = go.Figure()
|
|
|
|
fig5.add_trace(go.Bar(
|
|
name="High-support (CS > 0.5)",
|
|
x=mechanisms, y=high_support,
|
|
marker_color="#1E88E5",
|
|
))
|
|
|
|
fig5.add_trace(go.Bar(
|
|
name="Low-support (CS ≤ 0.5)",
|
|
x=mechanisms, y=low_support,
|
|
marker_color="#90CAF9",
|
|
))
|
|
|
|
fig5.update_layout(
|
|
title="Mechanism Classification: High-Support vs Low-Support Post-2024",
|
|
xaxis=dict(title="Mechanism", tickangle=45),
|
|
yaxis=dict(title="Count"),
|
|
barmode="group",
|
|
template="plotly_white", height=450,
|
|
legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99),
|
|
)
|
|
fig5.show()
|
|
```
|
|
|
|
The contrast between high- and low-support post-2024 motions is sharp.
|
|
|
|
**High-support motions (CS > 0.5)** are dominated by procedural/technical
|
|
framing (32%), consensus framing appealing to shared values (24%), and targeted
|
|
restriction rather than blanket bans (17%). Institutional challenges and system
|
|
dismantling are notably absent.
|
|
|
|
**Low-support motions (CS ≤ 0.5)** are dominated by targeted restriction (28%),
|
|
institutional challenges (25%), and system dismantling (17%). Zero system
|
|
dismantling motions achieved high centrist support.
|
|
|
|
Consensus framing is significantly more common in high-support motions (24%)
|
|
than low-support (8%): χ²(1) = 6.00, p = 0.014. Exploratory evidence suggests
|
|
consensus framing drives centrist support. Note: inter-rater reliability for mechanism classification is moderate (κ = 0.41). These patterns are exploratory and require taxonomy refinement.
|
|
|
|
**Party-level analysis** reveals the shift is not uniform. JA21 is the primary
|
|
driver, with a +0.203 CS shift and the only volume + support gains combination.
|
|
PVV entered government and filed fewer, milder motions. FVD remains structurally
|
|
shunned. Its motions consistently fail to gain centrist support regardless of
|
|
content.
|
|
|
|
## Temporal Dynamics
|
|
|
|
Quarterly analysis across 33 quarters (2016-Q2 through 2026-Q1) replaces the
|
|
binary pre/post-2024 comparison with a continuous trajectory that reveals the
|
|
exact timing, shape, and sustainability of the shift.
|
|
|
|
```{python}
|
|
#| label: chart-6-quarterly
|
|
#| fig-cap: "Quarterly Centrist Support Trajectory (2016–2026)"
|
|
#| column: page
|
|
|
|
quarterly = con.execute("""
|
|
SELECT
|
|
EXTRACT(YEAR FROM m.date) AS y,
|
|
CEIL(EXTRACT(MONTH FROM m.date) / 3.0) AS q,
|
|
AVG(r.centrist_support_strict) AS cs,
|
|
COUNT(*) AS n,
|
|
STDDEV(r.centrist_support_strict) AS std_cs
|
|
FROM right_wing_motions r
|
|
JOIN motions m ON r.motion_id = m.id
|
|
WHERE r.classified = TRUE AND m.date IS NOT NULL
|
|
GROUP BY y, q ORDER BY y, q
|
|
""").fetchdf()
|
|
|
|
quarterly["label"] = quarterly["y"].astype(int).astype(str) + "-Q" + quarterly["q"].astype(int).astype(str)
|
|
|
|
inflection_idx = quarterly[(quarterly["y"].astype(int) == 2024) & (quarterly["q"].astype(int) == 1)].index
|
|
peak_idx = quarterly[(quarterly["y"].astype(int) == 2024) & (quarterly["q"].astype(int) == 4)].index
|
|
latest_idx = quarterly[(quarterly["y"].astype(int) == 2026) & (quarterly["q"].astype(int) == 1)].index
|
|
|
|
fig6 = go.Figure()
|
|
|
|
fig6.add_trace(go.Scatter(
|
|
x=quarterly["label"], y=quarterly["cs"],
|
|
mode="lines+markers",
|
|
line=dict(color="#002366", width=3),
|
|
marker=dict(size=6),
|
|
error_y=dict(
|
|
type="data",
|
|
array=1.96 * quarterly["std_cs"] / np.sqrt(quarterly["n"]),
|
|
visible=True, thickness=0.6, width=1.5,
|
|
),
|
|
name="Centrist Support",
|
|
))
|
|
|
|
for idx in [inflection_idx, peak_idx, latest_idx]:
|
|
if len(idx) > 0:
|
|
i = idx[0]
|
|
fig6.add_annotation(
|
|
x=quarterly.loc[i, "label"], y=quarterly.loc[i, "cs"],
|
|
text=f'{quarterly.loc[i, "cs"]:.3f}',
|
|
showarrow=True, arrowhead=1, ax=0, ay=-30,
|
|
)
|
|
|
|
fig6.add_shape(
|
|
type="line", x0="2024-Q1", x1="2024-Q1", y0=0, y1=1,
|
|
line=dict(dash="dot", color="red", width=1.5),
|
|
)
|
|
fig6.add_annotation(
|
|
x="2024-Q1", y=0.95,
|
|
text="PVV election (Nov 2023)",
|
|
showarrow=False, textangle=-90,
|
|
font=dict(color="red", size=10),
|
|
)
|
|
|
|
fig6.add_shape(
|
|
type="line", x0="2024-Q3", x1="2024-Q3", y0=0, y1=1,
|
|
line=dict(dash="dot", color="orange", width=1.5),
|
|
)
|
|
fig6.add_annotation(
|
|
x="2024-Q3", y=0.88,
|
|
text="Schoof cabinet (Jul 2024)",
|
|
showarrow=False, textangle=-90,
|
|
font=dict(color="orange", size=10),
|
|
)
|
|
|
|
fig6.update_layout(
|
|
title="Quarterly Centrist Support Trajectory",
|
|
xaxis=dict(
|
|
title="Quarter",
|
|
tickangle=45,
|
|
tickmode="array",
|
|
tickvals=quarterly["label"][::4],
|
|
),
|
|
yaxis=dict(title="Centrist Support", range=[0, 1.0]),
|
|
template="plotly_white", height=450,
|
|
)
|
|
fig6.show()
|
|
```
|
|
|
|
**Timing.** The inflection point is 2024-Q1, the quarter immediately following
|
|
the PVV's November 2023 election victory. Centrist support jumped from 0.321
|
|
(2023-Q4) to 0.501 (2024-Q1), a single-quarter increase of +0.180, roughly
|
|
twice the average quarterly change.
|
|
|
|
**Shape.** Centrist support rose sharply through 2024-Q4, reaching an all-time
|
|
peak of 0.648 in the first full quarter of the Schoof cabinet. From that peak,
|
|
it declined steadily: 0.598, 0.503, 0.437, 0.450, and 0.334 in 2026-Q1,
|
|
below the 0.4 inflection threshold and approaching pre-shift levels.
|
|
|
|
**Causal mechanism.** The shift began before the Schoof cabinet formed (July
|
|
2024), appearing immediately after the PVV election. This is less consistent with
|
|
coalition dynamics as the primary driver. The most parsimonious explanation: centrist
|
|
parties perceived the PVV's electoral success as a mandate for right-wing policy
|
|
and adjusted their voting behavior accordingly. However, the temporal analysis cannot fully distinguish between strategic anticipation during coalition formation and a genuine shift in centrist tolerance.
|
|
|
|
**Sustainability.** The 2026-Q1 reversion to 0.334 raises a critical question:
|
|
is the centrist support surge a temporary electoral-cycle effect rather than a
|
|
permanent Overton window shift? Material moderation persisted (materieel ~2.4)
|
|
through the decline, but stylistic extremity reverted from 1.70 to 2.02. CS was
|
|
already declining through 2025 (0.648→0.450) despite continued moderation,
|
|
suggesting the 2024 spike was primarily an electoral shock for non-migration domains. However, 2026-Q2 shows CS bouncing back to 0.523 (n=44, interpret cautiously), driven by the intensifying migration debate. Migration centrist support (0.395) now exceeds non-migration (0.368) for the first time. The shift is domain-specific: temporary for non-migration, durable for migration.
|
|
|
|
| Hypothesis | Evidence | Verdict |
|
|
|------------|----------|---------|
|
|
| Electoral shock | Jump immediately followed PVV victory (Nov 2023) | **Supported** |
|
|
| Coalition dynamics | Shift began 3 quarters before cabinet formed | **Less consistent with the data** |
|
|
| Gradual learning | Jump was 1.9× average quarterly — discrete, not incremental | **Less consistent with the data** |
|
|
| European contagion | No Dutch response during 2022–2023 European shift | **Less consistent with the data** |
|
|
|
|
## Verdict: The Window Widened Through Moderation
|
|
|
|
**The Overton window widened: more right-wing positions became politically
|
|
acceptable after 2024. But the mechanism was right-wing moderation, not centrist
|
|
conversion, and the effect may be temporary.**
|
|
|
|
Centrist support for right-wing motions surged from 25% to 51%, while centrist
|
|
support for non-right-wing motions rose modestly (58%→62%, +3.5 pp). The window of acceptable
|
|
debate expanded rightward.
|
|
|
|
1. **Volume surged, impact declined.** Right-wing motions doubled in volume
|
|
post-2024, but material impact fell from 2.79 to 2.45 (Cohen's d = −0.36).
|
|
The M ≥ 4 share dropped from 23.7% to 11.3% and continued falling to 2.7%
|
|
by 2026.
|
|
|
|
2. **Centrists did not become more tolerant.** The extremity-stratified
|
|
gradient persists. Centrists still differentiate between mild and extreme
|
|
motions. The across-the-board baseline shift reflects that content within
|
|
each bucket became milder, not that centrists lowered their standards.
|
|
|
|
3. **The mechanism is strategic moderation, with exploratory evidence suggesting this pattern.** Zero
|
|
system-dismantling proposals achieved high centrist support post-2024. The
|
|
dominant pathways, such as procedural/technical (32%), consensus framing (24%),
|
|
and targeted restriction (17%), suggest right-wing parties learned which
|
|
frames work, though mechanism classification has moderate reliability (κ = 0.41).
|
|
|
|
4. **SVD divergence confirms this.** Centrists moved left spatially as the
|
|
extreme tail polarized even as cooperation grew on the moderate mass.
|
|
|
|
5. **The shift is electorally driven and domain-specific.** Centrist support
|
|
surged immediately after the PVV election, peaked at 0.648 in 2024-Q4, and
|
|
declined through 2025 to 0.450 despite continued material moderation. Then
|
|
hit 0.334 in 2026-Q1. But 2026-Q2 bounced back to 0.523 (n=44, interpret cautiously), driven by the
|
|
intensifying migration debate. Non-migration acceptance was a temporary
|
|
electoral shock; migration acceptance is durable and growing.
|
|
|
|
The gateway domain: migration. Migration is where the Overton shift is most
|
|
genuine. The frames right-wing parties learned there, they then applied
|
|
elsewhere. Material impact barely declined (−0.13), yet centrist support more
|
|
than doubled (0.153 → 0.369). Centrists went from zero support for M = 5
|
|
migration motions to nearly 20%. The gradient between impact levels flattened.
|
|
Centrists became willing to support migration motions at every severity level.
|
|
This is measurable acceptance expansion,
|
|
driven primarily by CDA and ChristenUnie rather than D66. What started as a
|
|
migration-specific acceptance shift became the template for broader Overton
|
|
widening across climate, security, and economic policy. As of 2026, migration
|
|
centrist support (0.395) exceeds non-migration (0.368) for the first time,
|
|
confirming that migration acceptance is durable while non-migration acceptance
|
|
was the temporary component. Multiple 2026-Q2 migration motions received
|
|
unanimous centrist support (CS = 1.00), including high-impact items.
|
|
|
|
### Limitations
|
|
|
|
- **Small-N time series:** 8 pre-2024 annual windows and 3 post-2024
|
|
(2026 is partial). Effect sizes are descriptive Cohen's d, not inferred from
|
|
a time-series model.
|
|
- **Coalition coding:** 2024 is ambiguous (Rutte IV until July, Schoof
|
|
thereafter). Opposition-only analysis and temporal timing mitigate this.
|
|
- **Mechanism classification:** Based on 150 post-2024 motions, single-classifier
|
|
assignment. Inter-rater agreement is moderate (κ = 0.41).
|
|
- **Causal direction:** The timing strongly supports an electoral explanation,
|
|
but this remains correlational.
|
|
- **Success ceiling:** 96%+ pass rate makes pass rate an insensitive dependent
|
|
variable.
|
|
|
|
### Explore the Data
|
|
|
|
This article is one surface of a three-tier analysis:
|
|
|
|
1. **Narrative spine.** You're reading it. The story, with the evidence.
|
|
2. **Technical appendices.** Detailed markdown reports in `reports/overton_window/`
|
|
cover every methodological decision, robustness check, and sensitivity
|
|
analysis.
|
|
3. **Live exploration.** Explore the Stemwijzer Explorer:
|
|
- **Kompas tab:** party positions on the SVD axes
|
|
- **Trajectories tab:** how parties drifted over time
|
|
- **Overton tab:** centrist support trends and right-wing motion browser
|
|
|
|
**Visit the Explorer** at `localhost:8501` to interact with the compass, plot
|
|
your position, and verify these findings against the underlying vote data.
|
|
|
|
```{python}
|
|
#| label: close-connection
|
|
#| include: false
|
|
|
|
con.close()
|
|
```
|
|
|