🚀 Korpusanalyse – Textkomplexität berechnen#

Hinweise zur Ausführung des Notebooks#

Dieses Notebook kann auf unterschiedlichen Levels erarbeitet werden (siehe Abschnitt “Technische Voraussetzungen”):

  1. Book-Only Mode

  2. Cloud Mode: Dafür auf 🚀 klicken und z.B. in Colab ausführen.

  3. 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.

  1. Einlesen des Korpus und der Metadaten

  2. 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#

Hide code cell content
# 🚀 Create data directory path
corpus_path = Path(r"../data/txt")
if not corpus_path.exists():
    corpus_path.mkdir()
Hide 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
Hide 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#

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