Compare commits

...

5 Commits

Author SHA1 Message Date
4e1360d49b README angepasst 2025-06-19 23:10:45 +02:00
d3f0d865a4 feat: CI-konforme Dendrogramm-Visualisierung mit Matplotlib als Abschluss der Analyse
- Neue Funktion `plot_dendrogram_matplotlib(df, scaled_anteile)` ergänzt:
  - erzeugt Dendrogramm der Kursanteile mit scipy + matplotlib
  - speichert das Diagramm als PNG im Forschungsprojektverzeichnis
  - automatische Farbgebung durch scipy.dendrogram()
  - sauberes CI-nahes Layout (Schriftgrößen, Achsentitel, Formatierung)

- Funktionsaufruf ans Ende des Skripts verschoben:
  - wird als letzter Schritt der statistischen Analyse ausgeführt
  - damit visuelle Gesamtstruktur abgeschlossen

- Bestehende Plotly-basierte Dendrogramm-Funktion bleibt erhalten (interaktiv, CI)
  - Fokus der matplotlib-Version liegt auf Publikationsreife und Clusterklarheit
2025-06-19 23:05:18 +02:00
ef140a5df1 DOI eingetragen 2025-05-31 22:12:40 +02:00
b56166a780 Aufräumen 2025-05-31 11:10:57 +02:00
0f6518aa1f Einfügen Tabelle statistische Werte 2025-05-31 11:09:38 +02:00
2 changed files with 187 additions and 30 deletions

View File

