---
title: "Quantifying Political Extremity: Voting vs Policy"
date: 2026-04-05
module: analysis
problem_type: research
component: motion-analysis
tags: [polarization, voting-extremity, policy-extremity, embedding-analysis, parliamentary-motion]
---

# Quantifying Political Extremity: Voting vs Policy

## Context

Initial analysis of parliamentary motions sought to measure polarization by examining how "extreme" policies have become. The hypothesis was that extremes on both sides became more extreme. The analysis revealed this hypothesis was incorrect — and surfaced two independent phenomena.

## Guidance

### Key Finding: Two Independent Measures of Extremity

**Voting Extremity** and **Policy Extremity** are independent phenomena with different trends:

| Measure | 2016 | 2026 | Trend |
|---------|------|------|-------|
| **Voting Extremity** (margin/total) | 0.70 | 0.46 | Parliament votes more closely |
| **Policy Extremity** (embedding distance from mainstream) | 5.65 | 4.17 | Policies are less extreme |

**Correlation: r ≈ 0** — these measures are statistically independent.

### What Each Measures

**Voting Extremity** = `abs(votes_for - votes_against) / total_votes`
- 1.0 = unanimous (all votes same direction)
- 0.0 = perfectly split (50-50)
- Captures how divided parliament is when voting

**Policy Extremity** = `||embedding - mainstream_centroid||`
- Euclidean distance in text embedding space (2560 dims)
- Captures how far a motion is from the political center

### How to Measure Each

```python
# Voting Extremity
margin = abs(votes_for - votes_against)
total = votes_for + votes_against
voting_extremity = margin / total

# Policy Extremity (using text embeddings, not SVD)
from embeddings table (qwen/qwen3-embedding-4b)
policy_extremity = np.linalg.norm(motion_embedding - mainstream_centroid)
```

### Why Use Text Embeddings (Not SVD)

SVD embeddings are fitted on **voting patterns**, capturing how parties vote together. They measure **voting extremity**, not **policy extremity**.

For policy content, use **raw text embeddings** (`embeddings` table, 2560 dimensions) which are computed from motion text only.

### Bipartisan Anchor Approach

Define the "mainstream" as the centroid of bipartisan motions (80%+ parties vote the same way):

```python
# Find bipartisan motions
bipartisan = [m for m in motions if majority_vote_pct >= 0.80]

# Compute mainstream centroid
mainstream_centroid = mean([m.embedding for m in bipartisan])

# Measure policy extremity
policy_extremity = ||motion.embedding - mainstream_centroid||
```

## Why This Matters

The hypothesis "extremes became more extreme" was wrong because:

1. **Voting extremity increased** — parliament votes more divided now
2. **Policy extremity decreased** — even extreme motions are closer to center

This means: what divides parties changed, not how radical the policies are.

## Quantifying Mainstream Shift

Using 2018 as baseline ("last normal year"):

| Period | Distance from 2018 | Interpretation |
|--------|-------------------|----------------|
| 2016-2018 | ~0.22 | Similar mainstream |
| **2019** | **0.46** | Shift begins |
| 2020-2026 | **~0.71** | New stable mainstream |

The mainstream shifted **0.71 units** after 2018 and has remained stable.

### Coalition Shift on Migration Policy

Parties that once opposed strict migration now vote for them:

| Party | 2016-2018 | 2025-2026 | Change |
|-------|------------|------------|--------|
| VVD | 100% voor | 78% voor | ↓ |
| CDA | 100% voor | 81% voor | ↓ |
| D66 | 100% voor | 60% voor | ↓↓ |
| PVV | 20% voor | 56% voor | ↑↑ |
| NSC | 0% (new) | 56% voor | new |
| BBB | 0% (new) | 79% voor | new |

## When to Apply

- When analyzing parliamentary polarization trends
- When comparing policy extremity across time periods
- When studying coalition formation and party positioning
- When testing hypotheses about political extremism

## Examples

### Correct Analysis
```python
# Compare voting extremity and policy extremity separately
voting_ext = compute_voting_margin(motion)
policy_ext = compute_embedding_distance(motion, mainstream_centroid)

# Plot both trends independently
plot_trend(years, voting_ext, label="Voting Extremity")
plot_trend(years, policy_ext, label="Policy Extremity")
```

### Incorrect Analysis
```python
# DON'T use SVD scores to measure policy extremity
svd_score = motion.svd_vector[0]  # This measures voting pattern, not content!

# DO use text embeddings for policy content
text_embedding = embeddings_table[motion.id]
```

## Related Findings

- `svd-stability-vs-overtone-shift.md` — SVD axes measure voting structure, not semantics
- `policy-extremity-vs-voting-extremity.md` — Initial documentation of the distinction

## Visualizations

- `docs/research/polarization_comprehensive.png` — Combined view of all metrics
- `docs/research/mainstream_shift.png` — Mainstream shift over time
- `docs/research/voting_vs_policy_extremity.png` — Independent trends
