5.5. đ Analyse 2: Syntaktische N-Gramme#
5.5.1. 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.
5.5.2. Ăbersicht#
Im Folgenden wird das Korpus mithilfe syntaktischer n-Gramme analysiert. Ziel ist es, wiederkehrende grammatische Muster, insbesondere adjektivische Modifikationen von Substantiven, ĂŒber Zeit zu untersuchen und damit ĂŒber rein lexikalische HĂ€ufigkeiten hinauszugehen. Im Unterschied zur Analyse semantischer Felder stehen hier syntaktisch motivierte Wortkombinationen auf Basis von AbhĂ€ngigkeitsbeziehungen im Fokus, etwa Adjektiv â Substantiv-Relationen wie âverdorbene â Luftâ, âfrischer â Windâ oder âdicker â Qualmâ. Untersucht wird, wie hĂ€ufig solche Konstruktionen im Korpus auftreten und wie sich ihr Vorkommen zeitlich entwickelt â um stilistische und diskursive VerĂ€nderungen in der Beschreibung bestimmter Konzepte sichtbar zu machen.
In diesem Notebook-Walkthrough beschrĂ€nken wir uns aus GrĂŒnden der Ăbersichtlichkeit auf das Substantiv âLuftâ, der Code funktioniert jedoch ebenso mit einer ganzen Liste mehrerer Substantive.
Dazu werden die folgenden Schritte durchgefĂŒhrt:
Einlesen des Korpus, der Metadaten sowie der bereits erzeugten spaCy-Annotationen
Auswahl relevanter POS-tags & Dependency-Relationen zur Bildung syntaktischer n-Gramme
Extraktion syntaktischer n-Gramme auf Basis der vorhandenen AbhÀngigkeitsinformationen
Identifikation von AusreiĂertexten, die auffĂ€llige Peaks in den Zeitreihen (mit-)verursachen
KWIC-/Kontextansicht ausgewÀhlter Belegstellen aus diesen Texten zur qualitativen Einordnung der syntaktischen Muster
VerÀnderungsorientierte Analyse: Identifikation aufkommender und verschwindender Modifikatoren sowie der ZeitrÀume mit dem stÀrksten Vokabularwandel
Diskussion der Ergebnisse
5.5.3. Import der Bibliotheken#
import sys
!{sys.executable} -m pip install numpy pandas plotly itables tqdm spacy click nbformat
import requests
from pathlib import Path
import re
from typing import Dict, List, Union, Tuple
from collections import OrderedDict, Counter
from time import time
import numpy as np
import pandas as pd
import nbformat
import plotly.express as px
import plotly.io as pio
import plotly.graph_objects as go
from itables import show
from tqdm import tqdm
import spacy
from spacy.tokens import DocBin, Doc
if 'google.colab' in sys.modules:
pio.renderers.default = "colab"
else:
pio.renderers.default = "notebook+plotly_mimetype"
Laden des spaCy-Modells
! {sys.executable} -m spacy download de_core_news_sm
nlp = spacy.load("de_core_news_sm")
5.5.4. Einlesen der Annotationen#
In diesem Schritt werden die zuvor erzeugten spaCy-Annotationen aus dem Dateisystem eingelesen. Die gespeicherte Doc-Dateien (siehe Annotation mit Spacy/Dateien schreiben werden zu vollstÀndigen spaCy.Doc-Objekten rekonstruiert und in einer Datenstruktur abgelegt, die eine weitere Analyse erlaubt.
In der folgenden Zelle definieren wir den Pfad zum Quellordner, in dem die spacy-Annotationen als Dateien gespeichert sind. AnschlieĂend erstellen wir diesen Ordner (mit der Methode mkdir, die Ordner anlegen kann):
# đ Create data directory path
# Change path here when using another corpus
annotation_dir = Path("../data/spacy")
annotation_dir.mkdir(parents=True, exist_ok=True)
In der folgenden Zelle erhalten wir die Liste der Dateien in dem Ordner auf GitHub, in dem die wĂ€hrend der Korpusverarbeitung erzeugten spaCy-Annotationen gespeichert sind. Die Liste der direkten URLs zu diesen Dateien wird zunĂ€chst in der Variable url_list gespeichert und anschlieĂend in die lokal abgelegte Textdatei github_txt_file_urls.txt geschrieben.
# đ Create download list
github_api_txt_dir_path = "https://api.github.com/repos/quadriga-dk/Text-Fallstudie-3/contents/data/spacy"
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))
In der folgenden Zelle verwenden wir das Programm wget, um jede Datei herunterzuladen. wget liest dabei die URL jeder Datei mit spaCy-Annotationen aus der Textdatei github_txt_file_urls.txt, die wir im vorherigen Schritt erstellt haben.
# â ïž Only execute, if you haven't downloaded the files yet!
# đ Download all spacy files from github â this step will take a while
! wget -i github_txt_file_urls.txt -P ../data/spacy
In der folgenden Zelle iterieren wir ĂŒber die .spacy-Dateien und öffnen jede einzelne als spaCy-Objekt in Python (damit alle linguistischen Merkmale des Textes im weiteren Code programmatisch genutzt werden können). â ïž Die Verarbeitung aller Annotationsdateien dauert mehrere Minuten!
# Create dictionary to save the corpus data (filenames and tables)
annotated_docs = {}
start = time()
# Iterate over spacy files
for fp in tqdm(annotation_dir.iterdir(), desc="Reading annotated data"):
# check if the entry is a file, not a directory
if fp.is_file():
# check if the file has the correct suffix spacy
if fp.suffix == '.spacy':
print( f"Loading file: {fp.name}" )
# load spacy DocBin objects
doc_bin = DocBin().from_disk(fp)
chunk_docs = list(doc_bin.get_docs(nlp.vocab))
# merge bins into one single document
full_doc = Doc.from_docs(chunk_docs)
# save the data frame to the dictionary, key=filename (without suffix), value=spacy.Doc
annotated_docs[fp.stem] = full_doc
took = time() - start
print(f"Loading the data took: {round(took, 4)} seconds")
Loading the data took: 264.5419 seconds
Diese Zelle gibt die ersten 20 Zeilen der Annotation des Textes aus, der zufÀllig der erste im Python-Dictionary ist, das die Annotationen aktuell im Arbeitsspeicher speichert.
print(f"Annotations of first 20 lines of the text: {list(annotated_docs.keys())[0]}:\n")
print("Token\tLemma\tPoS")
for token in annotated_docs[list(annotated_docs.keys())[0]][:20]:
print(f"{token.text}\t{token.lemma_}\t{token.pos_}")
Annotations of first 20 lines of the text: Gottfried_Keller_-_Die_drei_gerechten_Kammacher_(1856):
Token Lemma PoS
Gottfried Gottfried PROPN
Keller Keller PROPN
Die der DET
drei drei NUM
gerechten gerecht ADJ
Kammacher Kammacher NOUN
Die der DET
Leute Leute NOUN
von von ADP
Seldwyla Seldwyla PROPN
haben haben AUX
bewiesen beweisen VERB
, -- PUNCT
daĂ daĂ SCONJ
eine ein DET
ganze ganz ADJ
Stadt Stadt NOUN
von von ADP
Ungerechten Ungerechte NOUN
oder oder CCONJ
5.5.5. Einlesen der Metadaten#
AnschlieĂend werden die zugehörigen Metadaten geladen und auf diejenigen Texte beschrĂ€nkt, fĂŒr die Annotationen vorliegen. Die zeitlichen Angaben werden in ein geeignetes Datumsformat ĂŒberfĂŒhrt, um spĂ€tere zeitbasierte Aggregationen und Visualisierungen zu erleichtern.
Zur Erinnerung: Zwei-Stichproben-Ansatz#
FĂŒr die Robustheit der Analyse werden zwei verschiedene Stichproben (siehe Abschnitt âSampling und Filterung des Korpusâ) verwendet:
Sample 1: ZufÀllige Auswahl von 50 Texten pro Jahrzehnt (random_state=42)
Sample 2: Alternative ZufÀllige Auswahl von 50 Texten pro Jahrzehnt (random_state=31415)
Dieser Ansatz ermöglicht es, zu ĂŒberprĂŒfen, ob die beobachteten Muster robust gegenĂŒber unterschiedlichen Stichproben sind oder ob sie durch die spezifische Textauswahl beeinflusst werden.
In der folgenden Zelle definieren wir den Pfad zu dem Ordner, in dem die Metadatentabellen abgelegt werden sollen.
# đ Create metadata directory path
metadata_dir = Path("../metadata")
metadata_dir.mkdir(parents=True, exist_ok=True)
In der folgenden Zelle verwenden wir das Programm wget, um die Metadaten-Dateien fĂŒr die beiden Samples aus dem GitHub-Repository der Fallstudie herunterzuladen und sie im dafĂŒr vorgesehenen Ordner abzulegen.
# đ Load the metadata files from GitHub
! wget https://raw.githubusercontent.com/quadriga-dk/Text-Fallstudie-3/refs/heads/main/metadata/metadata_corpus-german_language_fiction_1810-1900_50-per-decade.csv -P ../metadata
! wget https://raw.githubusercontent.com/quadriga-dk/Text-Fallstudie-3/refs/heads/main/metadata/metadata_corpus-german_language_fiction_1810-1900_50-per-decade_ALT.csv -P ../metadata
In den folgenden Zellen lesen wir die heruntergeladenen Metadaten mit der Python-Bibliothek pandas fĂŒr die tabellarische Verarbeitung ein und zeigen, wie die Tabellen aussehen.
Stichprobe 1:
metadata_df = pd.read_csv(metadata_dir / "metadata_corpus-german_language_fiction_1810-1900_50-per-decade.csv")
# Datentyp der Datumsspalte fĂŒr eine einfachere Weiterverarbeitung Ă€ndern
metadata_df['DC.date'] = pd.to_datetime(metadata_df['DC.date'], format="%Y")
metadata_df = metadata_df[metadata_df['DC.identifier'].isin(annotated_docs.keys())]
show(metadata_df)
| Loading ITables v2.6.1 from the internet... (need help?) |
Stichprobe 2:
metadata_df_alt = pd.read_csv(metadata_dir / "metadata_corpus-german_language_fiction_1810-1900_50-per-decade_ALT.csv")
# Datentyp der Datumsspalte fĂŒr eine einfachere Weiterverarbeitung Ă€ndern
metadata_df_alt['DC.date'] = pd.to_datetime(metadata_df_alt['DC.date'], format="%Y")
metadata_df_alt = metadata_df_alt[metadata_df_alt['DC.identifier'].isin(annotated_docs.keys())]
show(metadata_df_alt)
| Loading ITables v2.6.1 from the internet... (need help?) |
5.5.6. Syntaktische N-Gramme extrahieren#
In einem weiteren Schritt können wir die Adjektive extrahieren, die mit dem Nomen Luft in Verbindung stehen (siehe Syntaktische N-Gramme). Wir suchen in den annotierten Texten nach allen FĂ€llen, in denen Luft durch ein Adjektiv nĂ€her bestimmt wird â wie in frische Luft, schlechte Luft oder verdorbene Luft.
Wir machen dabei Gebrauch von den Dependenzstrukturen, die sich durch das spaCy-eigene Doc einfach navigieren lassen. In den Standard-spaCy-Modellen fĂŒr die deutsche Sprache wird die syntaktische Position eines solchen adjektivischen Modifikators â anders als im Englischen und einigen anderen Sprachen, wo das Label âamodâ verwendet wird â aus historischen GrĂŒnden mit ânkâ (noun kernel element) annotiert.
In unserer Implementierung stĂŒtzen wir uns daher auf die Kombination aus dem Dependency-Label ânkâ und dem POS-Tag âADJâ, um Adjektive zu identifizieren, die unser Zielsubstantiv nĂ€her bestimmen.
In der folgenden Zelle definieren wir die Funktion, die die eigentliche Extraktion der abhĂ€ngigen Adjektive durchfĂŒhrt. FĂŒr jeden Text im Korpus sammelt sie alle Adjektive, die das Zielsubstantiv ĂŒber die Relation ânkâ nĂ€her bestimmen, und zĂ€hlt deren HĂ€ufigkeit. Das Ergebnis ist eine Tabelle, die fĂŒr jeden Text festhĂ€lt, wie oft jedes beobachtete Adjektiv mit dem Zielsubstantiv auftritt â die Grundlage fĂŒr die spĂ€tere Analyse der HĂ€ufigkeiten im Zeitverlauf.
def extract_dependent_adjective_list(spacy_docs: Dict, metadata_df: pd.DataFrame,
noun_input: Union[str, List[str]], top_n: int = 10) -> Tuple[pd.DataFrame, List[str]]:
"""
Extract adjective modifiers (nk) for a noun or list of nouns and track their frequency over time.
Parameters:
-----------
spacy_docs : dict
Dictionary with file_ids as keys and spaCy Doc objects as values
metadata_df : pd.DataFrame
DataFrame with columns: 'lastname', 'firstname', 'DC.title', 'year', 'volume', 'ID', 'decade'
noun_input : str or list of str
Single noun lemma (e.g., 'liebe') or list of noun lemmata (e.g., ['liebe', 'leidenschaft'])
top_n : int
Size of the returned top_adjectives list (default: 10).
The returned DataFrame always contains ALL observed adjectives â top_n only
controls the convenience list returned alongside it.
Returns:
--------
tuple : (pd.DataFrame, list)
- DataFrame with columns: filename, DC.title, year, adjective, count, noun_count
(one row per (file, observed adjective) for every file in which the target
noun appears at least once; count may be 0)
- List of the top N most frequent adjectives across the corpus
"""
# Convert single noun to list for uniform processing
if isinstance(noun_input, str):
noun_list = [noun_input]
else:
noun_list = noun_input
# Convert to lowercase for case-insensitive matching
noun_list_lower = [noun.lower() for noun in noun_list]
# Single token-scanning pass: for each metadata-matched doc, compute its
# per-document adjective counts once and simultaneously accumulate them into
# the corpus-wide counter. (Previously this token walk was done twice â once
# globally, once per document â which doubled the expensive spaCy work.)
# Building the global vocabulary only from metadata-matched docs also avoids
# phantom always-zero adjective columns from docs that are dropped below.
all_adjectives = Counter()
per_doc = [] # (file_id, meta_row, adjective_counts, noun_count)
for file_id, doc in spacy_docs.items():
# Get metadata for this file
meta_row = metadata_df[metadata_df['DC.identifier'] == file_id]
if meta_row.empty:
continue
# Count adjectives modifying the target nouns in this document
adjective_counts = Counter()
noun_count = 0
for token in doc:
# Check if this token is one of our target nouns
if token.lemma_.lower() in noun_list_lower and token.pos_ == 'NOUN':
noun_count += 1
# Find adjective modifiers (nk dependency)
for child in token.children:
if child.dep_ == 'nk' and child.pos_ == 'ADJ':
adjective_counts[child.lemma_.lower()] += 1
all_adjectives.update(adjective_counts)
per_doc.append((file_id, meta_row, adjective_counts, noun_count))
# Full set of adjectives observed in the corpus (no cutoff â used to populate the DataFrame)
all_observed_adjectives = list(all_adjectives.keys())
# Top N most frequent â returned as a convenience list for downstream filtering
top_adjectives = [adj for adj, count in all_adjectives.most_common(top_n)]
# Densify: emit one row per (document, observed adjective). No spaCy work here â
# we only read the cached per-document counts computed in the pass above.
results = []
for file_id, meta_row, adjective_counts, noun_count in per_doc:
# Create a row for each observed adjective in the corpus when the noun
# appears in this document (count may be 0 â needed by downstream
# time-series averaging, which divides by the number of texts in a year).
for adjective in all_observed_adjectives:
count = adjective_counts.get(adjective, 0)
if noun_count > 0 or count > 0: # Include if we have nouns or this adjective
results.append({
'filename': file_id,
'DC.title': meta_row['DC.title'].values[0],
'DC.date': meta_row['DC.date'].dt.year.values[0], #['DC.date'].values[0],
'year': meta_row['year'].values[0],
'adjective': adjective,
'count': count,
'noun_count': noun_count
})
return pd.DataFrame(results), top_adjectives
Die obige Funktion lĂ€sst sich mit einem einzelnen Substantiv oder einer Liste von Substantiven verwenden. In diesem Beispiel nutzen wir lediglich das einzelne Substantiv âLuftâ:
noun = "Luft"
Wenn Sie dieses Notebook jedoch lokal verwenden, können Sie die Variable fĂŒr das Substantiv neu definieren, zum Beispiel so:
noun = "Nebel"
oder so:
noun = ["Luft", "Nebel", "Rauch"]
In der folgenden Zelle fĂŒhren wir die zuvor definierte Funktion extract_dependent_adjective_list aus und extrahieren die vom ausgewĂ€hlten Substantiv abhĂ€ngigen Adjektive (aus dem Korpus âStichprobe 1â). Wir erhalten eine Liste (einen DataFrame, adj_df), in der fĂŒr jedes Buch die HĂ€ufigkeit derjenigen Adjektive aufgefĂŒhrt ist, die in unserem Korpus gemeinsam mit dem Substantiv ein syntaktisches Bigramm bilden. Zudem erhalten wir eine Gesamtliste (top_adjs) der am hĂ€ufigsten vorkommenden Adjektive dieser Art fĂŒr das gesamte Korpus.
adj_df, top_adjs = extract_dependent_adjective_list(annotated_docs,
metadata_df,
noun,
top_n=10)
Nun fĂŒhren wir denselben Vorgang fĂŒr âStichprobe 2â durch.
adj_df_alt, top_adjs_alt = extract_dependent_adjective_list(annotated_docs,
metadata_df_alt,
noun,
top_n=10)
Ergebnisse der Extraktion syntaktischer N-Gramme#
Nun liegen fĂŒr jede Stichprobe zwei Arten von Ergebnissen vor. ZunĂ€chst die Liste der top_n hĂ€ufigsten Adjektive in der jeweiligen Stichprobe:
Sample 1:
# Print the top adjectives
for adj in top_adjs:
print('* ', adj)
* frisch
* frei
* rein
* blau
* kalt
* leer
* klar
* still
* warm
* heiĂ
Sample 2:
# Print the top adjectives (alternative sample)
for adj in top_adjs_alt:
print('* ', adj)
* frisch
* frei
* rein
* blau
* klar
* leer
* kalt
* warm
* still
* heiĂ
Zwischenergebnis#
Hier lĂ€sst sich bereits erkennen, dass die beiden Stichproben recht Ă€hnliche Ergebnisse liefern. So enthalten beispielsweise die beiden Listen der zehn hĂ€ufigsten Wörter dieselben Begriffe, wobei sich lediglich die Reihenfolge geringfĂŒgig unterscheidet. Wir werden im Weiteren analysieren, wie stark die HĂ€ufigkeiten jener Wörter variieren, die in dieser Rangliste unterschiedliche PlĂ€tze belegen.
Die folgenden Tabellen enthalten die HĂ€ufigkeiten dieser Adjektive (und des/der Zielsubstantiv(e) â siehe Spalte noun_count) in den einzelnen BĂŒchern â jeweils eine pro Stichprobe:
Sample 1:
show(adj_df)
| Loading ITables v2.6.1 from the internet... (need help?) |
Sample 2:
show(adj_df_alt)
| Loading ITables v2.6.1 from the internet... (need help?) |
Speichern der Ergebnisse als Dateien#
Die Ergebnisse einer solchen Extraktion stellen bereits ein gewisses Zwischenergebnis der Forschung dar. Speichern wir sie als Dateien:
# Create intermediate directory
interm_data_synt_ngrams_dir = Path("interm_data_synt_ngrams")
interm_data_synt_ngrams_dir.mkdir(parents=True, exist_ok=True)
# Save adj_df to CSV
adj_df.to_csv(interm_data_synt_ngrams_dir / "adj_df_luft.csv", index=False)
print(f"Saved adj_df to interm_data_synt_ngrams/adj_df_luft.csv ({len(adj_df)} rows)")
# Save adj_df_alt to CSV
adj_df_alt.to_csv(interm_data_synt_ngrams_dir / "adj_df_alt_luft.csv", index=False)
print(f"Saved adj_df_alt to interm_data_synt_ngrams/adj_df_alt_luft.csv ({len(adj_df_alt)} rows)")
Laden gespeicherter Ergebnisse (Optional)#
Falls die Ergebnisse bereits extrahiert und als CSV gespeichert wurden, können sie hier geladen werden, anstatt die Extraktion erneut durchzufĂŒhren:
# Optional: Load previously saved results from CSV
# Load adj_df
adj_df = pd.read_csv(interm_data_synt_ngrams_dir / "adj_df_luft.csv")
adj_df['DC.date'] = pd.to_datetime(adj_df['DC.date'], format="%Y")
# Load adj_df_alt
adj_df_alt = pd.read_csv(interm_data_synt_ngrams_dir / "adj_df_alt_luft.csv")
adj_df_alt['DC.date'] = pd.to_datetime(adj_df_alt['DC.date'], format="%Y")
# Recreate top_adjs lists from the dataframes
top_adjs = adj_df.groupby('adjective')['count'].sum().sort_values(ascending=False).head(10).index.tolist()
top_adjs_alt = adj_df_alt.groupby('adjective')['count'].sum().sort_values(ascending=False).head(10).index.tolist()
5.5.7. Analyse und Visualisierung#
Korpusweite HĂ€ufigkeiten adjektivischer Modifikatoren#
ZunĂ€chst betrachten wir die Gesamtverteilung der adjektivischen Modifikatoren. Bevor zeitliche Entwicklungen analysiert werden, ist es sinnvoll, einen Ăberblick darĂŒber zu gewinnen, welche Adjektive im gesamten Korpus am hĂ€ufigsten als syntaktische Modifikatoren der untersuchten Substantive auftreten. Diese aggregierte Betrachtung erlaubt es, dominante Beschreibungs- und Bewertungsmuster zu identifizieren und dient zugleich als Ausgangspunkt fĂŒr die nachfolgenden diachronen Analysen.
Sample 1:#
totals = plot_top_adjective_ranking(adj_df, top_n=20, noun_input=noun, metric="count")
show_top_adjectives_table(adj_df)
Sample 2:#
totals_alt = plot_top_adjective_ranking(adj_df_alt, top_n=20, noun_input=noun, metric="count")
show_top_adjectives_table(adj_df_alt)
Hier zeigt sich erneut, dass sich die Ergebnisse der HĂ€ufigkeitsverteilung zwischen den beiden Stichproben nicht wesentlich unterscheiden â oder dass zumindest die allgemeine Rangfolge der Adjektive Ă€hnlich ist. âFrischâ steht eindeutig an der Spitze, und âFreiâ ist in beiden Korpora zweifellos das zweithĂ€ufigste Adjektiv. Die ĂŒbrigen Adjektive weisen deutlich geringere HĂ€ufigkeiten auf, und auch die Unterschiede zwischen ihren HĂ€ufigkeitswerten sind weitaus geringer.
Diachrone Analyse adjektivisch-substantivischer Konstruktionen#
Im nĂ€chsten Schritt wird die Analyse um eine diachrone Perspektive erweitert. Anstatt ausschlieĂlich korpusweite GesamthĂ€ufigkeiten zu betrachten, wird nun untersucht, wie sich die Verwendung der zuvor identifizierten adjektivischen Modifikatoren im Zeitverlauf entwickelt. Die zeitliche Aggregation erlaubt es, Verschiebungen in Beschreibungs- und Bewertungsmustern nachzuzeichnen und diese mit historischen Prozessen in Beziehung zu setzen.
Sample 1#
# yearly lineplots + moving average
yearly_1, moving_1 = plot_adjective_trends_moving_avg_plotly(
adj_df, top_adjs, noun_input=noun, window_years=10, n_plot=8
)
Sample 2:#
# NEW: yearly lineplots + moving average
yearly_2, moving_2 = plot_adjective_trends_moving_avg_plotly(
adj_df_alt, top_adjs_alt, noun_input=noun, window_years=10, n_plot=8
)
AusreiĂertexte und Kontextanalyse#
Welche konkreten Texte sind fĂŒr die AusschlĂ€ge in den Zeitreihen verantwortlich?
Um diese Frage transparent zu beantworten, identifizieren wir sogenannte âAusreiĂertexteâ (aber nicht ĂŒber einen komplexen statistischen Test, sondern ĂŒber eine nachvollziehbare ZĂ€hl- und Ranking-Logik). FĂŒr jedes Werk zĂ€hlen wir (1) noun_count, also wie oft das Lemma Luft im Text vorkommt, und (2) count, also wie oft ein bestimmtes Adjektiv (z.B. frisch) in der Dependenzannotation als attributiver Modifikator von Luft erscheint (ADJ â Luft, z.B. frische Luft). Daraus berechnen wir pro Text eine normalisierte Kennzahl rel_freq = (count / noun_count) * 100, also den Anteil der Luft-Vorkommen, die mit diesem Adjektiv modifiziert werden.
AnschlieĂend bestimmen wir fĂŒr jedes Adjektiv zunĂ€chst die Dekaden, in denen der Durchschnitt dieser relativen HĂ€ufigkeit besonders hoch ist (Peak-Dekaden), und listen innerhalb dieser Zeitfenster die Texte mit den höchsten rel_freq-Werten (Top-k) als âAusreiĂertexteâ auf. Um instabile Extremwerte zu vermeiden, filtern wir Texte mit sehr wenigen Luft-Belegen (z.B. noun_count < 5) aus. ErgĂ€nzend berechnen wir pro Dekade den Anteil eines einzelnen Textes an allen AdjektivâLuft-Treffern (share_of_adj_count_in_decade), um sichtbar zu machen, ob ein Ausschlag der Zeitreihe vor allem von wenigen Werken getragen wird. Diese identifizierten Texte werden anschlieĂend mithilfe von KWIC- bzw. Satzkontexten qualitativ ĂŒberprĂŒft, um die semantische Funktion der jeweiligen Konstruktionen genauer einzuordnen.
metadata_df.columns
Index(['lastname', 'firstname', 'DC.title', 'DC.date', 'year', 'volume',
'DC.identifier', 'decade'],
dtype='object')
Die folgende Tabelle listet die identifizierten AusreiĂertexte fĂŒr die jeweiligen AdjektivâNomen-Konstruktionen auf, also jene Werke, die maĂgeblich zu den beobachteten AusschlĂ€gen in den Zeitreihen beitragen (in Sample 1).
show(outliers_table_1)
| Loading ITables v2.6.1 from the internet... (need help?) |
Die KWIC-Ansicht zeigt ausgewĂ€hlte Satzkontexte der AdjektivâNomen-Konstruktionen aus diesen AusreiĂertexten und ermöglicht eine qualitative Einordnung ihrer semantischen Verwendung.
show(kwic_table_1)
| Loading ITables v2.6.1 from the internet... (need help?) |
Die folgende Tabelle listet die identifizierten AusreiĂertexte fĂŒr die jeweiligen AdjektivâNomen-Konstruktionen auf, also jene Werke, die maĂgeblich zu den beobachteten AusschlĂ€gen in den Zeitreihen beitragen (in Sample 2).
show(outliers_table_2)
| Loading ITables v2.6.1 from the internet... (need help?) |
Die KWIC-Ansicht zeigt ausgewĂ€hlte Satzkontexte der AdjektivâNomen-Konstruktionen aus diesen AusreiĂertexten und ermöglicht eine qualitative Einordnung ihrer semantischen Verwendung.
show(kwic_table_2)
| Loading ITables v2.6.1 from the internet... (need help?) |
5.5.8. Diskussion der syntaktischen N-Gramm-Analyse#
Die syntaktische N-Gramm-Analyse verschiebt den Blick von der bloĂen HĂ€ufigkeit des Begriffs Luft hin zur Frage, wie ĂŒber Luft gesprochen wird â also welche Adjektive als Modifikatoren in Konstruktionen wie frische Luft, reine Luft oder freie Luft auftreten. Ăber beide Stichproben hinweg zeigt sich, dass einige Modifikatoren sehr stabil und dominant sind â insbesondere frisch (und in Teilen auch frei).
Offensichtlich spiegeln sich in dieser Liste der Adjektive keinerlei Anzeichen fĂŒr schlechte LuftqualitĂ€t wider. Vielmehr deutet die Dominanz positiv konnotierter Adjektive wie frisch, rein und frei darauf hin, dass die Literatur eher in jene Richtung reagiert, die wir in der Operationalisierung als utopischen Gegenentwurf beschrieben haben â mit einer herausgehoben positiven Semantik von âLuftâ.