382 lines
17 KiB
Python
382 lines
17 KiB
Python
import pandas as pd
|
|
import plotly.graph_objs as go
|
|
import os
|
|
import re
|
|
import unicodedata
|
|
import subprocess
|
|
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
|
|
set_theme(plotly_theme)
|
|
|
|
# --- Hilfsfunktionen für Export und Slugify ---
|
|
def slugify(text):
|
|
text = unicodedata.normalize('NFKD', text)
|
|
text = text.encode('ascii', 'ignore').decode('ascii')
|
|
text = re.sub(r'[^\w\s-]', '', text)
|
|
text = re.sub(r'[\s]+', '-', text)
|
|
return text.strip().lower()
|
|
|
|
def export_figure(fig, name, export_flag_html, export_flag_png):
|
|
filename_part = "notsan-aprv-vergleich"
|
|
safe_filename = slugify(f"{name}_{filename_part}")
|
|
remote_path = "jochen-hanisch@sternenflottenakademie.local:/mnt/deep-space-nine/public/plot/promotion/"
|
|
|
|
if export_flag_html:
|
|
export_path_html = os.path.join("..", "Allgemein beruflich", "Research", "Forschungsprojekte", "Systemische Kompetenzentwicklung für High Responsibility Teams", f"{safe_filename}.html")
|
|
fig.write_html(export_path_html, full_html=True, include_plotlyjs="cdn")
|
|
try:
|
|
subprocess.run(["scp", export_path_html, remote_path], check=True)
|
|
print(f"✅ HTML-Datei '{export_path_html}' erfolgreich übertragen.")
|
|
os.remove(export_path_html)
|
|
print(f"🗑️ Lokale HTML-Datei '{export_path_html}' wurde gelöscht.")
|
|
except subprocess.CalledProcessError as e:
|
|
print("❌ Fehler beim HTML-Übertragen:")
|
|
print(e.stderr)
|
|
|
|
if export_flag_png:
|
|
export_path_png = os.path.join("..", "Allgemein beruflich", "Research", "Forschungsprojekte", "Systemische Kompetenzentwicklung für High Responsibility Teams", f"{safe_filename}.png")
|
|
try:
|
|
fig.write_image(export_path_png, width=1200, height=800, scale=2)
|
|
print(f"✅ PNG-Datei lokal gespeichert: '{export_path_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({
|
|
"Kursbezeichnung": [
|
|
"NFS-H-01", "NFS-H-02", "NFS-H-03", "NFS-H-04", "NFS-H-05", "NFS-H-06",
|
|
"NFS-H-07", "NFS-H-08", "NFS-H-09", "NFS-H-10", "NFS-H-11", "NFS-H-12",
|
|
"NFS-H-13", "NFS-H-14", "NFS-H-15", "NFS-H-16", "NFS-H-17", "NFS-H-18",
|
|
"NFS-H-19", "NFS-H-20", "NFS-H-21", "NFS-H-22", "NFS-H-23", "NFS-H-24",
|
|
"NFS-H-25", "NFS-H-26", "NFS-H-27", "NFS-H-28", "NFS-H-29", "NFS-H-30",
|
|
"NFS-H-31", "NFS-H-32"
|
|
],
|
|
"Titel": [
|
|
"Einführung in die berufliche Ausbildung", "Das eigene Berufsfeld erkunden und berufliches Selbstverständnis entwickeln",
|
|
"Die eigene Lehrrettungswache erleben", "Das rettungsdienstliche Umfeld kennen lernen", "Mit sich selbst und Anderen umgehen",
|
|
"Einen Patienten im Krankentransport beurteilen", "Lebensrettende Maßnahmen durchführen", "Eine Einsatzfahrt durchführen",
|
|
"Einen Krankentransport durchführen", "Mit BOS-Peers kommunizieren", "Mit unterschiedlichen Patienten-Peers kommunizieren",
|
|
"Beratungsgespräche führen", "Mit Sterben im Rettungsdienst umgehen", "Einen Einsatz in der primären Notfallmedizin durchführen",
|
|
"Einen Notfallpatienten beurteilen", "Patienten mit A-Problem behandeln", "Patienten mit B-Problem behandeln",
|
|
"Patienten mit C-Problem behandeln", "Patienten mit D-Problem behandeln", "Patienten mit E-Problem behandeln",
|
|
"Einen pädiatrischen Patienten behandeln", "Einen Einsatz in der Sekundärrettung durchführen",
|
|
"Urologische und nephrologische Notfälle versorgen", "Hals-Nasen-Ohren Erkrankungen und Verletzungen versorgen",
|
|
"Mit psychischen Erkrankungen und Notfällen umgehen", "Eine gynäkologische Patientin versorgen",
|
|
"Notfälle der Wasserrettung versorgen", "Einsätze mit besonderen Verhaltensmaßnahmen durchführen",
|
|
"Einsätze mit besonderer Logistik durchführen", "Mit BOS zusammenarbeiten", "Das eigene Berufsfeld reflektieren",
|
|
"Vorbereitung auf die Notfallsanitäterprüfung"
|
|
],
|
|
"∑ Aufgaben": [
|
|
6, 16, 28, 37, 31, 14, 47, 36, 11, 25, 12, 18, 15, 18, 24, 26, 24, 36, 20, 57, 15, 11, 17, 19, 11, 22, 9, 61, 55, 13, 54, 60
|
|
],
|
|
"Dauer [d]": [
|
|
7, 32, 17, 31, 53, 15, 67, 77, 13, 42, 2, 7, 4, 20, 31, 48, 7, 12, 29, 90, 2, 53, 5, 2, 42, 18, 2, 93, 100, 5, 5, 40
|
|
]
|
|
})
|
|
|
|
# --- 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
|
|
"1a": "medizinisch", "1b": "medizinisch", "1c": "medizinisch", "1d": "rettungsdienstlich", "1e": "medizinisch", "1f": "medizinisch",
|
|
"7a": "medizinisch", "7b": "medizinisch", "7c": "medizinisch", "7d": "medizinisch",
|
|
"7e": "medizinisch", "7f": "medizinisch", "7g": "medizinisch", "7h": "medizinisch", "7i": "medizinisch",
|
|
# Rettungsdienstlich
|
|
"2a": "rettungsdienstlich", "2b": "rettungsdienstlich", "2c": "rettungsdienstlich", "2d": "rettungsdienstlich", "2e": "rettungsdienstlich", "2f": "rettungsdienstlich", "2g": "rettungsdienstlich", "2h": "rettungsdienstlich",
|
|
"4a": "rettungsdienstlich", "4b": "rettungsdienstlich", "4c": "rettungsdienstlich",
|
|
"5a": "rettungsdienstlich", "5b": "rettungsdienstlich", "5c": "rettungsdienstlich", "5d": "rettungsdienstlich", "5e": "rettungsdienstlich",
|
|
# Bezugswissenschaftlich
|
|
"3a": "bezugswissenschaftlich", "3b": "bezugswissenschaftlich", "3c": "bezugswissenschaftlich", "3d": "bezugswissenschaftlich", "3e": "bezugswissenschaftlich",
|
|
"6a": "bezugswissenschaftlich", "6b": "bezugswissenschaftlich", "6c": "bezugswissenschaftlich", "6d": "bezugswissenschaftlich",
|
|
"8a": "bezugswissenschaftlich", "8b": "bezugswissenschaftlich", "8c": "bezugswissenschaftlich", "8d": "bezugswissenschaftlich",
|
|
"9a": "bezugswissenschaftlich", "9b": "bezugswissenschaftlich", "9c": "bezugswissenschaftlich", "9d": "bezugswissenschaftlich", "9e": "bezugswissenschaftlich",
|
|
"10a": "bezugswissenschaftlich", "10b": "bezugswissenschaftlich", "10c": "bezugswissenschaftlich", "10d": "bezugswissenschaftlich"
|
|
}
|
|
|
|
# Systematische Zuordnung aus externer Excel
|
|
df_para = pd.read_excel(Path(__file__).parent / "lms-verteilung.xlsx")
|
|
df_para = df_para.set_index("Nummer")
|
|
|
|
anteile_liste = []
|
|
for idx, row in df.iterrows():
|
|
kursnummer = int(row["Kursbezeichnung"].split("-")[-1])
|
|
if kursnummer not in df_para.index:
|
|
# Keine manuelle Kurs-Zuordnung mehr, ausschließlich systematische APrV-Zuordnung
|
|
d = "unbekannt"
|
|
print(f"❗ Kurs {kursnummer} ({row['Titel']}) hat unbekannten Themenbereich.")
|
|
print(f" → Verwendete APrV-Kürzel: {[ ]}")
|
|
anteile_liste.append((0, 0, 0, "unbekannt"))
|
|
continue
|
|
else:
|
|
para_raw = df_para.loc[kursnummer, "NotSan-APrV"]
|
|
if pd.isna(para_raw):
|
|
d = "unbekannt"
|
|
print(f"❗ Kurs {kursnummer} ({row['Titel']}) hat unbekannten Themenbereich.")
|
|
print(f" → Verwendete APrV-Kürzel: {[ ]}")
|
|
anteile_liste.append((0, 0, 0, "unbekannt"))
|
|
continue
|
|
else:
|
|
para_liste = [p.strip() for p in str(para_raw).split(",")]
|
|
zähler = defaultdict(int)
|
|
gesamt = 0
|
|
for para in para_liste:
|
|
bereich = zuordnung_praezise.get(para)
|
|
if bereich:
|
|
zähler[bereich] += 1
|
|
gesamt += 1
|
|
m = round(zähler["medizinisch"] / gesamt * 100, 2) if gesamt else 0
|
|
r = round(zähler["rettungsdienstlich"] / gesamt * 100, 2) if gesamt else 0
|
|
b = round(zähler["bezugswissenschaftlich"] / gesamt * 100, 2) if gesamt else 0
|
|
d = max(("medizinisch", m), ("rettungsdienstlich", r), ("bezugswissenschaftlich", b), key=lambda x: x[1])[0] if gesamt else "unbekannt"
|
|
# Debug-Ausgabe bei unbekanntem Themenbereich
|
|
if d == "unbekannt":
|
|
print(f"❗ Kurs {kursnummer} ({row['Titel']}) hat unbekannten Themenbereich.")
|
|
print(f" → Verwendete APrV-Kürzel: {para_liste}")
|
|
anteile_liste.append((m, r, b, d))
|
|
|
|
df["Anteil medizinisch"], df["Anteil rettungsdienstlich"], df["Anteil bezugswissenschaftlich"], df["Themenbereich"] = zip(*anteile_liste)
|
|
|
|
#
|
|
# --- APrV-Grundlagen: Themenschwerpunkte und Kompetenzbereiche ---
|
|
#
|
|
# Grundlage: APrV-Zuordnung durch Schüler*innen (prozentuale Verteilung nach Stunden)
|
|
# Thematische Gewichtung nach APrV
|
|
apr_theme_df = pd.DataFrame({
|
|
"Thema": ["medizinisch", "rettungsdienstlich", "bezugswissenschaftlich"],
|
|
"Stunden": [496, 909, 515],
|
|
"Anteil (%)": [27, 47, 26]
|
|
})
|
|
|
|
# Kompetenzgewichtung nach APrV
|
|
apr_kompetenz_df = pd.DataFrame({
|
|
"Kompetenz": ["fachlich", "sozial", "personal", "methodisch"],
|
|
"Stunden": [464, 294, 206, 956],
|
|
"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")
|
|
|
|
# Deskriptive Gesamtstatistik
|
|
gesamt_stats = {
|
|
"Anzahl Kurse": len(df),
|
|
"Gesamtaufgaben": df["∑ Aufgaben"].sum(),
|
|
"Gesamtdauer [d]": df["Dauer [d]"].sum(),
|
|
"Ø Aufgaben pro Kurs": df["∑ Aufgaben"].mean(),
|
|
"Ø Dauer pro Kurs [d]": df["Dauer [d]"].mean(),
|
|
"Median Aufgaben": df["∑ Aufgaben"].median(),
|
|
"Median Dauer [d]": df["Dauer [d]"].median(),
|
|
"Standardabweichung Aufgaben": df["∑ Aufgaben"].std(),
|
|
"Standardabweichung Dauer [d]": df["Dauer [d]"].std(),
|
|
"Min Aufgaben": df["∑ Aufgaben"].min(),
|
|
"Max Aufgaben": df["∑ Aufgaben"].max(),
|
|
"Min Dauer [d]": df["Dauer [d]"].min(),
|
|
"Max Dauer [d]": df["Dauer [d]"].max(),
|
|
"Korrelation Aufgaben vs. Dauer": df["∑ Aufgaben"].corr(df["Dauer [d]"])
|
|
}
|
|
print("Gesamtstatistik:\n", pd.Series(gesamt_stats).round(2))
|
|
|
|
# Gruppierte Statistik
|
|
gruppe = df.groupby("Themenbereich").agg({
|
|
"∑ Aufgaben": ["mean", "std", "count"],
|
|
"Dauer [d]": ["mean", "std"]
|
|
}).round(2)
|
|
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
|
|
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)
|
|
|
|
# Aufgaben-Boxplot
|
|
fig1 = go.Figure()
|
|
for k in kategorie_order:
|
|
d = df[df["Themenbereich"] == k]
|
|
fig1.add_trace(go.Box(
|
|
y=d["∑ Aufgaben"],
|
|
name=k,
|
|
boxmean='sd',
|
|
marker=dict(color=colors["secondaryLine"]),
|
|
line=dict(color=colors["primaryLine"])
|
|
))
|
|
fig1.update_layout(get_standard_layout(
|
|
title="Verteilung der Aufgaben pro Themenbereich",
|
|
x_title="Themenbereich",
|
|
y_title="∑ Aufgaben"
|
|
))
|
|
export_figure(fig1, fig1.layout.title.text, export_fig_visual, export_fig_png)
|
|
fig1.show()
|
|
|
|
# Dauer-Boxplot
|
|
fig2 = go.Figure()
|
|
for k in kategorie_order:
|
|
d = df[df["Themenbereich"] == k]
|
|
fig2.add_trace(go.Box(
|
|
y=d["Dauer [d]"],
|
|
name=k,
|
|
boxmean='sd',
|
|
marker=dict(color=colors["secondaryLine"]),
|
|
line=dict(color=colors["primaryLine"])
|
|
))
|
|
fig2.update_layout(get_standard_layout(
|
|
title="Verteilung der Kursdauer pro Themenbereich",
|
|
x_title="Themenbereich",
|
|
y_title="Dauer in Tagen"
|
|
))
|
|
export_figure(fig2, fig2.layout.title.text, export_fig_visual, export_fig_png)
|
|
fig2.show()
|
|
|
|
|
|
def plot_aprv_pies():
|
|
colors = get_colors()
|
|
|
|
# Thema-Tortendiagramm
|
|
fig1 = go.Figure(data=[go.Pie(
|
|
labels=apr_theme_df["Thema"],
|
|
values=apr_theme_df["Anteil (%)"],
|
|
marker=dict(colors=[colors["primaryLine"], colors["secondaryLine"], colors["depthArea"]]),
|
|
textinfo='label+percent',
|
|
insidetextorientation='radial'
|
|
)])
|
|
fig1.update_layout(get_standard_layout(
|
|
title="Anteil der Themenbereiche nach APrV",
|
|
x_title="", y_title=""
|
|
))
|
|
fig1.update_layout(showlegend=True)
|
|
export_figure(fig1, fig1.layout.title.text, export_fig_visual, export_fig_png)
|
|
fig1.show()
|
|
|
|
# Kompetenz-Tortendiagramm
|
|
fig2 = go.Figure(data=[go.Pie(
|
|
labels=apr_kompetenz_df["Kompetenz"],
|
|
values=apr_kompetenz_df["Anteil (%)"],
|
|
marker=dict(colors=[
|
|
colors["primaryLine"], colors["secondaryLine"],
|
|
colors["depthArea"], colors["brightArea"]
|
|
]),
|
|
textinfo='label+percent',
|
|
insidetextorientation='radial'
|
|
)])
|
|
fig2.update_layout(get_standard_layout(
|
|
title="Anteil der Kompetenzbereiche nach APrV",
|
|
x_title="", y_title=""
|
|
))
|
|
fig2.update_layout(showlegend=True)
|
|
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()
|
|
nfsh_theme["Anteil (%)"] = 100 * nfsh_theme["Dauer [d]"] / nfsh_theme["Dauer [d]"].sum()
|
|
|
|
# Zusammenführung mit Schüler*innen-Schätzung aus APrV
|
|
vergleich_df = pd.merge(
|
|
apr_theme_df[["Thema", "Anteil (%)"]].rename(columns={"Anteil (%)": "Schätzung APrV"}),
|
|
nfsh_theme.rename(columns={"Themenbereich": "Thema", "Anteil (%)": "NFS-H-Anteil"}),
|
|
on="Thema"
|
|
)
|
|
|
|
# Visualisierung: Gegenüberstellung als Balkendiagramm
|
|
colors = get_colors()
|
|
fig = go.Figure(data=[
|
|
go.Bar(name='Schätzung APrV', x=vergleich_df["Thema"], y=vergleich_df["Schätzung APrV"],
|
|
marker_color=colors["primaryLine"]),
|
|
go.Bar(name='NFS-H-Anteil', x=vergleich_df["Thema"], y=vergleich_df["NFS-H-Anteil"],
|
|
marker_color=colors["secondaryLine"])
|
|
])
|
|
fig.update_layout(
|
|
get_standard_layout("Vergleich Themengewichtung: APrV-Schätzung vs. NFS-H-Lehrplan", "Thema", "Anteil [%]"),
|
|
barmode='group'
|
|
)
|
|
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")
|
|
kuerzel_map = dict(zip(kuerzel_df["Kürzel"], kuerzel_df["Kompetenzbereich"]))
|
|
|
|
kompetenz_liste = []
|
|
for idx, row in df.iterrows():
|
|
kursnummer = int(row["Kursbezeichnung"].split("-")[-1])
|
|
if kursnummer not in df_para.index:
|
|
kompetenz_liste.append("unbekannt")
|
|
continue
|
|
para_raw = df_para.loc[kursnummer, "NotSan-APrV"]
|
|
if pd.isna(para_raw):
|
|
kompetenz_liste.append("unbekannt")
|
|
continue
|
|
para_liste = [p.strip() for p in str(para_raw).split(",")]
|
|
zähler = defaultdict(int)
|
|
for para in para_liste:
|
|
komp = kuerzel_map.get(para)
|
|
if komp:
|
|
zähler[komp] += 1
|
|
if zähler:
|
|
hauptkategorie = max(zähler.items(), key=lambda x: x[1])[0]
|
|
else:
|
|
hauptkategorie = "unbekannt"
|
|
kompetenz_liste.append(hauptkategorie)
|
|
|
|
df["Kompetenzbereich"] = kompetenz_liste
|
|
|
|
# Gruppierung nach Kompetenzbereich
|
|
nfsh_k = df.groupby("Kompetenzbereich")["Dauer [d]"].sum().reset_index()
|
|
nfsh_k["Anteil (%)"] = 100 * nfsh_k["Dauer [d]"] / nfsh_k["Dauer [d]"].sum()
|
|
|
|
# Merge mit APrV-Kompetenzverteilung
|
|
vergleich_k = pd.merge(
|
|
apr_kompetenz_df[["Kompetenz", "Anteil (%)"]].rename(columns={"Anteil (%)": "Schätzung APrV"}),
|
|
nfsh_k.rename(columns={"Kompetenzbereich": "Kompetenz", "Anteil (%)": "NFS-H-Anteil"}),
|
|
on="Kompetenz"
|
|
)
|
|
|
|
# Visualisierung
|
|
colors = get_colors()
|
|
fig = go.Figure(data=[
|
|
go.Bar(name='Schätzung APrV', x=vergleich_k["Kompetenz"], y=vergleich_k["Schätzung APrV"],
|
|
marker_color=colors["primaryLine"]),
|
|
go.Bar(name='NFS-H-Anteil', x=vergleich_k["Kompetenz"], y=vergleich_k["NFS-H-Anteil"],
|
|
marker_color=colors["secondaryLine"])
|
|
])
|
|
fig.update_layout(
|
|
get_standard_layout("Vergleich Kompetenzgewichtung: APrV-Schätzung vs. NFS-H-Lehrplan", "Kompetenzbereich", "Anteil [%]"),
|
|
barmode='group'
|
|
)
|
|
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)
|