Analyse 1: Diachrone Frequenzdiagramme #

Jetzt werden wir die CONLL-Daten nehmen und einige Analysen durchführen. Unser erster Schritt wird sein, die Wort-/Lemma-Häufigkeiten für die Monate des Jahres 1918 zu plotten und zu sehen, ob sie mit den Wellen der Grippepandemie korrelieren.

0. Importe und Daten-Upload #

Hide code cell content
import pandas as pd
import re
from pathlib import Path
## for interactivity in jupyter books
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS, TextInput, Div, RadioButtonGroup
# Ensure Bokeh output is displayed in the notebook
output_notebook()
Loading BokehJS ...
conllfiles = Path(r"../data/csv")
corpus_annotations = {}
for file in conllfiles.iterdir():
    if file.suffix == '.csv':
        #path = os.path.join(conllfiles, filename)  
        data = pd.read_csv(file) 
        corpus_annotations[file.name] = data
corpus_metadata = pd.read_csv(Path('../data/metadata/QUADRIGA_FS-Text-01_Data01_Corpus-Table.csv'), sep=';')
corpus_metadata = corpus_metadata.set_index('DC.identifier')
corpus_metadata
DC.publisher DC.date DC.source
DC.identifier
SNP2719372X-19180101-0-0-0-0 Berliner Morgenpost 1918-01-01 https://content.staatsbibliothek-berlin.de/zef...
SNP2719372X-19180102-0-0-0-0 Berliner Morgenpost 1918-01-02 https://content.staatsbibliothek-berlin.de/zef...
SNP2719372X-19180103-0-0-0-0 Berliner Morgenpost 1918-01-03 https://content.staatsbibliothek-berlin.de/zef...
SNP2719372X-19180104-0-0-0-0 Berliner Morgenpost 1918-01-04 https://content.staatsbibliothek-berlin.de/zef...
SNP2719372X-19180105-0-0-0-0 Berliner Morgenpost 1918-01-05 https://content.staatsbibliothek-berlin.de/zef...
... ... ... ...
SNP27112366-19191224-0-0-0-0 Vossische Zeitung 1919-12-24 https://content.staatsbibliothek-berlin.de/zef...
SNP27112366-19191227-0-0-0-0 Vossische Zeitung 1919-12-27 https://content.staatsbibliothek-berlin.de/zef...
SNP27112366-19191229-0-0-0-0 Vossische Zeitung 1919-12-29 https://content.staatsbibliothek-berlin.de/zef...
SNP27112366-19191230-0-0-0-0 Vossische Zeitung 1919-12-30 https://content.staatsbibliothek-berlin.de/zef...
SNP27112366-19191231-0-0-0-0 Vossische Zeitung 1919-12-31 https://content.staatsbibliothek-berlin.de/zef...

1328 rows × 3 columns

1. Suche nach einem Lemma und plotte die Häufigkeit #

class WordSearchEngine:
    
    def __init__(self, corpus_annotated, corpus_metadata, granularity_parameter='month'):
        self.granularity_parameter = granularity_parameter
        self.prepare_index_dataframe_for_search(corpus_annotated, corpus_metadata)
    
    def prepare_index_dataframe_for_search(self, corpus_annotated, corpus_metadata):
        for filename, annotated_text in corpus_annotated.items():
            txtname = filename.replace('.csv', '')
            if txtname in corpus_metadata.index:
                year, month, day, week, newspaper = self.get_date_and_newsp_from_fname(txtname, corpus_metadata)
                annotated_text['month'] = month
                annotated_text['day'] = day
                annotated_text['week'] = week
                annotated_text['newspaper'] = newspaper
        self.full_df = pd.concat(corpus_annotated.values()) ## a df that contains all words in all newspapers
        
        # for interactive plotting of relative freqs we'll need all these:
        
        #self.monthly_word_counts = self.full_df.groupby('month').count().Token
        #self.weekly_word_counts = self.full_df.groupby('week').count().Token
        #self.daily_word_counts = self.full_df.groupby('day').count().Token

        
        print(f'Searching in a corpus of {self.full_df.shape[0]} word occurences')
        
    def get_date_and_newsp_from_fname(self, txtname, corpus_metadata):  
        date_and_newspaper = corpus_metadata.loc[txtname, ['DC.date', 'DC.publisher']]
        date = date_and_newspaper.iloc[0]
        newspaper = date_and_newspaper.iloc[1]
        date_str = str(date)
        year = date_str[:4]
        month = f"{date_str[:7]}"
        day = date_str
        # week
        date_obj = pd.to_datetime(date_str)
        week_number = f'{year}_week_{date_obj.weekofyear}'
        return year, month, day, week_number, newspaper 
        
    def search_and_plot(self, search_terms, absolute_freqs=True):
        search_terms = search_terms.split(',')
        search_terms = [x.strip() for x in search_terms]
        result = self.full_df.query(f'Lemma.isin({search_terms})')
        
        ## count freqs by different time spans -- we'll need them all for the interactive graph
        
        freqs_month = result.groupby('month').count().Lemma
        grouped_by_week = result.groupby('week')
        freqs_week = grouped_by_week.count().Lemma
        week_dates = grouped_by_week.first()['day']
        freqs_week.index = week_dates
        freqs_week = freqs_week.sort_index()
        freqs_day = result.groupby('day').count().Lemma
        
        self.plot_with_js(freqs_month, freqs_week, freqs_day, search_terms)
        

    def plot_with_js(self, freqs_month, freqs_week, freqs_day, words):
        # Prepare data sources
        daily_source = ColumnDataSource(data=dict(x=pd.to_datetime(freqs_day.index), y=list(freqs_day)))
        weekly_source = ColumnDataSource(data=dict(x=pd.to_datetime(freqs_week.index), y=list(freqs_week)))
        monthly_source = ColumnDataSource(data=dict(x=pd.to_datetime(freqs_month.index), y=list(freqs_month)))
        starter_source = ColumnDataSource(data=dict(x=pd.to_datetime(freqs_month.index), y=list(freqs_month)))
        #print('weeks x:\n', pd.to_datetime(freqs_week.index))
        #print('weeks y:\n', list(freqs_week))

        # Create a plot
        p = figure(title=f"Frequency of words {words}", x_axis_type="datetime", x_axis_label='Time', 
                   y_axis_label='Frequency', width=700, height=400)
        line = p.line('x', 'y', source=starter_source, line_width=2, color='blue')

        # Callback to update the data based on selected mode
        callback = CustomJS(args=dict(line=line, daily_source=daily_source, 
                                      weekly_source=weekly_source, monthly_source=monthly_source),
                            code="""
            const mode = cb_obj.active;
            if (mode === 2) {
                line.data_source.data = daily_source.data;
            } else if (mode === 1) {
                line.data_source.data = weekly_source.data;
            } else if (mode === 0) {
                line.data_source.data = monthly_source.data;
            }
            line.data_source.change.emit();
        """)

        # RadioButtonGroup to select mode
        radio_button_group = RadioButtonGroup(labels=["Monthly", "Weekly", "Daily"], active=0)
        radio_button_group.js_on_change('active', callback)

        # Layout the RadioButtonGroup and plot
        layout = column(radio_button_group, p)
        show(layout)
