3.4. 🚀 Auswahl des Forschungskorpus#

Diese Fallstudie untersucht, wie deutschsprachige literarische Texte des 19. Jahrhunderts die abnehmende LuftqualitÀt reflektieren und diskursivieren.
Ein zentraler Schritt besteht darin, ein geeignetes Forschungskorpus auszuwÀhlen, das den historischen Zeitraum und die thematische Breite unserer Forschungsfrage abdeckt.


3.4.1. Vom Aufbau zur Auswahl#

Da bereits digitale Korpora deutschsprachiger Prosa vorliegen, stehen wir nicht vor der Aufgabe, Texte selbst zu digitalisieren, sondern mĂŒssen reflektiert entscheiden, welches existierende Korpus fĂŒr unsere Forschungsfrage geeignet ist. Die im Kapitel Korpora als Forschungsobjekte beschriebenen Strategien – VollstĂ€ndigkeit, ReprĂ€sentativitĂ€t, Balance und Opportunismus – bilden dabei unseren Bewertungsrahmen (Schöch, 2017).

Was wenn noch kein geeignetes Korpus digital vorliegt?

Wenn Ihre Forschungsobjekte noch nicht als Text sondern als Bilddigitalisate, muss der Text z.B. mittels Optical Character Recognition (OCR) extrahiert werden. Wie das geht zeigen wir an Hand von Zeitungsdigitalisaten in der Quadriga-Fallstudie ”Quantitative Analyse der Medienwellen der Spanischen Grippe (1918/19)”


3.4.2. Vorhandene Korpora deutschsprachiger Prosa#

Wir stellen drei frei verfĂŒgbare Korpora vor, die sich fĂŒr literaturwissenschaftliche Analysen deutscher Prosa eignen. Konkret werden das d-Prose-Korpus, das Corpus of German-Language Fiction sowie das German ELTeC-Korpus herangezogen.

Korpus

Beschreibung

Zeitraum

Format

Auswahlstrategie

StÀrken

SchwÀchen

d-Prose 1870–1920 (Zenodo)

ca. 150 Werke, TEI/XML, kuratiert

1870–1920

TEI/XML

reprÀsentativ

gute Metadaten, literaturwissenschaftlich gepflegt

begrenzter Zeitraum

Corpus of German-Language Fiction (Figshare)

ca. 2 700 Werke in Plain Text mit Metadaten

1510–1950

TXT

opportunistisch

großer Umfang, gute zeitliche Abdeckung

uneinheitliche Metadaten, OCR-Fehler

ELTeC-German (Zenodo)

ca. 100 Werke, nach ELTeC-Samplingprotokoll

1840–1920

TEI/XML

balanciert

methodisch solide, Gender-Balance

relativ klein, LĂŒcken vor 1840

Hinweis

Bereits die Übersicht zeigt, dass kein Korpus „perfekt“ ist. Die Entscheidung fĂŒr ein Korpus hĂ€ngt immer vom Zusammenspiel zwischen Forschungsfrage, zeitlicher Abdeckung, DatenqualitĂ€t und praktischer ZugĂ€nglichkeit ab.


3.4.3. Explorative Analyse der Metadaten#

Um die Eignung der Korpora genauer zu prĂŒfen, untersuchen wir zunĂ€chst ihre Metadaten. Ziel ist es, ein erstes GefĂŒhl fĂŒr die zeitliche Verteilung, VollstĂ€ndigkeit und Struktur der Daten zu gewinnen. Um das zu erreichen, mĂŒssen wir mithilfe von Code einige Metadaten von Korpora analysieren. Dieser Code wird unten als ausfĂŒhrbares Notebook prĂ€sentiert.

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 Auswahl eines geeigneten Forschungskorpus auf Basis von Metadaten vorgenommen. Ziel dieses Schrittes ist es, vor der eigentlichen Textanalyse zu prĂŒfen, welche Korpora fĂŒr die vorliegende Forschungsfrage geeignet sind und welche EinschrĂ€nkungen sie mit sich bringen.

Im Fokus stehen dabei zeitliche Abdeckung, Verteilung der Texte und strukturelle Eigenschaften verschiedener Korpora deutschsprachiger Prosa. Die Analyse dient nicht der inhaltlichen Interpretation einzelner Texte, sondern der methodisch reflektierten Korpusentscheidung als Grundlage fĂŒr die spĂ€teren Analyseschritte.

