diff --git a/lms_statistische-analyse.py b/lms_statistische-analyse.py
index c5eac8e..5d39471 100644
--- a/lms_statistische-analyse.py
+++ b/lms_statistische-analyse.py
@@ -8,6 +8,9 @@ from ci_template.plotly_template import (
get_standard_layout, get_colors, set_theme,
plot_table_from_dict, plot_table_from_dataframe
)
+from sklearn.cluster import KMeans
+from sklearn.metrics import silhouette_score
+from sklearn.preprocessing import StandardScaler
set_theme(plotly_theme)
# --- Hilfsfunktionen für Export und Slugify ---
@@ -364,6 +367,168 @@ plot_vergleich_lehrplan_aprv()
plot_vergleich_kompetenz_aprv()
plot_boxplots(df)
+
+# --- K-Means-Clusteranalyse auf Basis der Themenbereichsanteile ---
+anteilsmerkmale = ['Anteil medizinisch', 'Anteil rettungsdienstlich', 'Anteil bezugswissenschaftlich']
+
+if df[anteilsmerkmale].nunique().eq(1).all():
+ print("⚠️ Alle Anteilsmerkmale sind konstant. K-Means-Clustering wird übersprungen.")
+ df['Cluster_Anteile'] = 'Nicht gültig'
+ silhouette_avg_anteile = None
+else:
+ try:
+ scaler = StandardScaler()
+ scaled_anteile = scaler.fit_transform(df[anteilsmerkmale])
+
+ if len(df) < 2:
+ raise ValueError("Zu wenige Datenpunkte für die Clusteranalyse der Anteile.")
+
+ kmeans_anteile = KMeans(n_clusters=4, random_state=42)
+ df['Cluster_Anteile'] = kmeans_anteile.fit_predict(scaled_anteile).astype(str)
+
+ silhouette_avg_anteile = silhouette_score(scaled_anteile, df['Cluster_Anteile'].astype(int))
+ print(f"Silhouette-Score (Anteile): {silhouette_avg_anteile:.4f}")
+
+ # Beschreibung der Cluster
+ cluster_means_anteile = df[anteilsmerkmale + ['Cluster_Anteile']].groupby('Cluster_Anteile').mean()
+ cluster_labels_anteile = {
+ str(c): "
".join(cluster_means_anteile.loc[str(c)].sort_values(ascending=False).head(3).index)
+ for c in cluster_means_anteile.index
+ }
+ df['Cluster_Label_Anteile'] = df['Cluster_Anteile'].map(cluster_labels_anteile)
+
+ for cluster, label in cluster_labels_anteile.items():
+ print(f"Cluster {cluster}: {label.replace('
', ', ')}")
+
+ # Visualisierung als 3D-Scatterplot
+ import plotly.express as px
+ colors = get_colors()
+ fig_anteile = px.scatter_3d(
+ df,
+ x='Anteil medizinisch',
+ y='Anteil rettungsdienstlich',
+ z='Anteil bezugswissenschaftlich',
+ color='Cluster_Label_Anteile',
+ size_max=50,
+ color_discrete_sequence=list(colors.values()),
+ hover_data=anteilsmerkmale + ['Cluster_Label_Anteile'],
+ title=f"Clusteranalyse (Themenanteile) | Silhouette: {silhouette_avg_anteile:.4f}",
+ labels={
+ 'Anteil medizinisch': 'Medizinisch',
+ 'Anteil rettungsdienstlich': 'Rettungsdienstlich',
+ 'Anteil bezugswissenschaftlich': 'Bezugswissenschaftlich',
+ 'Cluster_Label_Anteile': 'Cluster-Beschreibung'
+ }
+ )
+ fig_anteile.update_layout(get_standard_layout(
+ title="K-Means-Clusteranalyse nach Themenanteilen",
+ x_title='Medizinisch',
+ y_title='Rettungsdienstlich',
+ z_title='Bezugswissenschaftlich'
+ ))
+ export_figure(fig_anteile, "kmeans_cluster_anteile", export_fig_visual, export_fig_png)
+
+ except Exception as e:
+ print(f"Fehler bei der Clusteranalyse auf Anteilsdaten: {e}")
+ df['Cluster_Anteile'] = 'Fehler'
+ silhouette_avg_anteile = None
+
+
+
+# --- Hierarchisches Clustering der Themenanteile mit Dendrogramm ---
+
+
+# --- Interaktives Dendrogramm mit Plotly ---
+
+from scipy.cluster.hierarchy import dendrogram, linkage
+import plotly.graph_objects as go
+
+# Berechnung der Linkage-Matrix für hierarchisches Clustering
+try:
+ linkage_matrix = linkage(scaled_anteile, method='ward')
+except Exception as e:
+ print(f"Fehler beim Erzeugen der Linkage-Matrix für das Dendrogramm: {e}")
+ linkage_matrix = None
+
+def create_plotly_dendrogram(linkage_matrix, labels, orientation='bottom'):
+ colors = get_colors()
+ dendro = dendrogram(linkage_matrix, labels=labels, orientation=orientation, no_plot=True)
+ icoord = dendro['icoord']
+ dcoord = dendro['dcoord']
+ color_list = dendro['color_list']
+ label = dendro['ivl']
+
+ data = []
+ for i in range(len(icoord)):
+ xs = icoord[i]
+ ys = dcoord[i]
+ data.append(go.Scatter(
+ x=xs,
+ y=ys,
+ mode='lines',
+ line=dict(color=colors["primaryLine"], width=2),
+ hoverinfo='none'
+ ))
+
+ layout = get_standard_layout(
+ title="Interaktives Dendrogramm der Kursanteile (Ward-Linkage)",
+ x_title="Kursbezeichnung",
+ y_title="Distanz"
+ )
+ layout.update(dict(
+ xaxis=dict(tickvals=list(range(5, len(labels)*10+5, 10)), ticktext=label, tickangle=90),
+ showlegend=False,
+ margin=dict(l=40, r=0, b=120, t=50),
+ width=1200,
+ height=600
+ ))
+
+ fig_dendrogramm_anteile = go.Figure(data=data, layout=layout)
+ export_figure(fig_dendrogramm_anteile, "dendrogramm_cluster_anteile", export_fig_visual, export_fig_png)
+ fig_dendrogramm_anteile.show()
+
+
+# linkage_matrix und scaled_anteile werden weiter oben erzeugt
+create_plotly_dendrogram(linkage_matrix, df["Kursbezeichnung"].tolist())
+
+
# Tabellen-Visualisierungen nach bisherigen Plots aus ci_template aufrufen
plot_table_from_dict(gesamt_stats, "Gesamtstatistik der curricularen Struktur", export_figure, export_fig_visual, export_fig_png)
plot_table_from_dataframe(gruppe, "Themenbereich", "Gruppierte Statistik nach Themenbereichen", export_figure, export_fig_visual, export_fig_png)
+
+
+# --- Matplotlib-Dendrogramm-Plot und Export (CI-konform) ---
+import matplotlib.pyplot as plt
+from scipy.cluster.hierarchy import dendrogram, linkage
+
+def plot_dendrogram_matplotlib(df, scaled_anteile):
+ try:
+ linkage_matrix = linkage(scaled_anteile, method='ward')
+ fig, ax = plt.subplots(figsize=(16, 8))
+ dendrogram(
+ linkage_matrix,
+ labels=df["Kursbezeichnung"].tolist(),
+ leaf_rotation=90,
+ leaf_font_size=10,
+ color_threshold=None
+ )
+ ax.set_title("Dendrogramm der Kursanteile (Ward-Linkage)", fontsize=14)
+ ax.set_xlabel("Kursbezeichnung", fontsize=12)
+ ax.set_ylabel("Distanz", fontsize=12)
+ fig.tight_layout()
+
+ # Speichern
+ export_path_png = os.path.join(
+ "..", "Allgemein beruflich", "Research", "Forschungsprojekte",
+ "Systemische Kompetenzentwicklung für High Responsibility Teams",
+ "dendrogramm_cluster_anteile_matplotlib.png"
+ )
+ fig.savefig(export_path_png, dpi=300)
+ print(f"✅ Matplotlib-Dendrogramm gespeichert: {export_path_png}")
+ plt.close(fig)
+ except Exception as e:
+ print(f"❌ Fehler beim Erzeugen des Matplotlib-Dendrogramms: {e}")
+
+
+# Am Ende aufrufen
+plot_dendrogram_matplotlib(df, scaled_anteile)