@ -487,51 +487,18 @@ def load_party_axis_scores(db_path: str) -> Dict[str, List[float]]:
@st . cache_data ( show_spinner = " Scree-plot laden… " )
def load_scree_data ( db_path : str ) - > List [ float ] :
""" Return per-component importances (L2-norm per SVD dim) , sorted descending.
""" Return explained variance ratios ( % ) for all SVD components , sorted descending.
Uses individual MP vectors from current_parliament ( entity_id LIKE ' % , % ' ) .
Computes L2 - norm per SVD dimension across all MPs , then sorts descending
so the elbow shape is visible in the scree chart .
Uses the same Procrustes - aligned multi - window matrix as the compass axes so the
scree plot is consistent with what the compass actually uses .
"""
try :
con = duckdb . connect ( database = db_path , read_only = True )
rows = con . execute (
" SELECT entity_id, vector FROM svd_vectors "
" WHERE entity_type= ' mp ' AND window_id= ' current_parliament ' "
" AND entity_id LIKE ' % , % ' "
) . fetchall ( )
vectors : List [ List [ float ] ] = [ ]
for entity_id , raw_vec in rows :
if isinstance ( raw_vec , str ) :
vec = json . loads ( raw_vec )
elif isinstance ( raw_vec , ( bytes , bytearray ) ) :
vec = json . loads ( raw_vec . decode ( ) )
elif isinstance ( raw_vec , list ) :
vec = raw_vec
else :
try :
vec = list ( raw_vec )
except Exception :
continue
fvec = [ float ( v ) if v is not None else 0.0 for v in vec ]
vectors . append ( fvec )
if not vectors :
return [ ]
n_dims = len ( vectors [ 0 ] )
importances : List [ float ] = [ ]
for dim in range ( n_dims ) :
col = [ v [ dim ] for v in vectors if dim < len ( v ) ]
l2 = sum ( x * * 2 for x in col ) * * 0.5
importances . append ( l2 )
return sorted ( importances , reverse = True )
from analysis . political_axis import compute_svd_spectrum
return compute_svd_spectrum ( db_path )
except Exception :
logger . exception ( " Failed to load scree data " )
return [ ]
finally :
try :
con . close ( )
except Exception :
pass
def _render_scree_plot ( importances : List [ float ] , n_show : int = 15 ) - > None :
@ -547,9 +514,9 @@ def _render_scree_plot(importances: List[float], n_show: int = 15) -> None:
"""
if not importances :
return
total = sum ( importances ) or 1.0
raw = importances [ : n_show ]
data = [ v / total * 100 for v in raw ]
# importances are already EVR percentages summing to ~100 over all components.
# Slice to n_show for display; cumulative line shows how much variance is covered.
data = list ( importances [ : n_show ] )
ranks = list ( range ( 1 , len ( data ) + 1 ) )
# Cumulative variance for the dashed overlay line
@ -573,7 +540,7 @@ def _render_scree_plot(importances: List[float], n_show: int = 15) -> None:
x = ranks ,
y = data ,
marker_color = bar_colours ,
hovertemplate = " As % {x} <br><b> % {y:.1f} % </b> van totaal <extra></extra> " ,
hovertemplate = " As % {x} <br><b> % {y:.1f} % </b> verklaarde variantie <extra></extra> " ,
showlegend = False ,
)
)