Dazu werden die folgenden Schritte durchgefĂŒhrt:

  1. Vorstellung und Einordnung mehrerer verfĂŒgbarer Korpora

  2. Einlesen und Aufbereitung der zugehörigen Metadaten

  3. Explorative Analyse der zeitlichen Verteilung der Texte

  4. Vergleich der Korpora hinsichtlich Abdeckung und Balance

  5. BegrĂŒndete Auswahl eines Korpus fĂŒr die weitere Analyse

Hide code cell content

! pip install pandas plotly itables

Hide code cell content

import sys
from pathlib import Path
import pandas as pd
import plotly.express as px
import plotly.io as pio
from itables import show

if 'google.colab' in sys.modules:
  pio.renderers.default = "colab"
else:
  pio.renderers.default = "notebook"

3.4.4. Option 1. ELTeC-DEU corpus#

Einlesen der Korpusmetadaten in Python#

Hide code cell source

eltec_published_url = "https://zenodo.org/records/4662482/files/metadata.csv"
eltec_local_copy = Path("../metadata/metadata_eltec_deu.csv")

try:
    meta = pd.read_csv(eltec_published_url)
except Exception as e:
    print(f"Konnte Metadaten nicht von Zenodo laden. Nutze lokale Kopie: {eltec_local_copy}. Das Problem war: {e}")
    meta = pd.read_csv(eltec_local_copy)

show(meta)
Loading ITables v2.6.1 from the internet... (need help?)

Analyse der zeitlichen Verteilung des Korpus#

Pro Jahr

Hide code cell content

# Anzahl der Texte pro Jahr

def summarize_texts_per_year(df, year_column):
    """Return per-yearcounts and summary stats for the given year column."""
    bins = df[year_column].dropna()
    if bins.empty:
        raise ValueError(f"No year values found in column '{year_column}'.")
    bins = bins.astype(int)
    counts = bins.value_counts().sort_index()
    full_index = pd.RangeIndex(counts.index.min(), counts.index.max() + 1)
    counts = counts.reindex(full_index, fill_value=0)
    stats = counts.agg(['mean', 'max', 'min']).rename({'mean': 'avg_per_year'})
    return counts, stats

year_counts, year_stats = summarize_texts_per_year(meta, 'year')

print("Textanzahl der Texte im ELTEC-DEU pro Jahr:")
print(year_stats)
Textanzahl der Texte im ELTEC-DEU pro Jahr:
avg_per_year    1.234568
max             5.000000
min             0.000000
Name: count, dtype: float64

Hide code cell source

fig_year = px.bar(
    x=year_counts.index,
    y=year_counts.values,
    labels={
        "x": "Jahr",
        "y": "Anzahl Texte"
    },
    title="Zeitliche Verteilung der Texte im ELTEC-DEU pro Jahr"
)

fig_year.update_layout(
    height=350,
    margin=dict(l=40, r=40, t=60, b=40),
    xaxis=dict(
        tickmode="linear",
        tickformat="d",  # ensure clean integer years
        dtick=10 # Display ticks every 10 years
    ),
)

Pro Jahrzehnt

Hide code cell content

# Anzahl der Texte pro Jahrzehnt
def summarize_texts_per_decade(df, decade_column):
    """Return per-yearcounts and summary stats for the given year column."""
    bins = df[decade_column].dropna()
    if bins.empty:
        raise ValueError(f"No year values found in column '{decade_column}'.")
    bins = bins.astype(int)
    counts = bins.value_counts().sort_index()
    stats = counts.agg(['mean', 'max', 'min']).rename({'mean': 'avg_per_year'})
    return counts, stats

