Initialer Commit: curriculare Analyse NFS-H
This commit is contained in:
80
.gitignore
vendored
Normal file
80
.gitignore
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Pytest
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# profiling
|
||||||
|
prof/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
Pipfile.lock
|
||||||
|
|
||||||
|
# pyright
|
||||||
|
.pyright/
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# system files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Output files
|
||||||
|
# Ignore only generated or temporary figures in Abbildungen/
|
||||||
|
Abbildungen/*.tmp.*
|
||||||
|
Abbildungen/*_entwurf.*
|
||||||
|
*.html
|
||||||
|
*.png
|
||||||
54
APrV-Kuerzel_zu_Kompetenzbereichen.csv
Normal file
54
APrV-Kuerzel_zu_Kompetenzbereichen.csv
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
Kürzel,Kompetenzbereich
|
||||||
|
1a,fachlich
|
||||||
|
1b,fachlich
|
||||||
|
1c,fachlich
|
||||||
|
1d,fachlich
|
||||||
|
1e,fachlich
|
||||||
|
1f,fachlich
|
||||||
|
2a,fachlich
|
||||||
|
2b,fachlich
|
||||||
|
2c,fachlich
|
||||||
|
2d,methodisch
|
||||||
|
2e,methodisch
|
||||||
|
2f,methodisch
|
||||||
|
2g,methodisch
|
||||||
|
2h,methodisch
|
||||||
|
3a,sozial
|
||||||
|
3b,sozial
|
||||||
|
3c,sozial
|
||||||
|
3d,sozial
|
||||||
|
3e,sozial
|
||||||
|
4a,methodisch
|
||||||
|
4b,methodisch
|
||||||
|
4c,methodisch
|
||||||
|
5a,methodisch
|
||||||
|
5b,methodisch
|
||||||
|
5c,methodisch
|
||||||
|
5d,methodisch
|
||||||
|
5e,methodisch
|
||||||
|
6a,methodisch
|
||||||
|
6b,methodisch
|
||||||
|
6c,methodisch
|
||||||
|
6d,methodisch
|
||||||
|
7a,fachlich
|
||||||
|
7b,fachlich
|
||||||
|
7c,fachlich
|
||||||
|
7d,fachlich
|
||||||
|
7e,fachlich
|
||||||
|
7f,fachlich
|
||||||
|
7g,fachlich
|
||||||
|
7h,fachlich
|
||||||
|
7i,fachlich
|
||||||
|
8a,personal
|
||||||
|
8b,personal
|
||||||
|
8c,personal
|
||||||
|
8d,personal
|
||||||
|
9a,personal
|
||||||
|
9b,personal
|
||||||
|
9c,personal
|
||||||
|
9d,personal
|
||||||
|
9e,personal
|
||||||
|
10a,sozial
|
||||||
|
10b,sozial
|
||||||
|
10c,sozial
|
||||||
|
10d,sozial
|
||||||
|
32
README.md
Normal file
32
README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Learning Management System – Curriculum Analyse
|
||||||
|
|
||||||
|
Dieses Projekt enthält die vollständige Python-gestützte Analyse der curricularen Struktur des digitalen Bildungsraums „NFS-H“. Ziel ist die datenbasierte Validierung der curricularen Kohärenz anhand der Ausbildungs- und Prüfungsverordnung für Notfallsanitäter*innen (NotSan-APrV).
|
||||||
|
|
||||||
|
## Zielsetzung
|
||||||
|
|
||||||
|
Nachgewiesen werden:
|
||||||
|
- curriculare Konsistenz (Korrelation Aufgaben vs. Dauer, r = 0.66)
|
||||||
|
- inhaltliche Validität (Deckung mit APrV-Themenverteilung: 47/27/26)
|
||||||
|
- strukturierte Kompetenzorientierung (fachlich, sozial, personal, methodisch)
|
||||||
|
- Visualisierungen zur quantitativen Analyse
|
||||||
|
|
||||||
|
## Inhalt
|
||||||
|
|
||||||
|
- `lms_statistische-analyse.py`: Hauptskript zur statistischen Auswertung
|
||||||
|
- `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
|
||||||
|
|
||||||
|
## Methoden
|
||||||
|
|
||||||
|
- Automatisierte Zuordnung über Pandas
|
||||||
|
- Visualisierung mit Plotly (Boxplots, Tortendiagramme, Balkendiagramme)
|
||||||
|
- Vergleich mit APrV-Gesetzesvorgaben als Referenzstruktur
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
MIT License – siehe `LICENSE`
|
||||||
|
|
||||||
|
## Hinweis
|
||||||
|
|
||||||
|
Dieses Repository ist Bestandteil der Dissertationsarbeit an der Charité – Universitätsmedizin Berlin.
|
||||||
5
config_lms.py
Normal file
5
config_lms.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
# Visualisierungs- und Darstellungsparameter für Plotly-Diagramme
|
||||||
|
plotly_theme = "dark" # CI-konformes Plotly-Theme: "dark" oder "light"
|
||||||
|
export_fig_visual = False # Steuerung, ob Visualisierungen exportiert werden
|
||||||
|
export_fig_png = False # Separater Schalter für PNG-Export
|
||||||
BIN
lms-verteilung.xlsx
Normal file
BIN
lms-verteilung.xlsx
Normal file
Binary file not shown.
381
lms_statistische-analyse.py
Normal file
381
lms_statistische-analyse.py
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
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)
|
||||||
Reference in New Issue
Block a user