|
|
|
|
@ -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)
|
|
|
|
|
|