meta['decade'] = (meta['year'] // 10) * 10
decade_counts, decade_stats = summarize_texts_per_decade(meta, 'decade')

print("Textanzahl der Texte im ELTEC-DEU pro Jahrzehnt:")
print(decade_stats)
Textanzahl der Texte im ELTEC-DEU pro Jahrzehnt:
avg_per_year    11.111111
max             16.000000
min              1.000000
Name: count, dtype: float64

Hide code cell source

# Visualisierung der Dekadenverteilung (Textanzahl pro Dekade)
fig_decade = px.bar(
    x=decade_counts.index.astype(str),
    y=decade_counts.values,
    labels={
        "x": "Jahrzehnt",
        "y": "Anzahl Texte"
    },
    title="Zeitliche Verteilung der Texte im ELTEC-DEU pro Jahrzehnt"
)

fig_decade.update_layout(
    height=350,
    margin=dict(l=40, r=40, t=60, b=40),
)

3.4.5. Option 2. d-Prose corpus#

Einlesen der Korpusmetadaten in Python#

Hide code cell source

## code to analyse the metadata of the d-Prose corpus

d_prose_published_url = "https://zenodo.org/records/5015008/files/d-prose_V2_norm_year.csv"
d_prose_local_copy = Path("../metadata/metadata_d-prose.csv")

try:
    meta_d_prose = pd.read_csv(d_prose_published_url, sep=';')
except Exception as e:
    print(f"Konnte Metadaten nicht von Zenodo laden. Nutze lokale Kopie: {d_prose_local_copy}. Das Problem war: {e}")
    meta_d_prose = pd.read_csv(d_prose_local_copy, sep=';')

show(meta_d_prose)
Loading ITables v2.6.1 from the internet... (need help?)

Analyse der zeitlichen Verteilung des Korpus#

Pro Jahr (d-Prose)

Hide code cell content

year_counts, year_stats = summarize_texts_per_year(meta_d_prose, 'norm_year')

print("Textanzahl der Texte im d-Prose pro Jahr:")
print(year_stats)
Textanzahl der Texte im d-Prose pro Jahr:
avg_per_year     49.235294
max             136.000000
min               6.000000
Name: count, dtype: float64

Man sieht, dass d-Prose ein wesentlich “dichteres” Korpus ist 


Hide code cell source

# Visualisierung der Jahresverteilung (Textanzahl pro Jahr)

fig_year = px.bar(
    x=year_counts.index,
    y=year_counts.values,
    labels={
        "x": "Jahr",
        "y": "Anzahl Texte"
    },
    title="Zeitliche Verteilung der Texte im d-Prose pro Jahr"
)

fig_year.update_layout(
    height=350,
    margin=dict(l=40, r=40, t=60, b=40),
    xaxis=dict(
        tickmode="linear",
        tickformat="d",  # ensure clean integer years
        dtick=10 # Display ticks every 10 years
    ),
)

Hide code cell content

# Anzahl der Texte pro Jahrzehnt

meta_d_prose['decade'] = (meta_d_prose['norm_year'] // 10) * 10

decade_counts, decade_stats = summarize_texts_per_decade(meta_d_prose, 'decade')

print("Textanzahl der Texte im d-Prose pro Jahrzehnt:")
print(decade_stats)
Textanzahl der Texte im d-Prose pro Jahrzehnt:
avg_per_year    418.5
max             681.0
min             106.0
Name: count, dtype: float64

Hide code cell source

# Visualisierung der Dekadenverteilung (Textanzahl pro Dekade)
fig_decade = px.bar(
    x=decade_counts.index.astype(str),
    y=decade_counts.values,
    labels={
        "x": "Jahrzehnt",
        "y": "Anzahl Texte"
    },
    title="Zeitliche Verteilung der Texte im d-Prose pro Jahrzehnt"
)

fig_decade.update_layout(
    height=350,
    margin=dict(l=40, r=40, t=60, b=40),
)

Man sieht jedoch auch, dass dieses Korpus sehr klein ist und den fĂŒr uns relevanten Zeitraum nicht abdeckt.

3.4.6. Option 3. Corpus of German-Language Fiction#

FĂŒr das ‘’Corpus of German-Language Fiction’’ liegt keine fertige Metadatentabelle vor. SĂ€mtliche Metadaten sind hier in den Dateinamen in relativ standardisierter Form kodiert:

Author_name_-_Text_title_(year).txt

e.g.

Abraham_Manuel_Fröhlich_-_Die_Verschüttung_im_Hauenstein_(1858).txt

Daher werden die Korpusdateien in einem separaten Notebook per RegEx in Metadaten ĂŒberfĂŒhrt. An dieser Stelle arbeiten wir mit den Metadaten, die aus diesem Parsing hervorgehen.

# 🚀 Create metadata directory path
metadata_dir = Path("../metadata")
metadata_dir.mkdir(parents=True, exist_ok=True)

# Download metadata file from GitHub
metadata_file_path = metadata_dir / "metadata_corpus-german_language_fiction.csv"
if not metadata_file_path.exists():
    ! wget https://raw.githubusercontent.com/quadriga-dk/Text-Fallstudie-3/refs/heads/main/metadata/metadata_corpus-german_language_fiction.csv -P ../metadata

Hide code cell source

meta_gfc = pd.read_csv(metadata_file_path)
meta_gfc = meta_gfc[meta_gfc['DC.date'] > 1500] # removing super-old outliers
show(meta_gfc)
Loading ITables v2.6.1 from the internet... (need help?)

Analyse der zeitlichen Verteilung des Korpus#

Textanzahl der Texte im ‘Corpus of German Fiction’ pro Jahr:

Hide code cell content

year_counts, year_stats = summarize_texts_per_year(meta_gfc, 'DC.date')

print("Textanzahl der Texte im 'Corpus of German Fiction' pro Jahr:")
print(year_stats)
Textanzahl der Texte im 'Corpus of German Fiction' pro Jahr:
avg_per_year     5.902808
max             66.000000
min              0.000000
Name: count, dtype: float64

Visualisierung der Verteilung pro Jahr

Hide code cell source

fig_year = px.bar(
    x=year_counts.index,
    y=year_counts.values,
    labels={
        "x": "Jahr",
        "y": "Anzahl Texte"
    },
    title="Zeitliche Verteilung der Texte im 'Corpus of German-Language Fiction' pro Jahr"
)

fig_year.update_layout(
    height=350,
    margin=dict(l=40, r=40, t=60, b=40),
    xaxis=dict(
        tickmode="linear",
        tickformat="d",  # ensure clean integer years
        dtick=50  # Display ticks every 50 years
    )
)

Anzahl der Texte pro Jahrzehnt

Hide code cell content

# Anzahl der Texte pro Jahrzehnt

meta_gfc['decade'] = (meta_gfc['DC.date'] // 10) * 10

decade_counts, decade_stats = summarize_texts_per_decade(meta_gfc, 'decade')

print("Textanzahl der Texte im 'Corpus of German-Language Fiction' pro Jahrzehnt:")
print(decade_stats)
Textanzahl der Texte im 'Corpus of German-Language Fiction' pro Jahrzehnt:
avg_per_year     82.818182
max             380.000000
min               1.000000
Name: count, dtype: float64

Visualisierung der Dekadenverteilung (Textanzahl pro Dekade)

Hide code cell source

# Visualisierung der Dekadenverteilung (Textanzahl pro Dekade)

fig_decade = px.bar(
    x=decade_counts.index.astype(str),
    y=decade_counts.values,
    labels={
        "x": "Jahrzehnt",
        "y": "Anzahl Texte"
    },
    title="Zeitliche Verteilung der Texte im 'Corpus of German-Language Fiction' pro Jahrzehnt"
)

fig_decade.update_layout(
    height=350,
    margin=dict(l=40, r=40, t=60, b=40),
)

3.4.7. Bewertung und Entscheidung#

Die explorative Analyse erlaubt nun eine systematische Bewertung entlang der Kriterien von (Schöch, 2017).

Kriterium

ELTeC-German

d-Prose 1870–1920

Corpus of German Fiction

Zeitliche Abdeckung

mittel

gering

hoch

DatenqualitÀt

hoch

hoch

mittel

ReprÀsentativitÀt

hoch

mittel

mittel

Umfang

klein

mittel

groß

VerfĂŒgbarkeit

sehr gut

gut

gut

Zwischenfazit

Das Corpus of German-Language Fiction bietet die grĂ¶ĂŸte zeitliche Breite und damit die besten Voraussetzungen, um VerĂ€nderungen im sprachlichen Diskurs ĂŒber LuftqualitĂ€t im 19. Jahrhundert zu untersuchen.


Man erkennt, dass das Corpus of German-Language Fiction einerseits das einzige Korpus ist, das den fĂŒr unsere Forschung notwendigen Zeitraum abdeckt. Andererseits ist es – wie die Verteilung zeigt – eindeutig nicht balanciert. In den Metadaten oder in der Korpusbeschreibung gibt es zudem keinerlei Hinweise darauf, dass dieses Korpus als reprĂ€sentativ angelegt wurde.

FĂŒr die weiteren Analysen mĂŒssen wir das Korpus daher filtern. Dies erfolgt im nĂ€chsten Abschnitt.