search_terms = TextInput(value='Grippe, Krankheit', 
                                 title="Geben Sie die zu suchenden Wörter ein und trennen Sie sie durch Kommas, wenn es mehrere sind:") #input('Insert words to search, split by comma if more than one: ')

search_terms_str = search_terms.value.strip()
# JavaScript callback to update the in Jupyter Book
rewrite_var_after_input = CustomJS(args=dict(text_input=search_terms), code="""
    var word = text_input.value.trim();
    console.log('Input value:', word);
    function sendToPython(){
    var kernel = IPython.notebook.kernel;
    kernel.execute("search_terms_str = '" + word + "'");
    }
    sendToPython();
""")



search_terms.js_on_change('value', rewrite_var_after_input)

# Layout and display
layout = column(search_terms)

show(layout)
engine = WordSearchEngine(corpus_annotations, corpus_metadata)
Searching in a corpus of 33192061 word occurences
engine.search_and_plot(search_terms_str)

3. Diskussion des Zwischenergebnisses#

Ist dieses Ergebnis sinnvoll und spiegelt es tatsächlich etwas wider? Eine Möglichkeit, dies zu überprüfen, besteht darin, unser Diagramm mit den tatsächlichen Daten über die Intensität der Pandemie zu vergleichen.

In (Taubenberger, J. K., & Morens, D. M. (2006). 1918 Influenza: the Mother of All Pandemics. Emerging Infectious Diseases, 12(1), 15-22. https://doi.org/10.3201/eid1201.050979) wird festgestellt, dass ‘The first pandemic influenza wave appeared in the spring of 1918, followed in rapid succession by much more fatal second and third waves in the fall and winter of 1918–1919, respectively’(‘Die erste Pandemie-Influenza-Welle im Frühjahr 1918 auftrat, gefolgt von weitaus tödlicheren zweiten und dritten Wellen im Herbst und Winter 1918–1919’). Sie ergänzen diese Aussage auch mit einem Diagramm aus einem früheren Papier (Jordan E. (1927). Epidemic influenza: a survey. Chicago: American Medical Association):

https://wwwnc.cdc.gov/eid/images/05-0979-F1.gif

Unsere zwei Wellen von Erwähnungen des Wortes ‘Grippe’ scheinen den Sterblichkeitszahlen zu entsprechen, was darauf hindeuten könnte, dass die Methode, obwohl sehr einfach, funktioniert und dass historische Ereignisse manchmal in Wortfrequenzzählungen reflektiert werden können… Die dritte Welle scheint nicht reproduziert zu werden, was eine weitere Untersuchung erfordert. Eine Hypothese könnte sein, dass, ähnlich wie bei der COVID-Pandemie, neue Krankheitswellen irgendwann aufhören, die Aufmerksamkeit der Öffentlichkeit zu erregen. Beispielsweise waren die COVID-Wellen im Jahr 2021 stärker als die im Jahr 2020, aber die Berichterstattung in den Nachrichten nahm bereits ab. Dies könnte besonders für Anfang 1919 zutreffen, als nach dem Verlust des Krieges und der Revolution von 1918 Grippetodesfälle kein Nachrichtenthema mehr waren.