commit 1ec07f2cf2b73df87427df3252bf6cb4838fb3f5 Author: Jochen Hanisch-Johannsen Date: Sun May 11 08:53:37 2025 +0200 Initiale Analyse-Skripte für Korrelation und Netzwerk diff --git a/analyse_korrelation.py b/analyse_korrelation.py new file mode 100644 index 0000000..f16d1ef --- /dev/null +++ b/analyse_korrelation.py @@ -0,0 +1,772 @@ + +import os + +# Terminal leeren +os.system('cls' if os.name == 'nt' else 'clear') + +from datetime import datetime +import bibtexparser +import pandas as pd +import numpy as np + +import subprocess +from slugify import slugify + +# Machine Learning Tools +from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans +from sklearn.metrics import silhouette_score +from sklearn.preprocessing import StandardScaler +from sklearn.decomposition import PCA +from sklearn.manifold import TSNE +from sklearn.metrics import silhouette_score + +# Visualization +import plotly.express as px +import matplotlib.pyplot as plt + +# Debugging and Output +from tabulate import tabulate + +# Name der BibTeX-Datei für Exportzwecke +bib_filename = "Suchergebnisse.bib" + +# Zentrale Steuerung für Export-Flags +export_fig_visual = True + +# Export-Flags für Visualisierungen +export_fig_clusteranalyse = export_fig_visual +export_fig_correlation_suchbegriffe_kategorien = export_fig_visual +export_fig_correlation_fu_kategorien = export_fig_visual +export_fig_correlation_fu_suchbegriffe = export_fig_visual +export_fig_correlation_indizes_kategorien = export_fig_visual +export_fig_correlation_indizes_suchbegriffe = export_fig_visual +export_fig_correlation_fu_indizes = export_fig_visual +export_fig_correlation_fu_fu = export_fig_visual +export_fig_correlation_suchbegriffe_suchbegriffe = export_fig_visual +export_fig_correlation_kategorien_kategorien = export_fig_visual +export_fig_correlation_indizes_indizes = export_fig_visual +export_fig_summary_plot = export_fig_visual + +# Universelle Hilfsfunktion für Export und Titelmanipulation +def prepare_figure_export(fig, name): + # Titel ergänzen + if fig.layout.title and fig.layout.title.text: + if f"| Quelle: {bib_filename.replace('.bib', '')}" not in fig.layout.title.text: + fig.update_layout(title_text=f"{fig.layout.title.text} | Quelle: {bib_filename.replace('.bib', '')}") + # Dateiname generieren + safe_filename = slugify(f"{name}_{bib_filename.replace('.bib', '')}") + return f"{safe_filename}.html" + +# Zentrale Exportfunktion +def export_and_transfer_figure(fig, function_name, export_flag): + # Plot immer anzeigen, unabhängig vom Exportflag + fig.show(config={"responsive": True}) + print(f"🔄 Exportversuch: {function_name} | Aktiv: {export_flag}") + + if export_flag: + export_path = prepare_figure_export(fig, function_name) + try: + fig.write_html(export_path, full_html=True, include_plotlyjs="cdn") + print(f"HTML gespeichert unter: {export_path}") + + remote_path = "jochen-hanisch@sternenflottenakademie.local:/mnt/deep-space-nine/public/plot/promotion/" + result = subprocess.run(["scp", export_path, remote_path], check=True, capture_output=True, text=True) + print("✅ Übertragung erfolgreich") + # Nach erfolgreichem scp Transfer lokale Datei löschen + os.remove(export_path) + print(f"🗑️ Lokale Datei '{export_path}' wurde gelöscht.") + except Exception as e: + print(f"❌ Fehler: {str(e)}") + +# BibTeX-Datei laden +bib_path = os.path.join("Research", "Charité - Universitätsmedizin Berlin", "Systematische Literaturrecherche", "Bibliothek", bib_filename) +with open(bib_path, encoding='utf-8') as bibtex_file: + bib_database = bibtexparser.load(bibtex_file) + +# Farben definieren +colors = { + "background": "#003366", # Hintergrundfarbe + "text": "#333333", # Textfarbe + "accent": "#663300", # Akzentfarbe + "primaryLine": "#660066", # Bildungswirkfaktor + "secondaryLine": "#cc6600", # Bildungswirkindikator + "depthArea": "#006666", # Kompetenzmessunsicherheit + "brightArea": "#66CCCC", # Kompetenzentwicklungsunsicherheit + "positiveHighlight": "#336600", # Positive Hervorhebung + "negativeHighlight": "#990000", # Negative Hervorhebung + "white": "#ffffff" # Weiß +} + +# Aktuelles Datum +current_date = datetime.now().strftime("%Y-%m-%d") + +# Suchbegriffe +tags_to_search = [ + '#0:Zeitschriftenartikel:digital:learning', + '#0:Buch:digital:learning', + '#0:Buchteil:digital:learning', + '#0:Konferenz-Paper:digital:learning', + '#1:Zeitschriftenartikel:learning:management:system', + '#1:Buch:learning:management:system', + '#1:Buchteil:learning:management:system', + '#1:Konferenz-Paper:learning:management:system', + '#2:Zeitschriftenartikel:online:Lernplattform', + '#2:Buch:online:Lernplattform', + '#2:Buchteil:online:Lernplattform', + '#2:Konferenz-Paper:online:Lernplattform', + '#3:Zeitschriftenartikel:online:Lernumgebung', + '#3:Buch:online:Lernumgebung', + '#3:Buchteil:online:Lernumgebung', + '#3:Konferenz-Paper:online:Lernumgebung', + '#4:Zeitschriftenartikel:MOOC', + '#4:Buch:MOOC', + '#4:Buchteil:MOOC', + '#4:Konferenz-Paper:MOOC', + '#5:Zeitschriftenartikel:e-learning', + '#5:Buch:e-learning', + '#5:Buchteil:e-learning', + '#5:Konferenz-Paper:e-learning', + '#6:Zeitschriftenartikel:Bildung:Technologie', + '#6:Buch:Bildung:Technologie', + '#6:Buchteil:Bildung:Technologie', + '#6:Konferenz-Paper:Bildung:Technologie', + '#7:Zeitschriftenartikel:digital:Medien', + '#7:Buch:digital:Medien', + '#7:Buchteil:digital:Medien', + '#7:Konferenz-Paper:digital:Medien', + '#8:Zeitschriftenartikel:blended:learning', + '#8:Buch:blended:learning', + '#8:Buchteil:blended:learning', + '#8:Konferenz-Paper:blended:learning', + '#9:Zeitschriftenartikel:digital:lernen', + '#9:Buch:digital:lernen', + '#9:Buchteil:digital:lernen', + '#9:Konferenz-Paper:digital:lernen', + '#a:Zeitschriftenartikel:online:lernen', + '#a:Buch:online:lernen', + '#a:Buchteil:online:lernen', + '#a:Konferenz-Paper:online:lernen', + '#b:Zeitschriftenartikel:online:learning', + '#b:Buch:online:learning', + '#b:Buchteil:online:learning', + '#b:Konferenz-Paper:online:learning' +] +tags_to_search_processed = [tag.lower().replace('\\#', '#').strip() for tag in tags_to_search] + +# Indizes +index_terms = [ + 'Lernsystemarchitektur', + 'Bildungstheorien', + 'Lehr- und Lerneffektivität', + 'Kollaboratives Lernen', + 'Bewertungsmethoden', + 'Technologieintegration', + 'Datenschutz und IT-Sicherheit', + 'Systemanpassung', + 'Krisenreaktion im Bildungsbereich', + 'Forschungsansätze' +] +index_terms_processed = [term.lower().strip() for term in index_terms] + +# Forschungsunterfragen +research_questions = { + 'promotion:fu1': 'Akzeptanz und Nützlichkeit (FU1)', + 'promotion:fu2a': 'Effekt für Lernende (FU2a)', + 'promotion:fu2b': 'Effekt-Faktoren für Lehrende (FU2b)', + 'promotion:fu3': 'Konzeption und Merkmale (FU3)', + 'promotion:fu4a': 'Bildungswissenschaftliche Mechanismen (FU4a)', + 'promotion:fu4b': 'Technisch-gestalterische Mechanismen (FU4b)', + 'promotion:fu5': 'Möglichkeiten und Grenzen (FU5)', + 'promotion:fu6': 'Beurteilung als Kompetenzerwerbssystem (FU6)', + 'promotion:fu7': 'Inputs und Strategien (FU7)' +} +research_questions_processed = list(research_questions.keys()) + +# Kategorien +categories = { + 'promotion:argumentation': 'Argumentation', + 'promotion:kerngedanke': 'Kerngedanke', + 'promotion:weiterführung': 'Weiterführung', + 'promotion:schlussfolgerung': 'Schlussfolgerung' +} +categories_processed = list(categories.keys()) + +# Daten sammeln +data = [] + +# Verarbeiten der Einträge aus der BibTeX-Datenbank +for entry in bib_database.entries: + if 'keywords' in entry: + # Extrahieren und Verarbeiten der Schlagwörter + entry_keywords = set(map(str.lower, map(str.strip, entry['keywords'].replace('\\#', '#').split(',')))) + row = {} + + # Zuordnung der Schlagwörter zu Tags + row.update({tag: int(tag in entry_keywords) for tag in tags_to_search_processed}) + + # Zuordnung der Schlagwörter zu Index-Begriffen + row.update({index: int(index in entry_keywords) for index in index_terms_processed}) + + # Zuordnung der Schlagwörter zu Forschungsfragen + row.update({rq: int(rq in entry_keywords) for rq in research_questions_processed}) + + # Zuordnung der Schlagwörter zu Kategorien + row.update({cat: int(cat in entry_keywords) for cat in categories_processed}) + + # Titel des Eintrags hinzufügen + row['title'] = entry.get('title', 'No Title') + + # Zeile zur Datenliste hinzufügen + data.append(row) + + +# Daten in ein DataFrame umwandeln +df = pd.DataFrame(data).fillna(0).set_index('title') + +# Debugging: Zeige die ersten Zeilen des DataFrames +print("Erste Zeilen des DataFrames:") +print(df.head()) + +# Funktion zur Berechnung und Visualisierung bivariater Korrelationen mit Interpretation +def interpret_correlation(x_term, y_term, correlation, min_corr, max_corr): + if min_corr == max_corr: + return "Keine Variation" + third = (max_corr - min_corr) / 3 + if correlation > min_corr + 2 * third: + return "Stark verbunden" + elif correlation > min_corr + third: + return "Schwach verbunden" + else: + return "Negativ verbunden" + +from scipy.stats import pearsonr + +def visualize_bivariate_correlation(df, x_terms, y_terms, title, x_label, y_label, export_flag): + """ + Visualisiert bivariate Korrelationen und zeigt Interpretationen sowie Signifikanz im Tooltip an. + + Args: + df (DataFrame): Der DataFrame mit den Daten. + x_terms (list): Liste der Variablen für die x-Achse. + y_terms (list): Liste der Variablen für die y-Achse. + title (str): Titel der Visualisierung. + x_label (str): Beschriftung der x-Achse. + y_label (str): Beschriftung der y-Achse. + """ + correlations = [] + for x_term in x_terms: + for y_term in y_terms: + if x_term != y_term and x_term in df.columns and y_term in df.columns: + # Daten für x und y extrahieren + x_data = df[x_term] + y_data = df[y_term] + + # Robuster Prüf- und Berechnungsblock für Korrelation und Signifikanz (p-Wert) + if len(x_data) > 1 and len(y_data) > 1: + x_data = pd.to_numeric(x_data, errors='coerce') + y_data = pd.to_numeric(y_data, errors='coerce') + valid = x_data.notna() & y_data.notna() + x_valid, y_valid = x_data[valid], y_data[valid] + + if x_valid.nunique() > 1 and y_valid.nunique() > 1: + corr, p_value = pearsonr(x_valid, y_valid) + if pd.notnull(corr): + abs_corr = abs(corr) + significance = 'Signifikant' if p_value < 0.05 else 'Nicht signifikant' + hover_color = colors['brightArea'] if p_value < 0.05 else colors['depthArea'] + correlations.append({ + 'x_term': x_term, + 'y_term': y_term, + 'correlation': corr, + 'abs_correlation': abs_corr, + 'p_value': p_value, + 'significance': significance, + 'hover_color': hover_color, + 'interpretation': ( + f"Die Korrelation zwischen '{x_term}' und '{y_term}' beträgt {corr:.2f}. " + f"p-Wert: {p_value:.3e} ({significance})" + ) + }) + + correlation_df = pd.DataFrame(correlations) + if correlation_df.empty: + print(f"⚠️ Keine exportierbare Visualisierung für: {title} – DataFrame ist leer.") + return + + # Berechnung des min und max Korrelationswerts + min_corr = correlation_df['correlation'].min() + max_corr = correlation_df['correlation'].max() + + # Sicherstellen, dass 0 innerhalb des Bereichs von min_corr und max_corr liegt + if min_corr > 0: + min_corr = 0 + if max_corr < 0: + max_corr = 0 + + # Berechnung des zero_position + zero_position = (0 - min_corr) / (max_corr - min_corr) + + # Dynamische Farbskala, die sicherstellt, dass 0 immer weiß ist + color_scale = [ + [0.0, colors['negativeHighlight']], # Start bei min_corr + [zero_position, colors['white']], # Weiß bei 0 + [1.0, colors['positiveHighlight']] # Ende bei max_corr + ] + + # Tabelle im Terminal ausgeben + print(f"Korrelationen für: {title}") + print(tabulate(correlation_df[['x_term', 'y_term', 'correlation', 'p_value', 'significance']], + headers=['Variable X', 'Variable Y', 'Korrelation', 'p-Wert', 'Signifikanz'], + tablefmt='grid')) + + # Export als CSV-Datei + csv_filename = f"Research/Charité - Universitätsmedizin Berlin/Systematische Literaturrecherche/Tabellen/correlations_{title.replace(' ', '_')}.csv" + correlation_df[['x_term', 'y_term', 'correlation', 'p_value', 'significance']].to_csv(csv_filename, index=False) + print(f"Die Ergebnisse wurden als CSV-Datei gespeichert: {csv_filename}") + + # Visualisierung + fig = px.scatter( + correlation_df, + x='x_term', + y='y_term', + size='abs_correlation', + color='correlation', + hover_data={ + 'correlation': True, + 'p_value': True, + 'significance': True, + 'interpretation': True, # Interpretation im Tooltip anzeigen + }, + title=f'{title} (n={len(correlation_df)}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', + labels={'x_term': x_label, 'y_term': y_label, 'correlation': 'Korrelation'}, + color_continuous_scale=color_scale # Dynamische Farbskala + ) + + # Layout anpassen + fig.update_traces( + marker=dict( + line=dict(width=1) + ), + hovertemplate=( + '%{customdata[0]}
' + 'Korrelation: %{marker.color:.2f}
' + 'p-Wert: %{customdata[1]:.3e}
' + 'Signifikanz: %{customdata[2]}' + ), + customdata=correlation_df[['x_term', 'p_value', 'significance']].to_numpy() + ) + + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + coloraxis_colorbar=dict(title='Korrelationswert'), # Legende + autosize=True, + margin=dict(l=0, r=0, t=40, b=40) + ) + export_and_transfer_figure( + fig, + title.replace(" ", "_"), + export_flag + ) + +# --- Einzelne Visualisierungsfunktionen für jede bivariate Korrelation --- +def visualize_suchbegriffe_vs_kategorien(export_flag): + visualize_bivariate_correlation( + df, tags_to_search_processed, categories_processed, + 'Korrelation zwischen Suchbegriffen und Kategorien', + 'Suchbegriffe', 'Kategorien', + export_flag + ) + +def visualize_forschungsunterfragen_vs_kategorien(export_flag): + visualize_bivariate_correlation( + df, research_questions_processed, categories_processed, + 'Korrelation zwischen Forschungsunterfragen und Kategorien', + 'Forschungsunterfragen', 'Kategorien', + export_flag + ) + +def visualize_forschungsunterfragen_vs_suchbegriffe(export_flag): + visualize_bivariate_correlation( + df, research_questions_processed, tags_to_search_processed, + 'Korrelation zwischen Forschungsunterfragen und Suchbegriffen', + 'Forschungsunterfragen', 'Suchbegriffe', + export_flag + ) + +def visualize_indizes_vs_kategorien(export_flag): + visualize_bivariate_correlation( + df, index_terms_processed, categories_processed, + 'Korrelation zwischen Indizes und Kategorien', + 'Indizes', 'Kategorien', + export_flag + ) + +def visualize_indizes_vs_suchbegriffe(export_flag): + visualize_bivariate_correlation( + df, index_terms_processed, tags_to_search_processed, + 'Korrelation zwischen Indizes und Suchbegriffen', + 'Indizes', 'Suchbegriffe', + export_flag + ) + +def visualize_forschungsunterfragen_vs_indizes(export_flag): + visualize_bivariate_correlation( + df, research_questions_processed, index_terms_processed, + 'Korrelation zwischen Forschungsunterfragen und Indizes', + 'Forschungsunterfragen', 'Indizes', + export_flag + ) + +def visualize_forschungsunterfragen_vs_forschungsunterfragen(export_flag): + visualize_bivariate_correlation( + df, research_questions_processed, research_questions_processed, + 'Korrelation zwischen Forschungsunterfragen', + 'Forschungsunterfragen', 'Forschungsunterfragen', + export_flag + ) + +def visualize_suchbegriffe_vs_suchbegriffe(export_flag): + visualize_bivariate_correlation( + df, tags_to_search_processed, tags_to_search_processed, + 'Korrelation zwischen Suchbegriffen', + 'Suchbegriffe', 'Suchbegriffe', + export_flag + ) + +def visualize_kategorien_vs_kategorien(export_flag): + visualize_bivariate_correlation( + df, categories_processed, categories_processed, + 'Korrelation zwischen Kategorien', + 'Kategorien', 'Kategorien', + export_flag + ) + +def visualize_indizes_vs_indizes(export_flag): + visualize_bivariate_correlation( + df, index_terms_processed, index_terms_processed, + 'Korrelation zwischen Indizes', + 'Indizes', 'Indizes', + export_flag + ) + + +#====================================== + +# Farben für die Cluster +cluster_colors = { + "0": colors['primaryLine'], # Cluster 0 + "1": colors['secondaryLine'], # Cluster 1 + "2": colors['depthArea'], # Cluster 2 + "3": colors['brightArea'] # Cluster 3 +} + +# Vorbereitung: Positionierung entlang deduktiver Dimensionen +df['X_Dimension'] = df[[tag for tag in tags_to_search_processed if tag in df.columns]].sum(axis=1) +df['Y_Dimension'] = df[[cat for cat in categories_processed if cat in df.columns]].sum(axis=1) +df['Z_Dimension'] = df[[rq for rq in research_questions_processed if rq in df.columns]].sum(axis=1) + +# Clusteranalyse mit K-Means basierend auf den deduktiven Dimensionen +features = df[['X_Dimension', 'Y_Dimension', 'Z_Dimension']] +scaler = StandardScaler() +scaled_features = scaler.fit_transform(features) + +# Clusteranalyse mit K-Means basierend auf den deduktiven Dimensionen +# Prüfung auf konstante deduktive Dimensionen +if df[['X_Dimension', 'Y_Dimension', 'Z_Dimension']].nunique().eq(1).all(): + print("⚠️ Alle deduktiven Dimensionen sind konstant. K-Means-Clustering wird übersprungen.") + df['KMeans_Cluster'] = 'Nicht gültig' + silhouette_avg = None +else: + try: + features = df[['X_Dimension', 'Y_Dimension', 'Z_Dimension']] + scaler = StandardScaler() + scaled_features = scaler.fit_transform(features) + + # Prüfen, ob genügend Datenpunkte für das Clustering vorliegen + if len(features) < 2: + raise ValueError("Zu wenige Datenpunkte für die Clusteranalyse. Mindestens zwei Punkte erforderlich.") + + # K-Means ausführen + kmeans = KMeans(n_clusters=4, random_state=42) + df['KMeans_Cluster'] = kmeans.fit_predict(scaled_features) + + # Cluster als Strings umwandeln (kategorisch) + df['KMeans_Cluster'] = df['KMeans_Cluster'].astype(str) + + # Dynamische Punktgröße basierend auf Wertigkeit (Summen der Dimensionen), mit klarer Klammerung und Skalierung + df['Point_Size'] = (df['X_Dimension'] + df['Y_Dimension'] + df['Z_Dimension']) * 100 + + # Statistik der Punktgröße ausgeben + print("Statistik der Punktgrößen:") + print(df['Point_Size'].describe()) + + # Silhouette-Analyse zur Bewertung der Clusterqualität + silhouette_avg = silhouette_score(scaled_features, df['KMeans_Cluster'].astype(int)) + print(f"Silhouette-Score: {silhouette_avg:.4f}") + + # Speichern der Clusterdaten als CSV-Datei + output_path = "Research/Charité - Universitätsmedizin Berlin/Systematische Literaturrecherche/Tabellen/cluster_data.csv" + df.to_csv(output_path, index=False) + print(f"Clusterdaten wurden als CSV gespeichert: {output_path}") + + except ValueError as e: + # Fehler aufgrund unsauberer Daten + print(f"Fehler bei der Clusteranalyse: {e}") + print("Bitte überprüfen Sie die Eingabedaten. Die Clusteranalyse konnte nicht durchgeführt werden.") + # Optionale Handlung, z.B. einen Default-Wert setzen oder den Prozess erneut starten + df['KMeans_Cluster'] = 'Nicht gültig' + silhouette_avg = None + except Exception as e: + # Allgemeiner Fehler + print(f"Ein unerwarteter Fehler ist aufgetreten: {e}") + df['KMeans_Cluster'] = 'Fehler' + silhouette_avg = None + +num_clusters = df['KMeans_Cluster'].nunique() if 'KMeans_Cluster' in df.columns else 0 +plot_title = f"3D-deduktiv-statistische Clusteranalyse (K-Means) (n={len(df)}, Cluster={num_clusters})" +if silhouette_avg is not None: + plot_title += f" | Silhouette-Score: {silhouette_avg:.4f}" + +# Relevante Spalten für die Clusterbeschreibung +relevant_columns = tags_to_search_processed + index_terms_processed + research_questions_processed + list(categories.keys()) + +# Cluster-Beschriftungen basierend auf den Top-Merkmalen mit Umbrüchen +cluster_means = df[relevant_columns + ['KMeans_Cluster']].groupby('KMeans_Cluster').mean() +cluster_labels = {} +for cluster in cluster_means.index: + # Sortiere relevante Spalten nach höchsten Mittelwerten + top_features = cluster_means.loc[cluster].sort_values(ascending=False).head(3) + # Erstelle die Beschriftung mit HTML-Zeilenumbrüchen + label = "
".join([col for col in top_features.index]) # Zeilenumbrüche einfügen + cluster_labels[str(cluster)] = label + +# Statische Cluster-Beschriftungen in den DataFrame einfügen +df['Cluster_Label'] = df['KMeans_Cluster'].map(cluster_labels) + +# Ausgabe der statischen Cluster-Beschriftungen +print("Cluster-Beschriftungen (inhaltlich):") +for cluster, label in cluster_labels.items(): + print(f"Cluster {cluster}: {label.replace('
', ', ')}") # Umbrüche für die Ausgabe ersetzen + +# Plotly 3D-Scatter-Plot mit deduktiven Dimensionen und dynamischer Punktgröße +plot_title += f" | Quelle: {bib_filename.replace('.bib', '')}" +fig_cluster = px.scatter_3d( + df, + x='X_Dimension', # X-Achse: Deduktive Dimension (Suchbegriffe) + y='Y_Dimension', # Y-Achse: Deduktive Dimension (Kategorien) + z='Z_Dimension', # Z-Achse: Deduktive Dimension (Forschungsfragen) + color='Cluster_Label', # Cluster-Beschreibungen mit Umbrüchen + size='Point_Size', # Dynamische Punktgröße basierend auf Wertigkeit + size_max=100, # Maximale Punktgröße anpassen für bessere Sichtbarkeit + color_discrete_sequence=list(cluster_colors.values()), # Farben für Cluster-Beschreibungen + hover_data={ + 'Cluster_Label': True, # Statische Cluster-Beschreibungen mit Umbrüchen + 'X_Dimension': True, # Deduktive Dimension: Suchbegriffe + 'Y_Dimension': True, # Deduktive Dimension: Kategorien + 'Z_Dimension': True, # Deduktive Dimension: Forschungsfragen + 'Point_Size': True # Dynamische Punktgröße + }, + title=plot_title, # Dynamische Überschrift mit Silhouette-Score und Quelle + labels={ + 'X_Dimension': 'Suchbegriffe', + 'Y_Dimension': 'Kategorien', + 'Z_Dimension': 'Forschungsfragen', + 'Point_Size': 'Punktgröße', + 'Cluster_Label': 'Cluster-Beschreibung' + } +) + +# Layout anpassen +fig_cluster.update_layout( + scene=dict( + xaxis=dict( + title='Suchbegriffe', # Titel der X-Achse + showbackground=True, # Hintergrund anzeigen + backgroundcolor=colors['background'], # Hintergrundfarbe + gridcolor=colors['white'], # Gitterlinienfarbe + zerolinecolor=colors['white'], # Null-Linienfarbe + showline=True, # Achsenlinie anzeigen + tickcolor=colors['white'], # Tick-Farbe + titlefont=dict(size=12, color=colors['white']) # Titelstil der Achse + ), + yaxis=dict( + title='Kategorien', # Titel der Y-Achse + showbackground=True, + backgroundcolor=colors['background'], + gridcolor=colors['white'], + zerolinecolor=colors['white'], + showline=True, + tickcolor=colors['white'], + titlefont=dict(size=12, color=colors['white']) + ), + zaxis=dict( + title='Forschungsfragen', # Titel der Z-Achse + showbackground=True, + backgroundcolor=colors['background'], + gridcolor=colors['white'], + zerolinecolor=colors['white'], + showline=True, + tickcolor=colors['white'], + titlefont=dict(size=12, color=colors['white']) + ) + ), + plot_bgcolor=colors['background'], # Plot-Hintergrundfarbe + paper_bgcolor=colors['background'], # Papierhintergrundfarbe + font=dict(color=colors['white']), # Schriftfarbe + showlegend=True, # Legende anzeigen + legend=dict( + title=dict(text="Cluster-Beschreibung", font=dict(size=12, color=colors['white'])), + font=dict(size=10, color=colors['white']), + bgcolor=colors['background'], # Hintergrund der Legende + bordercolor=colors['white'], # Rahmenfarbe der Legende + borderwidth=1 # Rahmenbreite der Legende + ), + template="plotly_white" # Plotly-Template +) + +# Plot anzeigen und ggf. exportieren +export_and_transfer_figure( + fig_cluster, + "clusteranalyse_kmeans_deduktiv", + export_fig_clusteranalyse +) + + +# Berechnung der Korrelationen und Erstellung der Übersicht + +def analyze_correlation_quality(df, x_terms, y_terms): + """ + Berechnet die Signifikanz und Qualität der Korrelationen zwischen zwei Gruppen von Variablen. + + Args: + df (DataFrame): Der DataFrame mit den Daten. + x_terms (list): Liste der Variablen für die x-Achse. + y_terms (list): Liste der Variablen für die y-Achse. + + Returns: + dict: Eine strukturierte Zusammenfassung der Korrelationsergebnisse. + """ + correlation_data = [] + + for x_term in x_terms: + for y_term in y_terms: + if x_term != y_term and x_term in df.columns and y_term in df.columns: + x_data = df[x_term] + y_data = df[y_term] + + if len(x_data) > 1 and len(y_data) > 1: + x_data = pd.to_numeric(x_data, errors='coerce') + y_data = pd.to_numeric(y_data, errors='coerce') + valid = x_data.notna() & y_data.notna() + x_valid, y_valid = x_data[valid], y_data[valid] + + if x_valid.nunique() > 1 and y_valid.nunique() > 1: + corr, p_value = pearsonr(x_valid, y_valid) + if pd.notnull(corr): + correlation_data.append({ + "x_term": x_term, + "y_term": y_term, + "correlation": corr, + "p_value": p_value + }) + + correlation_df = pd.DataFrame(correlation_data) + + if correlation_df.empty: + print("Keine signifikanten Korrelationen gefunden.") + return {} + + # Berechnung von Metriken + significant_count = correlation_df[correlation_df["p_value"] < 0.05].shape[0] + highly_significant_count = correlation_df[correlation_df["p_value"] < 0.01].shape[0] + very_highly_significant_count = correlation_df[correlation_df["p_value"] < 0.001].shape[0] + total_count = correlation_df.shape[0] + + correlation_quality_results = { + "total_count": total_count, + "significant_count": significant_count, + "highly_significant_count": highly_significant_count, + "very_highly_significant_count": very_highly_significant_count, + "significant_ratio": significant_count / total_count if total_count > 0 else 0, + "highly_significant_ratio": highly_significant_count / total_count if total_count > 0 else 0, + "very_highly_significant_ratio": very_highly_significant_count / total_count if total_count > 0 else 0, + "avg_correlation": correlation_df["correlation"].mean(), + "non_significant_ratio": (total_count - significant_count) / total_count if total_count > 0 else 0, + } + + return correlation_quality_results + +# Berechnung für verschiedene Korrelationstypen +correlation_quality_results = { + "Forschungsunterfragen & Kategorien": analyze_correlation_quality(df, research_questions_processed, categories_processed), + "Forschungsunterfragen & Suchbegriffe": analyze_correlation_quality(df, research_questions_processed, tags_to_search_processed), + "Forschungsunterfragen & Indizes": analyze_correlation_quality(df, research_questions_processed, index_terms_processed), + "Indizes & Kategorien": analyze_correlation_quality(df, index_terms_processed, categories_processed), + "Indizes & Suchbegriffe": analyze_correlation_quality(df, index_terms_processed, tags_to_search_processed), + "Suchbegriffe & Kategorien": analyze_correlation_quality(df, tags_to_search_processed, categories_processed), + "Indizes & Indizes": analyze_correlation_quality(df, index_terms_processed, index_terms_processed), + "Suchbegriffe & Suchbegriffe": analyze_correlation_quality(df, tags_to_search_processed, tags_to_search_processed), + "Kategorien & Kategorien": analyze_correlation_quality(df, categories_processed, categories_processed), +} + +# Entferne leere Einträge aus dem Dictionary +correlation_quality_results = {k: v for k, v in correlation_quality_results.items() if v} + +summary_df = pd.DataFrame({ + "Korrelationstyp": correlation_quality_results.keys(), + "Gesamtanzahl": [res["total_count"] for res in correlation_quality_results.values()], + "Signifikante Korrelationen (%)": [res["significant_ratio"] * 100 for res in correlation_quality_results.values()], + "Hoch signifikante Korrelationen (%)": [res["highly_significant_ratio"] * 100 for res in correlation_quality_results.values()], + "Sehr hoch signifikante Korrelationen (%)": [res["very_highly_significant_ratio"] * 100 for res in correlation_quality_results.values()], + "Durchschnittliche Korrelation": [res["avg_correlation"] for res in correlation_quality_results.values()], + "Nicht signifikante Korrelationen (%)": [res["non_significant_ratio"] * 100 for res in correlation_quality_results.values()], +}) + +# Plotly-Version für interaktive Darstellung +def plot_average_correlation_plotly(summary_df): + fig = px.bar( + summary_df, + x="Korrelationstyp", + y="Durchschnittliche Korrelation", + title=f"Durchschnittliche Korrelationen pro Korrelationstyp (n={len(summary_df)}, Stand: {current_date}) | Quelle: {bib_filename.replace('.bib', '')}", + labels={"Korrelationstyp": "Korrelationstyp", "Durchschnittliche Korrelation": "Durchschnittliche Korrelation"}, + color="Durchschnittliche Korrelation", + color_continuous_scale=[ + [0.0, colors['negativeHighlight']], + [0.5, colors['white']], + [1.0, colors['positiveHighlight']] + ] + ) + + fig.update_layout( + xaxis_tickangle=-45, + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + coloraxis_colorbar=dict(title="Korrelationswert"), + autosize=True, + margin=dict(l=0, r=0, t=40, b=40) + ) + export_and_transfer_figure( + fig, + "summary_plot", + export_fig_summary_plot + ) + + +#============================ +# Aufruf Alle möglichen bivariaten Korrelationen visualisieren + +visualize_suchbegriffe_vs_kategorien(export_flag=export_fig_correlation_suchbegriffe_kategorien) +visualize_forschungsunterfragen_vs_kategorien(export_flag=export_fig_correlation_fu_kategorien) +visualize_forschungsunterfragen_vs_suchbegriffe(export_flag=export_fig_correlation_fu_suchbegriffe) +visualize_indizes_vs_kategorien(export_flag=export_fig_correlation_indizes_kategorien) +visualize_indizes_vs_suchbegriffe(export_flag=export_fig_correlation_indizes_suchbegriffe) +visualize_forschungsunterfragen_vs_indizes(export_flag=export_fig_correlation_fu_indizes) +visualize_forschungsunterfragen_vs_forschungsunterfragen(export_flag=export_fig_correlation_fu_fu) +visualize_suchbegriffe_vs_suchbegriffe(export_flag=export_fig_correlation_suchbegriffe_suchbegriffe) +visualize_kategorien_vs_kategorien(export_flag=export_fig_correlation_kategorien_kategorien) +visualize_indizes_vs_indizes(export_flag=export_fig_correlation_indizes_indizes) +plot_average_correlation_plotly(summary_df) \ No newline at end of file diff --git a/analyse_netzwerk.py b/analyse_netzwerk.py new file mode 100644 index 0000000..2237eeb --- /dev/null +++ b/analyse_netzwerk.py @@ -0,0 +1,1156 @@ + +import os + +# Clear the terminal +os.system('cls' if os.name == 'nt' else 'clear') + +import bibtexparser +import pandas as pd +import numpy as np +import networkx as nx +import matplotlib.pyplot as plt +from datetime import datetime +from collections import defaultdict, Counter +from itertools import product +from wordcloud import WordCloud +from tabulate import tabulate +import plotly.express as px +import plotly.graph_objects as go +import random +import math +import re +import subprocess + +bib_filename = "Suchergebnisse.bib" +export_fig_visual = True + +# Optional: slugify-Funktion +def slugify(value): + return re.sub(r'[^a-zA-Z0-9_-]', '', value.replace(' ', '_').lower()) + +# Zentrale Hilfsfunktion für Figure-Export und Titelergänzung +def prepare_figure_export(fig, name): + if fig.layout.title and fig.layout.title.text: + if f"| Quelle: {bib_filename.replace('.bib', '')}" not in fig.layout.title.text: + fig.update_layout(title_text=f"{fig.layout.title.text} | Quelle: {bib_filename.replace('.bib', '')}") + safe_filename = slugify(f"{name}_{bib_filename.replace('.bib', '')}") + return f"{safe_filename}.html" + +# Zentraler Schalter für Export-Flags + +# Export-Flags für Visualisierungen (abhängig vom zentralen Schalter) +export_fig_visualize_network = export_fig_visual +export_fig_visualize_tags = export_fig_visual +export_fig_visualize_index = export_fig_visual +export_fig_visualize_research_questions = export_fig_visual +export_fig_visualize_categories = export_fig_visual +export_fig_visualize_time_series = export_fig_visual +export_fig_visualize_top_authors = export_fig_visual +export_fig_visualize_top_publications = export_fig_visual +export_fig_create_path_diagram = export_fig_visual +export_fig_create_sankey_diagram = export_fig_visual +export_fig_visualize_sources_status = export_fig_visual +export_fig_create_wordcloud_from_titles = export_fig_visual + + +# Zentrale Exportfunktion für Visualisierungen +def export_figure(fig, name, flag, bib_filename=None): + if flag: + export_path = prepare_figure_export(fig, name) + fig.write_html(export_path, full_html=True, include_plotlyjs="cdn") + remote_path = "jochen-hanisch@sternenflottenakademie.local:/mnt/deep-space-nine/public/plot/promotion/" + try: + subprocess.run(["scp", export_path, remote_path], check=True, capture_output=True, text=True) + print(f"✅ Datei '{export_path}' erfolgreich übertragen.") + os.remove(export_path) + print(f"🗑️ Lokale Datei '{export_path}' wurde gelöscht.") + except subprocess.CalledProcessError as e: + print("❌ Fehler beim Übertragen:") + print(e.stderr) + +# Farben definieren +colors = { + "background": "#003366", # Hintergrundfarbe + "text": "#333333", # Textfarbe + "accent": "#663300", # Akzentfarbe + "primaryLine": "#660066", # Bildungswirkfaktor + "secondaryLine": "#cc6600", # Bildungswirkindikator + "depthArea": "#006666", # Kompetenzmessunsicherheit + "brightArea": "#66CCCC", # Kompetenzentwicklungsunsicherheit + "positiveHighlight": "#336600", # Positive Hervorhebung + "negativeHighlight": "#990000", # Negative Hervorhebung + "white": "#ffffff" # Weiß +} + +# Liste der Farben, die für die Wörter verwendet werden sollen +word_colors = [ + colors["white"], + colors["brightArea"], + colors["positiveHighlight"], + colors["negativeHighlight"] +] + +# Aktuelles Datum +current_date = datetime.now().strftime("%Y-%m-%d") + +# BibTeX-Datei Definitionen +bib_path = os.path.join("Research", "Charité - Universitätsmedizin Berlin", "Systematische Literaturrecherche", "Bibliothek", bib_filename) + +# BibTeX-Datei laden +with open(bib_path, encoding='utf-8') as bibtex_file: + bib_database = bibtexparser.load(bibtex_file) + +# Stopplisten laden +with open('de_complete.txt', 'r', encoding='utf-8') as file: + stop_words_de = set(file.read().split()) + +with open('en_complete.txt', 'r', encoding='utf-8') as file: + stop_words_en = set(file.read().split()) + +# Kombinierte Stoppliste +stop_words = stop_words_de.union(stop_words_en) + +# Funktion zur Berechnung der Stichprobengröße +def calculate_sample_size(N, Z=1.96, p=0.5, e=0.05): + n_0 = (Z**2 * p * (1 - p)) / (e**2) + n = n_0 / (1 + ((n_0 - 1) / N)) + return math.ceil(n) + +# Visualisierung 1: Netzwerkanalyse +# Visualisierung 1: Netzwerkanalyse +def visualize_network(bib_database): + search_terms = { + '0': 'digital:learning', + '1': 'learning:management:system', + '2': 'online:Lernplattform', + '3': 'online:Lernumgebung', + '4': 'MOOC', + '5': 'e-learning', + '6': 'Bildung:Technologie', + '7': 'digital:Medien', + '8': 'blended:learning', + '9': 'digital:lernen', + 'a': 'online:lernen', + 'b': 'online:learning' + } + + numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b'] + types = [ + 'Zeitschriftenartikel', + 'Buch', + 'Buchteil', + 'Bericht', + 'Konferenz-Paper' + ] + tags_to_search = set() + for number, type_ in product(numbers, types): + search_term = search_terms[number] + tag = f'#{number}:{type_}:{search_term}' + tags_to_search.add(tag.lower()) + + tag_counts = defaultdict(int) + for entry in bib_database.entries: + if 'keywords' in entry: + entry_keywords = list(map(str.lower, map(str.strip, entry['keywords'].replace('\\#', '#').split(',')))) + for keyword in entry_keywords: + for tag in tags_to_search: + if tag in keyword: + tag_counts[tag] += 1 + + fundzahlen = defaultdict(int) + for tag, count in tag_counts.items(): + search_term = tag.split(':')[-1] + for key, value in search_terms.items(): + if search_term == value: + fundzahlen[value] += count + + search_terms_network = { + "Primäre Begriffe": { + "learning:management:system": [ + "e-learning", + "bildung:technologie", + "online:lernplattform", + "online:lernumgebung", + "digital:learning", + "digitales:lernen" + ] + }, + "Sekundäre Begriffe": { + "e-learning": [ + "mooc", + "online:lernplattform" + ], + "bildung:technologie": [ + "digital:learning", + "digitales:lernen", + "blended:learning" + ], + "digital:learning": [ + "digitale:medien", + "online:learning" + ], + "digitales:lernen": [ + "digitale:medien", + "online:lernen" + ], + "blended:learning": ["mooc"] + }, + "Tertiäre Begriffe": { + "online:learning": [], + "online:lernen": [] + } + } + + G = nx.Graph() + + hierarchy_colors = { + "Primäre Begriffe": colors['primaryLine'], + "Sekundäre Begriffe": colors['secondaryLine'], + "Tertiäre Begriffe": colors['brightArea'] + } + + def add_terms_to_graph(level, terms): + for primary_term, related_terms in terms.items(): + if primary_term not in G: + G.add_node(primary_term, color=hierarchy_colors[level], size=fundzahlen.get(primary_term, 10)) + else: + if level == "Tertiäre Begriffe": + G.nodes[primary_term]['color'] = hierarchy_colors[level] + for related_term in related_terms: + if related_term not in G: + G.add_node(related_term, color=hierarchy_colors[level], size=fundzahlen.get(related_term, 10)) + else: + if level == "Tertiäre Begriffe": + G.nodes[related_term]['color'] = hierarchy_colors[level] + G.add_edge(primary_term, related_term) + + for level, terms in search_terms_network.items(): + add_terms_to_graph(level, terms) + + np.random.seed(42) + pos = nx.spring_layout(G) + + x_scale_min, x_scale_max = 0, 10 + y_scale_min, y_scale_max = 0, 10 + + min_x = min(pos[node][0] for node in pos) + max_x = max(pos[node][0] for node in pos) + min_y = min(pos[node][1] for node in pos) + max_y = max(pos[node][1] for node in pos) + + scale_x_range = x_scale_max - x_scale_min + scale_y_range = y_scale_max - y_scale_min + + for node in pos: + x, y = pos[node] + norm_x = scale_x_range * (x - min_x) / (max_x - min_x) + x_scale_min + norm_y = scale_y_range * (y - min_y) / (max_y - min_y) + y_scale_min + pos[node] = (norm_x, norm_y) + + for node in pos: + x, y = pos[node] + x = max(min(x, x_scale_max), x_scale_min) + y = max(min(y, y_scale_max), y_scale_min) + pos[node] = (x, y) + + edge_x = [] + edge_y = [] + for edge in G.edges(): + x0, y0 = pos[edge[0]] + x1, y1 = pos[edge[1]] + edge_x.append(x0) + edge_x.append(x1) + edge_x.append(None) + edge_y.append(y0) + edge_y.append(y1) + edge_y.append(None) + + edge_trace = go.Scatter( + x=edge_x, y=edge_y, + line=dict(width=0.5, color=colors['white']), + hoverinfo='none', + mode='lines') + + # Knoten in drei Traces aufteilen: Primär, Sekundär, Tertiär + primary_nodes = [] + secondary_nodes = [] + tertiary_nodes = [] + + for node in G.nodes(): + color = G.nodes[node]['color'] + size = math.log(G.nodes[node].get('size', 10) + 1) * 10 + x, y = pos[node] + hovertext = f"{node}
Anzahl Funde: {fundzahlen.get(node, 0)}" + node_data = dict(x=x, y=y, text=node, size=size, hovertext=hovertext) + if color == colors['primaryLine']: + primary_nodes.append(node_data) + elif color == colors['secondaryLine']: + secondary_nodes.append(node_data) + elif color == colors['brightArea']: + tertiary_nodes.append(node_data) + + def create_node_trace(nodes, name, color): + return go.Scatter( + x=[n['x'] for n in nodes], + y=[n['y'] for n in nodes], + mode='markers+text', + text=[n['text'] for n in nodes], + hovertext=[n['hovertext'] for n in nodes], + hoverinfo='text', + marker=dict( + size=[n['size'] for n in nodes], + color=color, + line_width=2 + ), + textposition="top center", + textfont=dict(size=12), + name=name + ) + + primary_trace = create_node_trace(primary_nodes, "Primäre Begriffe", colors['primaryLine']) + secondary_trace = create_node_trace(secondary_nodes, "Sekundäre Begriffe", colors['secondaryLine']) + tertiary_trace = create_node_trace(tertiary_nodes, "Tertiäre Begriffe", colors['brightArea']) + + fig = go.Figure(data=[edge_trace, primary_trace, secondary_trace, tertiary_trace], + layout=go.Layout( + title=f'Suchbegriff-Netzwerk nach Relevanz und Semantik (n={sum(fundzahlen.values())}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', + titlefont_size=16, + showlegend=True, + legend=dict( + bgcolor=colors['background'], + bordercolor=colors['white'], + borderwidth=1, + font=dict(color=colors['white']), + itemsizing='constant' + ), + hovermode='closest', + margin=dict(b=20, l=5, r=5, t=40), + xaxis=dict( + range=[x_scale_min, x_scale_max + 1], + showgrid=True, + zeroline=True, + tickmode='linear', + tick0=x_scale_min, + dtick=(x_scale_max - x_scale_min) / 4, + title='Technologische Dimension' + ), + yaxis=dict( + range=[y_scale_min, y_scale_max + 1], + showgrid=True, + zeroline=True, + tickmode='linear', + tick0=y_scale_min, + dtick=(y_scale_max - y_scale_min) / 4, + title='Pädagogische Dimension' + ), + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']) + )) + + fig.show() + export_figure(fig, "visualize_network", export_fig_visualize_network, bib_filename) + + # Einfache Pfadanalyse nach dem Anzeigen der Figur + if 'e-learning' in G and 'online:lernen' in G: + try: + pfad = nx.shortest_path(G, source='e-learning', target='online:lernen') + print(f"Kürzester Pfad von 'e-learning' zu 'online:lernen': {pfad}") + except nx.NetworkXNoPath: + print("Kein Pfad von 'e-learning' zu 'online:lernen' gefunden.") + + # Visualisierung 2: Häufigkeit spezifischer Tags +def visualize_tags(bib_database): + # Definierte Suchbegriffe + search_terms = { + '0': 'digital:learning', + '1': 'learning:management:system', + '2': 'online:Lernplattform', + '3': 'online:Lernumgebung', + '4': 'MOOC', + '5': 'e-learning', + '6': 'Bildung:Technologie', + '7': 'digital:Medien', + '8': 'blended:learning', + '9': 'digital:lernen', + 'a': 'online:lernen', + 'b': 'online:learning' + } + + # Kombinierte Tags erzeugen + numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b'] + types = [ + 'Zeitschriftenartikel', + 'Buch', + 'Buchteil', + 'Bericht', + 'Konferenz-Paper' + ] + tags_to_search = set( + f"#{number}:{type_}:{search_terms[number]}" + for number, type_ in product(numbers, types) + ) + + # Tag-Zählungen initialisieren + tag_counts = defaultdict(int) + if not bib_database or not bib_database.entries: + print("Fehler: Keine Einträge in der Datenbank gefunden.") + return + + for entry in bib_database.entries: + if 'keywords' in entry: + entry_keywords = map( + str.lower, + map(str.strip, entry['keywords'].replace('\\#', '#').split(',')) + ) + for keyword in entry_keywords: + for tag in tags_to_search: + if tag in keyword: + tag_counts[tag] += 1 + + # Daten für Visualisierung aufbereiten + data = [ + {'Tag': tag, 'Count': count, 'Type': tag.split(':')[1].lower()} + for tag, count in tag_counts.items() + if count > 0 + ] + + if not data: + print("Warnung: Keine Tags gefunden, die den Suchkriterien entsprechen.") + return + + # Farbzuordnung + color_map = { + 'zeitschriftenartikel': colors['primaryLine'], + 'konferenz-paper': colors['secondaryLine'], + 'buch': colors['depthArea'], + 'buchteil': colors['brightArea'], + 'bericht': colors['accent'] + } + + # Visualisierung erstellen + total_count = sum(tag_counts.values()) + fig = px.bar( + data, + x='Tag', + y='Count', + title=f'Häufigkeit der Suchbegriffe in der Literaturanalyse (n={total_count}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', + labels={'Tag': 'Tag', 'Count': 'Anzahl der Vorkommen'}, + color='Type', + color_discrete_map=color_map, + text_auto=True + ) + + # Layout anpassen + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + margin=dict(l=0, r=0, t=40, b=40), + autosize=True + ) + + fig.update_traces( + marker_line_color=colors['white'], + marker_line_width=1.5 + ) + + fig.show(config={"responsive": True}) + export_figure(fig, "visualize_tags", export_fig_visualize_tags, bib_filename) + + # Visualisierung 3: Häufigkeit Index +def visualize_index(bib_database): + index_terms = [ + 'Lernsystemarchitektur', + 'Bildungstheorien', + 'Lehr- und Lerneffektivität', + 'Kollaboratives Lernen', + 'Bewertungsmethoden', + 'Technologieintegration', + 'Datenschutz und IT-Sicherheit', + 'Systemanpassung', + 'Krisenreaktion im Bildungsbereich', + 'Forschungsansätze' + ] + + index_counts = defaultdict(int) + for entry in bib_database.entries: + if 'keywords' in entry: + entry_keywords = list(map(str.lower, map(str.strip, entry['keywords'].replace('\\#', '#').split(',')))) + for index_term in index_terms: + if index_term.lower() in entry_keywords: + index_counts[index_term] += 1 + + index_data = [{'Index': index, 'Count': count} for index, count in index_counts.items()] + index_data = sorted(index_data, key=lambda x: x['Count'], reverse=True) + + total_count = sum(index_counts.values()) + print(f"Häufigkeit Indizes (Gesamtanzahl: {total_count}):") + print(tabulate(index_data, headers="keys", tablefmt="grid")) + + fig = px.bar(index_data, x='Index', y='Count', title=f'Relevanzschlüssel nach Indexkategorien (n={total_count}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', labels={'Index': 'Index', 'Count': 'Anzahl der Vorkommen'}, text_auto=True) + + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + margin=dict(l=0, r=0, t=40, b=40), + autosize=True + ) + + fig.update_traces(marker_color=colors['primaryLine'], marker_line_color=colors['white'], marker_line_width=1.5) + + fig.show(config={"responsive": True}) + export_figure(fig, "visualize_index", export_fig_visualize_index, bib_filename) + + # Visualisierung 4: Häufigkeit Forschungsunterfragen +def visualize_research_questions(bib_database): + research_questions = { + 'promotion:fu1': 'Akzeptanz und Nützlichkeit (FU1)', + 'promotion:fu2a': 'Effekt für Lernende (FU2a)', + 'promotion:fu2b': 'Effekt-Faktoren für Lehrende (FU2b)', + 'promotion:fu3': 'Konzeption und Merkmale (FU3)', + 'promotion:fu4a': 'Bildungswissenschaftliche Mechanismen (FU4a)', + 'promotion:fu4b': 'Technisch-gestalterische Mechanismen (FU4b)', + 'promotion:fu5': 'Möglichkeiten und Grenzen (FU5)', + 'promotion:fu6': 'Beurteilung als Kompetenzerwerbssystem (FU6)', + 'promotion:fu7': 'Inputs und Strategien (FU7)' + } + + rq_counts = defaultdict(int) + for entry in bib_database.entries: + if 'keywords' in entry: + entry_keywords = list(map(str.lower, map(str.strip, entry['keywords'].replace('\\#', '#').split(',')))) + for keyword in entry_keywords: + if keyword in research_questions: + rq_counts[keyword] += 1 + + rq_data = [{'Research_Question': research_questions[keyword], 'Count': count} for keyword, count in rq_counts.items()] + rq_data = sorted(rq_data, key=lambda x: x['Count'], reverse=True) + + rq_data_df = pd.DataFrame(rq_data) + + total_count = rq_data_df['Count'].sum() + print(f"Häufigkeit Forschungsunterfragen (Gesamtanzahl: {total_count}):") + print(tabulate(rq_data, headers="keys", tablefmt="grid")) + + fig = px.bar(rq_data_df, x='Research_Question', y='Count', title=f'Zuordnung der Literatur zu Forschungsunterfragen (n={total_count}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', labels={'Research_Question': 'Forschungsunterfrage', 'Count': 'Anzahl der Vorkommen'}, text_auto=True) + + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + margin=dict(l=0, r=0, t=40, b=40), + autosize=True + ) + + fig.update_traces(marker_color=colors['primaryLine'], marker_line_color=colors['white'], marker_line_width=1.5) + + fig.show(config={"responsive": True}) + export_figure(fig, "visualize_research_questions", export_fig_visualize_research_questions, bib_filename) + + # Visualisierung 5: Häufigkeit spezifischer Kategorien +def visualize_categories(bib_database): + categories = { + 'promotion:argumentation': 'Argumentation', + 'promotion:kerngedanke': 'Kerngedanke', + 'promotion:weiterführung': 'Weiterführung', + 'promotion:schlussfolgerung': 'Schlussfolgerung' + } + + cat_counts = defaultdict(int) + for entry in bib_database.entries: + if 'keywords' in entry: + entry_keywords = list(map(str.lower, map(str.strip, entry['keywords'].replace('\\#', '#').split(',')))) + for keyword in entry_keywords: + if keyword in categories: + cat_counts[keyword] += 1 + + cat_data = [{'Category': categories[keyword], 'Count': count} for keyword, count in cat_counts.items()] + cat_data = sorted(cat_data, key=lambda x: x['Count'], reverse=True) + + cat_data_df = pd.DataFrame(cat_data) + + total_count = cat_data_df['Count'].sum() + print(f"Häufigkeit Kategorien (Gesamtanzahl: {total_count}):") + print(tabulate(cat_data, headers="keys", tablefmt="grid")) + + fig = px.bar(cat_data_df, x='Category', y='Count', title=f'Textsortenzuordnung der analysierten Quellen (n={total_count}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', labels={'Category': 'Kategorie', 'Count': 'Anzahl der Vorkommen'}, text_auto=True) + + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + margin=dict(l=0, r=0, t=40, b=40), + autosize=True + ) + + fig.update_traces(marker_color=colors['primaryLine'], marker_line_color=colors['white'], marker_line_width=1.5) + + fig.show(config={"responsive": True}) + export_figure(fig, "visualize_categories", export_fig_visualize_categories, bib_filename) + + # Zeitreihenanalyse der Veröffentlichungen +def visualize_time_series(bib_database): + publication_years = [] + + for entry in bib_database.entries: + if 'year' in entry: + year_str = entry['year'].strip() + try: + # Extrahiere die erste gültige Zahl (z. B. 2017 aus '2017/2018') + year_match = re.search(r'\b\d{4}\b', year_str) + if year_match: + year = int(year_match.group()) + publication_years.append(year) + else: + raise ValueError(f"Kein gültiges Jahr gefunden: {year_str}") + except ValueError as e: + print(f"Warnung: Ungültiger Jahreswert in Eintrag übersprungen: {year_str}") + + if publication_years: + year_counts = Counter(publication_years) + df = pd.DataFrame(year_counts.items(), columns=['Year', 'Count']).sort_values('Year') + + fig = px.line( + df, + x='Year', + y='Count', + title=f'Jährliche Veröffentlichungen in der Literaturanalyse (n={sum(year_counts.values())}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', + labels={'Year': 'Jahr', 'Count': 'Anzahl der Veröffentlichungen'} + ) + + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + xaxis=dict( + tickmode='linear', + dtick=2, + tick0=min(publication_years) + ), + margin=dict(l=0, r=0, t=40, b=40), + autosize=True + ) + + fig.update_traces(line=dict(color=colors['secondaryLine'], width=3)) + fig.show(config={"responsive": True}) + export_figure(fig, "visualize_time_series", export_fig_visualize_time_series, bib_filename) + else: + print("Keine gültigen Veröffentlichungsjahre gefunden.") + + # Top Autoren nach Anzahl der Werke +def visualize_top_authors(bib_database): + top_n = 25 # Anzahl der Top-Autoren, die angezeigt werden sollen + author_counts = defaultdict(int) + for entry in bib_database.entries: + if 'author' in entry: + authors = [author.strip() for author in entry['author'].split(' and ')] + for author in authors: + author_counts[author] += 1 + + top_authors = Counter(author_counts).most_common(top_n) + if top_authors: + df = pd.DataFrame(top_authors, columns=['Author', 'Count']) + + fig = px.bar(df, x='Author', y='Count', title=f'Meistgenannte Autor:innen in der Literaturanalyse (Top {top_n}, n={sum(author_counts.values())}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', labels={'Author': 'Autor', 'Count': 'Anzahl der Werke'}, text_auto=True) + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + margin=dict(l=0, r=0, t=40, b=40), + autosize=True + ) + fig.update_traces(marker_color=colors['primaryLine'], marker_line_color=colors['white'], marker_line_width=1.5) + + fig.show(config={"responsive": True}) + export_figure(fig, "visualize_top_authors", export_fig_visualize_top_authors, bib_filename) + else: + print("Keine Autoren gefunden.") + + + # Top Titel nach Anzahl der Werke +def normalize_title(title): + # Entfernen von Sonderzeichen und Standardisierung auf Kleinbuchstaben + title = title.lower().translate(str.maketrans('', '', ",.!?\"'()[]{}:;")) + # Zusammenführen ähnlicher Titel, die sich nur in geringfügigen Details unterscheiden + title = " ".join(title.split()) + # Entfernen häufiger Füllwörter oder Standardphrasen, die die Unterscheidung nicht unterstützen + common_phrases = ['eine studie', 'untersuchung der', 'analyse von'] + for phrase in common_phrases: + title = title.replace(phrase, '') + return title.strip() + +def visualize_top_publications(bib_database): + top_n = 25 # Anzahl der Top-Publikationen, die angezeigt werden sollen + publication_counts = defaultdict(int) + + for entry in bib_database.entries: + if 'title' in entry: + title = normalize_title(entry['title']) + publication_counts[title] += 1 + + top_publications = sorted(publication_counts.items(), key=lambda x: x[1], reverse=True)[:top_n] + publication_data = [{'Title': title[:50] + '...' if len(title) > 50 else title, 'Count': count} for title, count in top_publications] + + df = pd.DataFrame(publication_data) + + fig = px.bar(df, x='Title', y='Count', title=f'Häufig zitierte Publikationen in der Analyse (Top {top_n}, n={sum(publication_counts.values())}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', labels={'Title': 'Titel', 'Count': 'Anzahl der Nennungen'}) + + fig.update_layout( + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + xaxis_tickangle=-45, + margin=dict(l=0, r=0, t=40, b=40), + autosize=True + ) + + fig.update_traces(marker_color=colors['primaryLine'], marker_line_color=colors['white'], marker_line_width=1.5) + + fig.show(config={"responsive": True}) + export_figure(fig, "visualize_top_publications", export_fig_visualize_top_publications, bib_filename) + + + +########## + + +# Daten vorbereiten +def prepare_path_data(bib_database): + research_questions = { + 'promotion:fu1': 'Akzeptanz und Nützlichkeit (FU1)', + 'promotion:fu2a': 'Effekt für Lernende (FU2a)', + 'promotion:fu2b': 'Effekt-Faktoren für Lehrende (FU2b)', + 'promotion:fu3': 'Konzeption und Merkmale (FU3)', + 'promotion:fu4a': 'Bildungswissenschaftliche Mechanismen (FU4a)', + 'promotion:fu4b': 'Technisch-gestalterische Mechanismen (FU4b)', + 'promotion:fu5': 'Möglichkeiten und Grenzen (FU5)', + 'promotion:fu6': 'Beurteilung als Kompetenzerwerbssystem (FU6)', + 'promotion:fu7': 'Inputs und Strategien (FU7)' + } + + categories = { + 'promotion:argumentation': 'Argumentation', + 'promotion:kerngedanke': 'Kerngedanke', + 'promotion:weiterführung': 'Weiterführung', + 'promotion:schlussfolgerung': 'Schlussfolgerung' + } + + index_terms = [ + 'Lernsystemarchitektur', + 'Bildungstheorien', + 'Lehr- und Lerneffektivität', + 'Kollaboratives Lernen', + 'Bewertungsmethoden', + 'Technologieintegration', + 'Datenschutz und IT-Sicherheit', + 'Systemanpassung', + 'Krisenreaktion im Bildungsbereich', + 'Forschungsansätze' + ] + + entry_types = [ + 'Zeitschriftenartikel', + 'Buch', + 'Buchteil', + 'Bericht', + 'Konferenz-Paper' + ] + + data = [] + + for entry in bib_database.entries: + entry_data = { + 'FU': None, + 'Category': None, + 'Index': None, + 'Type': entry.get('ENTRYTYPE', '').lower() + } + + if 'keywords' in entry: + entry_keywords = list(map(str.lower, map(str.strip, entry['keywords'].replace('\\#', '#').split(',')))) + + for key, value in research_questions.items(): + if key in entry_keywords: + entry_data['FU'] = value + + for key, value in categories.items(): + if key in entry_keywords: + entry_data['Category'] = value + + for index_term in index_terms: + if index_term.lower() in entry_keywords: + entry_data['Index'] = index_term + + if all(value is not None for value in entry_data.values()): + data.append(entry_data) + + return data + + # Pfaddiagramm erstellen +def create_path_diagram(data): + labels = [] + sources = [] + targets = [] + values = [] + color_map = { + 'zeitschriftenartikel': colors['primaryLine'], + 'konferenz-paper': colors['secondaryLine'], + 'buch': colors['depthArea'], + 'buchteil': colors['brightArea'], + 'bericht': colors['accent'] + } + + def add_to_labels(label): + if label not in labels: + labels.append(label) + return labels.index(label) + + for entry in data: + fu_idx = add_to_labels(entry['FU']) + category_idx = add_to_labels(entry['Category']) + index_idx = add_to_labels(entry['Index']) + type_idx = add_to_labels(entry['Type']) + + sources.extend([fu_idx, category_idx, index_idx]) + targets.extend([category_idx, index_idx, type_idx]) + values.extend([1, 1, 1]) + + node_colors = [color_map.get(label, colors['primaryLine']) for label in labels] + + fig = go.Figure(data=[go.Sankey( + node=dict( + pad=15, + thickness=20, + line=dict(color=colors['white'], width=0.5), + label=labels, + color=node_colors + ), + link=dict( + source=sources, + target=targets, + value=values + ) + )]) + + fig.update_layout( + title_text=f'Kategorischer Analysepfad der Literatur (n={len(data)}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', + font=dict(size=10, color=colors['white']), + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'] + ) + + fig.show() + export_figure(fig, "create_path_diagram", export_fig_create_path_diagram, bib_filename) + + +############# + +def create_sankey_diagram(bib_database): + def extract_year(entry): + """ + Extrahiert ein gültiges Jahr aus dem `year`-Feld eines Eintrags. + """ + year_str = entry.get('year', '').strip() + try: + # Suche nach einer 4-stelligen Jahreszahl + year_match = re.search(r'\b\d{4}\b', year_str) + if year_match: + return int(year_match.group()) + else: + raise ValueError(f"Kein gültiges Jahr gefunden: {year_str}") + except ValueError: + print(f"Warnung: Ungültiger Jahreswert in Eintrag übersprungen: {year_str}") + return None + + current_year = datetime.now().year + + # Schätzungen und Filterkriterien mit sicheren Zugriffen + initial_sources = len(bib_database.entries) + screened_sources = sum( + 1 for entry in bib_database.entries if 'Promotion:Literaturanalyse' in entry.get('keywords', '') + ) + quality_sources = sum( + 1 for entry in bib_database.entries + if entry.get('ENTRYTYPE') in ['article', 'phdthesis'] and 'Promotion:Literaturanalyse' in entry.get('keywords', '') + ) + relevance_sources = sum( + 1 for entry in bib_database.entries + if any(kw in entry.get('keywords', '') for kw in ['Promotion:FU3', 'Promotion:Kerngedanke']) + ) + thematic_sources = sum( + 1 for entry in bib_database.entries + if any(kw in entry.get('keywords', '') for kw in ['digital', 'learning']) + ) + recent_sources = sum( + 1 for entry in bib_database.entries + if (year := extract_year(entry)) and year >= current_year - 5 + ) + classic_sources = sum( + 1 for entry in bib_database.entries + if (year := extract_year(entry)) and year < current_year - 5 and 'classic' in entry.get('keywords', '').lower() + ) + selected_sources = recent_sources + classic_sources + + # Stichprobengröße berechnen + sample_size = calculate_sample_size(initial_sources) + + # Phasen und Verbindungen definieren + phases = [ + "Identifizierte Quellen", + "Nach Screening (Literaturanalyse-Markierung)", + "Nach Qualitätsprüfung (Artikel und Dissertationen)", + "Nach Relevanzprüfung (FU3 und Kerngedanken)", + "Nach thematischer Prüfung (Digital & Learning)", + "Aktuelle Forschung (letzte 5 Jahre)", + "Klassische Werke", + "Ausgewählte Quellen (Endauswahl)" + ] + + sources = [0, 1, 2, 3, 4, 4, 4] + targets = [1, 2, 3, 4, 5, 6, 7] + values = [ + screened_sources, + quality_sources, + relevance_sources, + thematic_sources, + recent_sources, + classic_sources, + selected_sources + ] + + # Prozentsätze berechnen für die Labels + percentages = [ + "100.0%", # Startwert + f"{screened_sources / initial_sources * 100:.1f}%", + f"{quality_sources / screened_sources * 100:.1f}%" if screened_sources > 0 else "0.0%", + f"{relevance_sources / quality_sources * 100:.1f}%" if quality_sources > 0 else "0.0%", + f"{thematic_sources / relevance_sources * 100:.1f}%" if relevance_sources > 0 else "0.0%", + f"{recent_sources / thematic_sources * 100:.1f}%" if thematic_sources > 0 else "0.0%", + f"{classic_sources / thematic_sources * 100:.1f}%" if thematic_sources > 0 else "0.0%", + f"{selected_sources / (recent_sources + classic_sources) * 100:.1f}%" if (recent_sources + classic_sources) > 0 else "0.0%" + ] + + # Labels für Knoten anpassen, um Prozentsätze anzuzeigen + node_labels = [f"{ph} ({pct})" for ph, pct in zip(phases, percentages)] + + # Farben für die einzelnen Phasen + node_colors = [ + colors['primaryLine'], # Identifizierte Quellen + colors['secondaryLine'], # Nach Screening + colors['brightArea'], # Nach Qualitätsprüfung + colors['depthArea'], # Nach Relevanzprüfung + colors['positiveHighlight'], # Nach thematischer Prüfung + colors['negativeHighlight'], # Aktuelle Forschung + colors['accent'], # Klassische Werke + colors['positiveHighlight'] # Ausgewählte Quellen + ] + + # Sankey-Diagramm erstellen + fig = go.Figure(go.Sankey( + node=dict( + pad=15, + thickness=20, + line=dict(color="black", width=0.5), + label=node_labels, + color=node_colors + ), + link=dict( + source=sources, + target=targets, + value=values, + hoverinfo='all', # Zeigt detaillierte Infos bei Mouseover an + color=colors['accent'] + ) + )) + + # Layout anpassen + fig.update_layout( + title_text=f"Flussdiagramm der Literaturselektion (Stichprobe: n={sample_size}, Stand: {current_date}) | Quelle: {bib_filename.replace('.bib', '')}", + font_size=12, # Größere Schriftgröße für bessere Lesbarkeit + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']) + ) + + fig.show() + export_figure(fig, "create_sankey_diagram", export_fig_create_sankey_diagram, bib_filename) + +########## + +def calculate_sample_size(N, Z=1.96, p=0.5, e=0.05): + """ + Berechnet die Stichprobengröße basierend auf der Gesamtanzahl der Einträge (N). + """ + if N <= 0: + return 0 + n_0 = (Z**2 * p * (1 - p)) / (e**2) + n = n_0 / (1 + ((n_0 - 1) / N)) + return math.ceil(n) + +def visualize_sources_status(bib_database): + """ + Visualisiert den Status der analysierten und nicht analysierten Quellen pro Suchordner. + """ + search_folder_tags = [ + "#1:zeitschriftenartikel:learning:management:system", + "#2:zeitschriftenartikel:online:lernplattform", + "#3:zeitschriftenartikel:online:lernumgebung", + "#4:zeitschriftenartikel:mooc", + "#5:zeitschriftenartikel:e-learning", + "#6:zeitschriftenartikel:bildung:technologie", + "#7:zeitschriftenartikel:digital:medien", + "#8:zeitschriftenartikel:blended:learning", + "#9:zeitschriftenartikel:digital:lernen", + "#a:zeitschriftenartikel:online:lernen", + "#b:zeitschriftenartikel:online:learning", + "#0:zeitschriftenartikel:digital:learning", + "#1:konferenz-paper:learning:management:system", + "#2:konferenz-paper:online:lernplattform", + "#3:konferenz-paper:online:lernumgebung", + "#4:konferenz-paper:mooc", + "#5:konferenz-paper:e-learning", + "#6:konferenz-paper:bildung:technologie", + "#7:konferenz-paper:digital:medien", + "#8:konferenz-paper:blended:learning", + "#9:konferenz-paper:digital:lernen", + "#a:konferenz-paper:online:lernen", + "#b:konferenz-paper:online:learning", + "#0:konferenz-paper:digital:learning" + ] + + category_tags = {"promotion:argumentation", "promotion:kerngedanke", "promotion:weiterführung", "promotion:schlussfolgerung"} + source_data = defaultdict(lambda: {'Identifiziert': 0, 'Analysiert': 0}) + + if not bib_database or not bib_database.entries: + print("Fehler: Die Datenbank enthält keine Einträge.") + return + + for entry in bib_database.entries: + keywords = entry.get('keywords', '') + if not keywords: + continue + + entry_keywords = set(map(str.lower, map(str.strip, keywords.replace('\\#', '#').split(',')))) + + for tag in search_folder_tags: + if tag.lower() in entry_keywords: + source_data[tag]['Identifiziert'] += 1 + if entry_keywords & category_tags: + source_data[tag]['Analysiert'] += 1 + + table_data = [] + analysiert_values = [] + nicht_analysiert_values = [] + analysiert_colors = [] + tags = [] + + for tag, counts in sorted(source_data.items(), key=lambda item: item[1]['Identifiziert'], reverse=True): + stichprobe = calculate_sample_size(counts['Identifiziert']) + noch_zu_analysieren = counts['Identifiziert'] - counts['Analysiert'] + noch_benoetigt_fuer_stichprobe = max(0, stichprobe - counts['Analysiert']) + + table_data.append([ + tag, + counts['Identifiziert'], + counts['Analysiert'], + noch_zu_analysieren, + stichprobe, + noch_benoetigt_fuer_stichprobe + ]) + + analysiert_values.append(counts['Analysiert']) + nicht_analysiert_values.append(noch_zu_analysieren) + tags.append(tag) + + analysiert_colors.append(colors['positiveHighlight'] if counts['Analysiert'] >= stichprobe else colors['negativeHighlight']) + + print(tabulate( + table_data, + headers=['Suchordner', 'Identifiziert', 'Analysiert', 'nicht-Analysiert', 'Stichprobe', 'Noch benötigt für Stichprobe'], + tablefmt='grid' + )) + + fig = go.Figure() + + fig.add_trace(go.Bar( + x=tags, + y=analysiert_values, + name='Analysiert', + marker=dict(color=analysiert_colors) + )) + + fig.add_trace(go.Bar( + x=tags, + y=nicht_analysiert_values, + name='Nicht-Analysiert', + marker=dict(color=colors['primaryLine']) + )) + + fig.update_layout( + barmode='stack', + title=f'Analyse- und Stichprobenstatus je Suchordner (n={sum(counts["Identifiziert"] for counts in source_data.values())}, Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', + xaxis_title='Suchbegriffsordner', + yaxis_title='Anzahl der Quellen', + plot_bgcolor=colors['background'], + paper_bgcolor=colors['background'], + font=dict(color=colors['white']), + xaxis=dict( + categoryorder='array', + categoryarray=search_folder_tags + ) + ) + + fig.show() + export_figure(fig, "visualize_sources_status", export_fig_visualize_sources_status, bib_filename) + +############# + +# Funktion zur Erstellung einer Wortwolke aus Überschriften +def create_wordcloud_from_titles(bib_database, stop_words): + global bib_filename + titles = [entry.get('title', '') for entry in bib_database.entries] + + # Wörter zählen + word_counts = defaultdict(int) + for title in titles: + for word in title.split(): + word = word.lower().strip(",.!?\"'()[]{}:;") + if word and word not in stop_words: + word_counts[word] += 1 + + # Wortwolke erstellen + wordcloud = WordCloud( + width=800, + height=400, + background_color=colors['background'], + color_func=lambda *args, **kwargs: random.choice(word_colors) + ).generate_from_frequencies(word_counts) + + # Wortwolke anzeigen + plt.figure(figsize=(10, 5)) + plt.imshow(wordcloud, interpolation='bilinear') + plt.axis('off') + plt.title(f'Häufigkeitsanalyse von Titelwörtern (Stand: {current_date}) | Quelle: {bib_filename.replace(".bib", "")}', color=colors['white']) + plt.show() + + if export_fig_create_wordcloud_from_titles and bib_filename: + export_filename = f"wordcloud_{slugify(bib_filename.replace('.bib', ''))}.png" + wordcloud.to_file(export_filename) + print(f"✅ Wortwolke exportiert als '{export_filename}'") + +# Aufrufen der Visualisierungsfunktionen +visualize_network(bib_database) +visualize_tags(bib_database) +visualize_index(bib_database) +visualize_research_questions(bib_database) +visualize_categories(bib_database) +visualize_time_series(bib_database) +visualize_top_authors(bib_database) +visualize_top_publications(bib_database) +data = prepare_path_data(bib_database) +create_path_diagram(data) +create_sankey_diagram(bib_database) +visualize_sources_status(bib_database) +create_wordcloud_from_titles(bib_database, stop_words)