fix: resolve SVD axis label alignment and score mismatch in tijdtraject view

Two related bugs fixed:

1. Label alignment: Removed static left_pole/right_pole from SVD_THEMES
   entries. These labels assumed a fixed flip direction but could mismatch
   with runtime flip computation, causing right-wing parties to appear on
   the wrong side. Labels are now always derived from positive_pole,
   negative_pole, and the runtime flip direction.

2. Score mismatch: Changed tijdtraject view for components 3-10 from
   load_party_scores_all_windows_aligned() to load_party_scores_all_windows().
   Procrustes alignment rotates the full 50-dim vector space to align
   components 1-2, but this also transforms components 3-10, making their
   scores incomparable with the single-window view. Per-window flip
   computation already handles orientation alignment for these components.

Also updated svd_labels.py to prefer analysis.config as the canonical
source for SVD_THEMES, falling back to explorer only when config is
unavailable.
This commit is contained in:
2026-04-12 21:02:28 +02:00
parent 54489b6a30
commit 823df6f9ee
4 changed files with 33 additions and 47 deletions
-20
View File
@@ -80,8 +80,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Rechts: PVV, VVD, NSC, BBB, JA21 — kabinetsbeleid, defensie en restricties",
"negative_pole": "Links: GroenLinks-PvdA, SP, PvdD, Volt, DENK — oppositie, zorg en multilateraal",
"left_pole": "Links: GroenLinks-PvdA, SP, PvdD, Volt, DENK — oppositie, zorg en multilateraal",
"right_pole": "Rechts: PVV, VVD, NSC, BBB, JA21 — kabinetsbeleid, defensie en restricties",
"flip": False,
},
2: {
@@ -97,8 +95,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "PVV en FVD — soevereiniteit en anti-establishment",
"negative_pole": "Overige partijen: VVD, CDA, SGP, ChristenUnie, GroenLinks-PvdA, D66, Volt, BBB",
"left_pole": "Overige partijen: VVD, CDA, SGP, ChristenUnie, GroenLinks-PvdA, D66, Volt, BBB",
"right_pole": "PVV en FVD — soevereiniteit en anti-establishment",
"flip": False,
},
3: {
@@ -116,8 +112,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Pro-verzorgingsstaat: SP, PvdD, GroenLinks-PvdA (anti-bezuinigingen)",
"negative_pole": "Marktliberaal en fiscaal conservatief: VVD, D66, CDA, SGP, BBB",
"left_pole": "Marktliberaal en fiscaal conservatief: VVD, D66, CDA, SGP, BBB",
"right_pole": "Pro-verzorgingsstaat: SP, PvdD, GroenLinks-PvdA (anti-bezuinigingen)",
"flip": True,
},
4: {
@@ -132,8 +126,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "D66, CDA, JA21 — moties met brede steun",
"negative_pole": "NSC, BBB — moties met andere focus",
"left_pole": "NSC, BBB — moties met andere focus",
"right_pole": "D66, CDA, JA21 — moties met brede steun",
"flip": True,
},
5: {
@@ -150,8 +142,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Christelijk-sociaal: ChristenUnie, SGP, CDA, NSC — gemeenschap en vrijwilligers",
"negative_pole": "Progressief-individueel: SP, VVD, GL-PvdA, PvdD, Volt — individuele rechten",
"left_pole": "Progressief-individueel: SP, VVD, GL-PvdA, PvdD, Volt — individuele rechten",
"right_pole": "Christelijk-sociaal: ChristenUnie, SGP, CDA, NSC — gemeenschap en vrijwilligers",
"flip": False,
},
6: {
@@ -168,8 +158,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Restrictief migratiebeleid: PVV, JA21, BBB, CDA, ChristenUnie, VVD, SGP, FVD, DENK",
"negative_pole": "Progressieve inclusie: SP, PvdD, D66, GL-PvdA, Volt — klimaat en diversiteit",
"left_pole": "Progressieve inclusie: SP, PvdD, D66, GL-PvdA, Volt — klimaat en diversiteit",
"right_pole": "Restrictief migratiebeleid: PVV, JA21, BBB, CDA, ChristenUnie, VVD, SGP, FVD, DENK",
"flip": False,
},
7: {
@@ -188,8 +176,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Praktisch-bestuurlijk: ChristenUnie, Volt, SGP, DENK, SP",
"negative_pole": "Ideologisch-principieel: GroenLinks-PvdA, VVD, FVD, JA21",
"left_pole": "Ideologisch-principieel: GroenLinks-PvdA, VVD, FVD, JA21",
"right_pole": "Praktisch-bestuurlijk: ChristenUnie, Volt, SGP, DENK, SP",
"flip": True,
},
8: {
@@ -207,8 +193,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Onderwijs en volksgezondheid: BBB, SGP, JA21 — vaccinatie, profielkeuze, woningbouw",
"negative_pole": "Zorg en toegankelijkheid: SP, DENK, PvdD, Volt — coronaonderzoek, energie, basispakket",
"left_pole": "Zorg en toegankelijkheid: SP, DENK, PvdD, Volt — coronaonderzoek, energie, basispakket",
"right_pole": "Onderwijs en volksgezondheid: BBB, SGP, JA21 — vaccinatie, profielkeuze, woningbouw",
"flip": False,
},
9: {
@@ -227,8 +211,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Pragmatisch-bestuurlijk: SGP, ChristenUnie, DENK, SP — concrete oplossingen",
"negative_pole": "Systeemhervorming: D66, JA21, PVV — idealistische beleidsposities",
"left_pole": "Systeemhervorming: D66, JA21, PVV — idealistische beleidsposities",
"right_pole": "Pragmatisch-bestuurlijk: SGP, ChristenUnie, DENK, SP — concrete oplossingen",
"flip": True,
},
10: {
@@ -247,8 +229,6 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Kritisch op overheidsbemoeienis: DENK, SP, PvdD — minder inspectielast en lastenverlichting",
"negative_pole": "Pro-regulering: GroenLinks-PvdA, CDA, SGP — veiligheid, naleving en handhaving",
"left_pole": "Pro-regulering: GroenLinks-PvdA, CDA, SGP — veiligheid, naleving en handhaving",
"right_pole": "Kritisch op overheidsbemoeienis: DENK, SP, PvdD — minder inspectielast en lastenverlichting",
"flip": True,
},
}
+19 -3
View File
@@ -34,12 +34,28 @@ def _get_svd_themes() -> Dict[int, Dict[str, str]]:
if _svd_themes_cache is not None:
return _svd_themes_cache
# Prefer the lightweight canonical source in analysis.config which is
# intentionally free of heavy runtime dependencies. Fall back to
# explorer.SVD_THEMES only when the config module is unavailable or
# doesn't expose SVD_THEMES.
try:
# Import SVD_THEMES from explorer at runtime to avoid circular imports
# explorer.py now exports SVD_THEMES at module level
from analysis import config as _cfg
_svd_themes_cache = getattr(_cfg, "SVD_THEMES", {}) or {}
if _svd_themes_cache:
return _svd_themes_cache
except Exception:
_logger.exception(
"Could not import analysis.config or read SVD_THEMES; falling back to explorer"
)
try:
# Import explorer at runtime as a last resort; explorer may pull in
# heavy dependencies (duckdb/plotly) so we only try this if config
# didn't provide the themes.
import explorer
_svd_themes_cache = explorer.SVD_THEMES
_svd_themes_cache = getattr(explorer, "SVD_THEMES", {}) or {}
return _svd_themes_cache
except ImportError as e:
_logger.warning("Could not import explorer.SVD_THEMES: %s", e)