@ -13,6 +13,7 @@ Nachgewiesen werden:
## Inhalt
- `lms_statistische-analyse.py`: Hauptskript zur statistischen Auswertung
- `dendrogramm_cluster_anteile_matplotlib.png`: automatisch erzeugte Dendrogramm-Visualisierung auf Basis der Kursanteile (Ward-Linkage, matplotlib)
- `lms-verteilung.xlsx`: Zuordnung von Handlungssituationen zu APrV-Kürzeln
- `APrV-Kuerzel_zu_Kompetenzbereichen.csv`: Mapping von Kürzeln zu Kompetenzbereichen
- Visualisierungen werden automatisiert erzeugt; exportierte PNG-Dateien sind optional und werden lokal gespeichert
@ -22,6 +23,7 @@ Nachgewiesen werden:
- Automatisierte Zuordnung über Pandas
- Visualisierung mit Plotly (Boxplots, Tortendiagramme, Balkendiagramme)
- Vergleich mit APrV-Gesetzesvorgaben als Referenzstruktur
- Hierarchische Clusteranalyse mit `scipy` + `matplotlib` zur Dendrogrammerstellung auf Basis standardisierter Kursanteile
## Lizenz
@ -30,6 +32,8 @@ Dieses Projekt steht unter der [Creative Commons Attribution 4.0 International L
Für kommerzielle Nutzungen, die über die Bedingungen der CC BY 4.0 hinausgehen, bitte Kontakt an:
[kontakt@jochen-hanisch.de](mailto:kontakt@jochen-hanisch.de)
https://zenodo.org/records/15565960
## Hinweis
Dieses Repository ist Bestandteil der Dissertationsarbeit an der Charité Universitätsmedizin Berlin.

View File

@ -1,14 +1,16 @@
import os, re, sys, unicodedata, subprocess
from pathlib import Path
import pandas as pd
import plotly.graph_objs as go
import os
import re
import unicodedata
import subprocess
from collections import defaultdict
from config_lms import plotly_theme, export_fig_visual, export_fig_png
import sys
sys.path.append("/Users/jochen_hanisch-johannsen/Documents/scripte/Jochen-Hanisch/CI/ci_template")
from plotly_template import get_standard_layout, get_colors, set_theme
from pathlib import Path
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 ---
@ -44,8 +46,6 @@ def export_figure(fig, name, export_flag_html, export_flag_png):
except Exception as e:
print("❌ Fehler beim PNG-Export:", str(e))
# PNG-Exportpfad definieren
png_export_path = Path(__file__).parent.parent.parent / "Allgemein beruflich" / "Research" / "Charité - Universitätsmedizin Berlin" / "Dissertation" / "Abbildungen"
# Ursprüngliche Datentabelle (bitte bei Bedarf anpassen)
df = pd.DataFrame({
@ -80,9 +80,11 @@ df = pd.DataFrame({
]
})
# Zentrale Definition der Themenbereiche
THEMENBEREICHE = ["medizinisch", "rettungsdienstlich", "bezugswissenschaftlich", "Einführung/Prüfung"]
# --- Automatische Berechnung Themenbereichsanteile pro Kurs ---
# Ausschließlich systematische APrV-Zuordnung aus lms-verteilung.xlsx und zuordnung_praezise verwenden
from collections import defaultdict
zuordnung_praezise = {
# Medizinisch
@ -162,10 +164,6 @@ apr_kompetenz_df = pd.DataFrame({
"Anteil (%)": [24, 15, 11, 50]
})
# Umbenennung "unbekannt" → "Einführung/Prüfung" für valide Kategorisierung
df["Themenbereich"] = df["Themenbereich"].replace("unbekannt", "Einführung/Prüfung")
@ -196,18 +194,13 @@ gruppe = df.groupby("Themenbereich").agg({
gruppe.columns = ['Ø Aufgaben', 'SD Aufgaben', 'Anzahl Kurse', 'Ø Dauer', 'SD Dauer']
print("\nStatistik nach Themenbereich:\n", gruppe)
def plot_boxplots(df):
colors = get_colors()
# Spezifische Reihenfolge: medizinisch, rettungsdienstlich, bezugswissenschaftlich, Einführung/Prüfung
# Spezifische und dokumentierte Reihenfolge der Boxplots
kategorie_order = []
for k in ["medizinisch", "rettungsdienstlich", "bezugswissenschaftlich", "Einführung/Prüfung"]:
if k in df["Themenbereich"].unique():
kategorie_order.append(k)
# Ergänze ggf. andere Kategorien (falls weitere vorkommen)
for k in sorted(df["Themenbereich"].unique()):
if k not in kategorie_order:
kategorie_order.append(k)
for kategorie in ["medizinisch", "rettungsdienstlich", "bezugswissenschaftlich", "Einführung/Prüfung"]:
if kategorie in df["Themenbereich"].unique():
kategorie_order.append(kategorie)
# Aufgaben-Boxplot
fig1 = go.Figure()
@ -247,7 +240,6 @@ def plot_boxplots(df):
export_figure(fig2, fig2.layout.title.text, export_fig_visual, export_fig_png)
fig2.show()
def plot_aprv_pies():
colors = get_colors()
@ -286,7 +278,6 @@ def plot_aprv_pies():
export_figure(fig2, fig2.layout.title.text, export_fig_visual, export_fig_png)
fig2.show()
def plot_vergleich_lehrplan_aprv():
# Gruppierung der Kursdauer nach Themenbereich
nfsh_theme = df.groupby("Themenbereich")["Dauer [d]"].sum().reset_index()
@ -314,9 +305,6 @@ def plot_vergleich_lehrplan_aprv():
export_figure(fig, fig.layout.title.text, export_fig_visual, export_fig_png)
fig.show()
def plot_vergleich_kompetenz_aprv():
# Automatische Zuordnung der Kompetenzbereiche basierend auf CSV-Datei
kuerzel_df = pd.read_csv(Path(__file__).parent / "APrV-Kuerzel_zu_Kompetenzbereichen.csv")
@ -372,10 +360,175 @@ def plot_vergleich_kompetenz_aprv():
export_figure(fig, fig.layout.title.text, export_fig_visual, export_fig_png)
fig.show()
# --- Visualisierungen aufrufen ---
# Alle Visualisierungen in sinnvoller Reihenfolge aufrufen
plot_aprv_pies()
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): "<br>".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('<br>', ', ')}")
# 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)