🚀 Korpusanalyse – Textkomplexität berechnen#
Hinweise zur Ausführung des Notebooks#
Dieses Notebook kann auf unterschiedlichen Levels erarbeitet werden (siehe Abschnitt “Technische Voraussetzungen”):
Book-Only Mode
Cloud Mode: Dafür auf 🚀 klicken und z.B. in Colab ausführen.
Local Mode: Dafür auf Herunterladen ↓ klicken und “.ipynb” wählen.
Übersicht#
Im Folgenden wird die Textkomplexität der einzelnen Pressemitteilungen mit der Python-Bibliothek readability errechnet, für unterschiedliche Zeitabschnitte (Monate, Jahre) zusammengefasst und visualisiert.
Einlesen des Korpus und der Metadaten
Errechnen der Komplexitätsmaße
# import libraries for table processing and for compuation of readability metrics
from pathlib import Path
from multiprocessing import Pool, cpu_count
from functools import partial
import requests
import pandas as pd
import textstat
textstat.set_lang("de")
1. Einlesen der Daten und Metadaten#
Exemplarisch lesen wir einen Text ein und extrahieren die Metadaten des Texts aus der Metadaten-Tabelle, um in einem späteren Schritt das Datum der Veröffentlichung extrahieren zu können, um die Textkomplexität diachron zu analysieren.
Text einlesen#
Show code cell content
# 🚀 Create data directory path
corpus_path = Path(r"../data/txt")
if not corpus_path.exists():
corpus_path.mkdir()
Show code cell content
# 🚀 Load the text file from GitHub
! wget https://raw.githubusercontent.com/quadriga-dk/Text-Fallstudie-2/refs/heads/main/data/txt/2011-01-04_id8787.txt -P ../data/txt
# set path to text
text_path = Path(r"../data/txt/1000035.txt")
# read text
text = text_path.read_text()
print(text)
Bezirksamt setzt Empfehlungen des Rechnungshofs um
Direkt zur Kontaktinformation Pressemitteilung Nr. 198 vom 05.10.2020 Das Bezirksamt dankt dem Berliner Rechnungshof für seine umfängliche Bewertung. Die Empfehlungen des Rechnungshofs hinsichtlich verwaltungsinterner Abläufe wurden bereits nach Eingang des Entwurfs des Prüfberichts mit einem Bezirksamtsbeschluss im September 2020 umgesetzt. Sie tragen zu einer Optimierung der Abläufe beim Vorkaufsrecht bei. Zukünftig wird das Bezirksamtskollegium final entscheiden, ob ein Vorkaufsrecht ausgeübt werden soll. Im Vorfeld werden das Rechtsamt und die Beauftragte des Haushalts beteiligt. Zudem hat das Bezirksamt verabredet, für verschiedene Typen von vorkaufsbegünstigten Dritten spezielle Prüfkriterien zu entwickeln. Bezirksstadtrat Florian Schmidt erklärt: „Der Rechnungshof hat wichtige Hinweise gegeben, die das Bezirksamt im Sinne einer einheitlichen Verwaltungspraxis in Zukunft berücksichtigen wird. Entscheidend ist, dass das Vorkaufsrecht zu Gunsten von Genossenschaften seit 2019 zur Aufrechterhaltung des Milieuschutzes maßgeblich beiträgt. Inzwischen wurde in fünf Berliner Bezirken elf Mal das Vorkaufsrecht zugunsten von Genossenschaften ausgeübt, 386 Wohnungen konnten gesichert werden.“ Stadtrat Schmidt ergänzt: „Tatsächlich sind im Zusammenhang mit der Ausübung des Vorkaufsrechts Kosten entstanden. Bisher sind 160.000 Euro fällig geworden und es könnten nach derzeitigem Stand noch 110.000 Euro dazukommen. Das ist natürlich bedauerlich für den Bezirk und wird aus dem Etat der Stadtentwicklungsverwaltung getragen bzw. kompensiert. Das Bezirksamt bedauert das sehr. Hier muss jedoch berücksichtigt werden, dass diese Kosten vor allem aus den nicht vom Bezirksamt verantworteten Verzögerungen resultieren, wie beispielsweise durch dadurch entstandene höhere Anwalts- und Notarkosten. Für das Land Berlin bestand grundsätzlich kein außerordentliches finanzielles Risiko, weil die Häuser unter dem Verkehrswert erworben wurden und notfalls ein Weiterverkauf hätte durchgeführt werden können.“ Pressestelle Tel.: (030) 90298 2843 E-Mail E-Mail an die Pressestelle
Show code cell content
# 🚀 Load the metadata file from GitHub
! wget https://raw.githubusercontent.com/quadriga-dk/Text-Fallstudie-2/refs/heads/main/data/metadata.csv -P ../data
Metadaten einlesen#
# set path to metadata
metadata_path = "../data/metadata.csv"
# read metadata
metadata_df = pd.read_csv(metadata_path, sep=",")
Ersten fünf Zeilen anzeigen lassen:
metadata_df.head()
id | url | date | title | source | filename_html | filename | n_tokens | |
---|---|---|---|---|---|---|---|---|
0 | 1571189 | https://www.berlin.de/rbmskzl/aktuelles/presse... | 18.06.2025 | Mehr Sicherheit für Daten und Digitalisierungs... | Presse- und Informationsamt des Landes Berlin | 1571189.html | 1571189.txt | 786 |
1 | 1571151 | https://www.berlin.de/ba-spandau/aktuelles/pre... | 18.06.2025 | Aufruf zur Teilnahme: Online-Befragung Spandau... | Bezirksamt Spandau | 1571151.html | 1571151.txt | 713 |
2 | 1571106 | https://www.berlin.de/ba-marzahn-hellersdorf/a... | 18.06.2025 | Marzahn-Hellersdorf sucht freie Räume für bürg... | Bezirksamt Marzahn-Hellersdorf | 1571106.html | 1571106.txt | 671 |
3 | 1571032 | https://www.berlin.de/ba-lichtenberg/aktuelles... | 18.06.2025 | 4. Queere Kunst- und Kulturtage in Lichtenberg | Bezirksamt Lichtenberg | 1571032.html | 1571032.txt | 828 |
4 | 1571030 | https://www.berlin.de/ba-neukoelln/aktuelles/p... | 18.06.2025 | Online-Umfrage zur Sauberkeit und Ordnung in B... | Bezirksamt Neukölln | 1571030.html | 1571030.txt | 978 |
Metadatum anhand von Dateinamen extrahieren:
current_meta_data = metadata_df[metadata_df.filename == text_path.name]
current_meta_data
id | url | date | title | source | filename_html | filename | n_tokens | |
---|---|---|---|---|---|---|---|---|
31067 | 1000035 | https://www.berlin.de/ba-friedrichshain-kreuzb... | 05.10.2020 | Bezirksamt setzt Empfehlungen des Rechnungshof... | Bezirksamt Friedrichshain-Kreuzberg | 1000035.html | 1000035.txt | 1477 |
2. Textkomplexität berechnen#
Im folgenden berechnen wir mittels vier unterschiedlicher Metriken die Textkomplexität. Alle Metriken verwenden für die Berechnung des Komplexitätsscores die durchschnittliche Satzlänge und nehmen unterschiedliche Worteingenschaften dazu. Zuerst verschaffen wir uns einen Überblick über die Texteigenschaften, um die Ergebnisse der Metriken besser bewerten zu können.
print(f"Anzahl an Buchstaben: {textstat.letter_count(text)}")
print(f"Anzahl an Sätzen: {textstat.sentence_count(text)}")
print(f"Anzahl an Wörtern: {textstat.lexicon_count(text)}")
print(f"Durchschnittliche Anzahl an Buchstaben pro Wort: {textstat.avg_character_per_word(text)}")
print(f"Durchschnittliche Anzahl an Silben pro Wort: {textstat.avg_syllables_per_word(text)}")
print(f"Durchschnittliche Satzlänge: {textstat.avg_sentence_length(text)}")
print(f"Anzahl an Wörtern mit drei oder mehr Silben: {textstat.polysyllabcount(text)}")
print(f"Anzahl an langen Wörtern: {textstat.long_word_count(text)}")
Anzahl an Buchstaben: 1828
Anzahl an Sätzen: 20
Anzahl an Wörtern: 267
Durchschnittliche Anzahl an Buchstaben pro Wort: 7.00374531835206
Durchschnittliche Anzahl an Silben pro Wort: 2.161048689138577
Durchschnittliche Satzlänge: 13.35
Anzahl an Wörtern mit drei oder mehr Silben: 95
Anzahl an langen Wörtern: 115
/tmp/ipykernel_2066/3628178555.py:6: DeprecationWarning: The 'avg_sentence_length' method has been deprecated due to being the same as 'words_per_sentence'. This method will be removed in thefuture.
print(f"Durchschnittliche Satzlänge: {textstat.avg_sentence_length(text)}")
2.1 Flesch#
Berechnung:
180 - SL - (58,5 × ASW)
SL = Durchschnittliche Satzlänge (Anzahl der Wörter geteilt durch Anzahl der Sätze)
ASW = Durchschnittliche Silbenzahl pro Wort (Anzahl der Silben geteilt durch Anzahl der Wörter) \
Interpretation: 0 (sehr schwierig) bis 100 (sehr leicht)
textstat.flesch_reading_ease(text)
40.22865168539326
Ein Score zwischen 30 und 50 gibt eine hohe Schwierigkeit an, ungefährt äquivalent zum Niveau von Bachelorstudierenden.
2.2 Wiener Sachtextformel#
Berechnung: (0,2007 × MS) + (0,1682 × ASL) + (0,1373 × IW) - 2,779
MS = Prozentsatz der Wörter mit drei oder mehr Silben
SL = Durchschnittliche Satzlänge (Anzahl der Wörter geteilt durch Anzahl der Sätze)
IW = Anzahl an langen Wörtern (länger als sechs Buchstaben) \
4 (leicht) bis 15 (schwierig), orientiert an Schulklassen
textstat.wiener_sachtextformel(text, variant=2)
12.521151647940076
Die aufs Deutsche ausgelegte Wiener Sachtextformel stuft den Text auch als schwierig ein, etwa auf das Niveau der zwölften Klasse.
2.3 Automated Readability Index (ARI)#
Berechnung: 4,71 × WL + 0,5 × SL - 21,43
WL: Durchschnittliche Wortlänge in Buchstaben (Anzahl der Zeichen geteilt durch Anzahl der Wörter)
SL: Durchschnittliche Satzlänge (Anzahl der Wörter geteilt durch Anzahl der Sätze) \
1 (sehr leicht) bis 14 (schwierig), orientiert an Schulklassen
textstat.automated_readability_index(text)
18.2326404494382
Laut dem ARI wird der Text als äußerst schwieriger eingestuft und ist mit einem Score über 14 außerhalb der festgelegten Skala.
2.4 Coleman-Liau Index#
Berechnung: 0,0588 × L - 0,296 × S - 15,8
L = Durchschnittliche Anzahl von Buchstaben pro 100 Wörter
S = Durchschnittliche Anzahl von Sätzen pro 100 Wörter \
Interpretation: 1 (sehr leicht) bis 14 (schwierig), orientiert an Schulklassen
textstat.coleman_liau_index(text)
21.692134831460674
Wie bei ARI liegt auch beim Coleman-Liau Index der Index außerhalb der angedachten Skala und ist somit als sehr schwierig zu beurteilen.
3. Berechnung auf dem gesamten Korpus#
Show code cell content
# 🚀 Create download list
github_api_txt_dir_path = "https://github.com/quadriga-dk/Text-Fallstudie-2/tree/main/data/txt"
txt_dir_info = requests.get(github_api_txt_dir_path).json()
url_list = [entry["download_url"] for entry in txt_dir_info]
# 🚀 Write download list as txt file
url_list_path = Path("github_txt_file_urls.txt")
with url_list_path.open('w') as output_txt:
output_txt.write("\n".join(url_list))
Show code cell content
# ⚠️ Only execute, if you haven't downloaded the files yet!
# 🚀 Download all txt files – this step will take a while
! wget -i github_txt_file_urls.txt -P ../data/txt
Setzen des Pfads zum Korpus-Ordner:
corpus_path = Path(r"../data/txt/")
Korpus einlesen:
# Create dictionary to save the filenames and the text
corpus = {"filename":[], "text":[]}
# Iterate corpus directory, read text, save filename and text to corpus dict
for fp in corpus_path.iterdir():
if fp.is_file():
corpus["filename"].append(fp.name)
corpus["text"].append(fp.read_text())
Scores pro Text berechnen:
readability_scores = {}
# for speed, we use multiprocessing for computing the scores
with Pool(cpu_count()) as p:
readability_scores["Flesch"] = p.map(textstat.flesch_reading_ease, corpus["text"])
readability_scores["Wiener_Sachtextformel"] = p.map(partial(textstat.wiener_sachtextformel, variant=1), corpus["text"])
readability_scores["ARI"] = p.map(textstat.automated_readability_index, corpus["text"])
readability_scores["Coleman_Liau"] = p.map(textstat.coleman_liau_index, corpus["text"])
# add scores to corpus dictionary
for measure, score in readability_scores.items():
corpus[measure] = score
# transform to DataFrame
corpus_df = pd.DataFrame(corpus)
# delete text column
corpus_df = corpus_df.drop(["text"], axis=1)
corpus_df.head()
4. Zusammenfügen der Scores mit den Metadaten#
Korpusdaten mit dem Metadaten zusammenfügen. Die Einträge werden über den Dateinamen einander zugeordnet.
metadata_df = metadata_df.merge(corpus_df)
metadata_df.head()
5. Ergebnis speichern#
# 🚀 Create result directory path
result_path = Path(r"../results")
if not result_path.exists():
result_path.mkdir()
result_path = Path(r"../results")
metadata_df.to_csv(result_path / "metadata_with_readability_scores.csv", index=False)