Andreas Warntjen
21.2.2021
Mit Text Mining-Methoden lassen sich Informationen aus großen Textmengen generieren, etwa zu Themenschwerpunkten oder der Stimmung (sentiment analysis). Beispiele wären die Vorsortierung von Emails an ein Kundenservice-Center nach Thema (Beschwerde, Nachfrage zu einem Produkt, etc.) oder die Auswertung von Produktrezensionen (wie positiv wird das Produkt bewertet?). Da es sich bei Texten um unstrukturierte Daten handelt, sind für das Text Mining besondere Schritte bei der Datenaufbereitung notwendig. Die Datenaufbereitung umfasst typischerweise folgende Schritte:
Das folgende Beispiel analysiert die Antrittsreden (inaugural address) amerikanischer Präsidenten von 1789 (George Washington) bis 2017 (Trump). Der Fokus liegt auf der Datenaufbereitung, deshalb besteht die Analyse selber aus einem einfachen Vergleich von absoluten Häufigkeiten. Die Fragestellung ist, wie häufig in den Antrittsreden von Amerika bzw. der Verfassung die Rede ist.
Die Analyse erfolgt in Python mit Hilfe der Bibliothek NLTK.
Zunächst importieren wir die Bibliothekten nltk, re (für reguläre Ausdrücke), und pandas (für die Analyse). Die Bibliothek nltk beinhaltet eine Reihe von Beispieldatensätzen, darunter die Antrittsreden von US-Präsidenten (inaugural), die wir benutzen.
## regular expressions
import re
## data and text mining functionality
import nltk
# data
from nltk.corpus import inaugural
# list of stopwords
from nltk.corpus import stopwords
# tokenizer
from nltk.tokenize import word_tokenize
# Lemmatizer/Stemmer
from nltk.stem import WordNetLemmatizer
from nltk.stem import PorterStemmer
from nltk.stem import SnowballStemmer
## Data analysis and visualization
import pandas as pd
Texte sind typischerweise in einem Korpus zusammenhängender Texte organisiert. In unserem Beispiel also alle Antrittsreden von US-Präsidenten. Die einzelnen Texte sind als Textdateien abgelegt. Die Dateinamen enthalten Meta-Daten, nämlich das Jahr und den Nachnamen des Präsidenten, die wir auslesen möchten.
Auf die im Textkorpus inaugural enthaltenen Texte (bzw. Dateinamen) können wir mit der Methode fileids() zugreifen. Wir speichern diese Liste aller Antrittsreden in speeches.
speeches = inaugural.fileids()
speeches
['1789-Washington.txt', '1793-Washington.txt', '1797-Adams.txt', '1801-Jefferson.txt', '1805-Jefferson.txt', '1809-Madison.txt', '1813-Madison.txt', '1817-Monroe.txt', '1821-Monroe.txt', '1825-Adams.txt', '1829-Jackson.txt', '1833-Jackson.txt', '1837-VanBuren.txt', '1841-Harrison.txt', '1845-Polk.txt', '1849-Taylor.txt', '1853-Pierce.txt', '1857-Buchanan.txt', '1861-Lincoln.txt', '1865-Lincoln.txt', '1869-Grant.txt', '1873-Grant.txt', '1877-Hayes.txt', '1881-Garfield.txt', '1885-Cleveland.txt', '1889-Harrison.txt', '1893-Cleveland.txt', '1897-McKinley.txt', '1901-McKinley.txt', '1905-Roosevelt.txt', '1909-Taft.txt', '1913-Wilson.txt', '1917-Wilson.txt', '1921-Harding.txt', '1925-Coolidge.txt', '1929-Hoover.txt', '1933-Roosevelt.txt', '1937-Roosevelt.txt', '1941-Roosevelt.txt', '1945-Roosevelt.txt', '1949-Truman.txt', '1953-Eisenhower.txt', '1957-Eisenhower.txt', '1961-Kennedy.txt', '1965-Johnson.txt', '1969-Nixon.txt', '1973-Nixon.txt', '1977-Carter.txt', '1981-Reagan.txt', '1985-Reagan.txt', '1989-Bush.txt', '1993-Clinton.txt', '1997-Clinton.txt', '2001-Bush.txt', '2005-Bush.txt', '2009-Obama.txt', '2013-Obama.txt', '2017-Trump.txt']
In den Dateinamen ist das Jahr der Rede und der Name des Redners/Präsidenten enthalten. Diese Teile extrahieren wir mit regulären Ausdrücken.
Reguläre Ausdrücke (regular expressions) identifizieren Muster in Zeichenketten. Mit regulären Ausdrücken kann man nicht nur direkt nach einer bestimmten Zeichenfolge suchen (etwa "Obama"), sondern auch nach abstrakteren Mustern (4 Zahlen am Anfang eines Zeichenkette). In unserem Beispiel suchen wir nach einer vierstelligen Jahreszahl und wir könnten einfach die ersten vier Zeichen der Dateinamen nehmen. Ein etwas spezifischeres Muster ist es eine Zeichenkette zwischen bestimmten Zeichen zu suchen. Hier also etwa vier Zahlen (oder digits: "\d") vor einem Bindestrich. Wir extrahieren diese mit regulären Ausdrücken mit Hilfe der Funktion search() aus der Bibliothek re. Der erste Parameter ist der reguläre Ausdruck, der zweite der Text auf den es angewendet werden soll. "\d{4}" bedeutet, dass wir viermal eine Zahl (digit) suchen. Die Angabe "4" in eckigen Klammern ist ein quantifier: wie häufig soll dieser Ausdruck vorkommen (einmal? beliebig oft? eine bestimmte Anzahl?). Dieser gesamte Teil des regulären Ausdruck ist in runden Klammern und bildet damit eine Gruppe, die wir in einem nächsten Schritt mit group() extrahieren können. Prinzipiell kann man mehrere Bestandteile gleichzeitig extrahieren (mehrere jeweils geklammerte Teile eines regulären Ausdrucks), die dann mit der Nummer der Gruppe identifiziert werden müsste. Hier haben wir nur eine Gruppe (vier Zahlen).
Wir wollen die Jahreszahl nicht nur aus einer Zeichenkette (also einem Dateinamen) extrahieren wollen, sondern aus allen. Dazu könnte man eine For-Schleife benutzen. In Python gibt es das spezielle Konstrukt list comprehensions, die die gleiche Funktionalität liefern. Eine Funktion (hier: Extraktion eines Teils eines Zeichenkette mit re.search()) wird einzeln auf alle Elemente einer Liste angewandt und das Ergebnis wird als Liste zurückgelieft. Wir speichern die Liste der Jahreszahlen als _years_lst_.
years_lst = [re.search('(\d{4})-', speech).group(1) for speech in speeches]
years_lst[0:10]
['1789', '1793', '1797', '1801', '1805', '1809', '1813', '1817', '1821', '1825']
Für die Liste der Präsidenten extrahieren wir alle Buchstaben zwischen dem Bindestrich und dem Punkt vor dem Dateipräfix (".txt"). Oben haben wir "\d" genutzt, um nach beliebigen Zahlen (digits) zu suchen. Hier nutzen wir eine Definition der Gruppe von Zeichen nach denen wir suchen, nämlich alle Zeichen von A-Z (groß geschrieben) und a-z (klein geschrieben) - also alle Buchstaben. Im Gegensatz zur Jahreszahl wissen wir nicht, wie viele Buchstaben wir suchen. Die Namen der Präsidenten sind unterschiedlich lang. Deshalb nehmen wir als quantifier "*" (beliebig viele). Die Eingrenzung durch den "." ist hier etwas redundant (der Punkt ist kein Buchstabe). Man kann auf mehreren Webseiten reguläre Ausdrücke ausprobieren (z.B. https://regex101.com/). Das Ergebnis (Nachname der Präsidenten) speichern wir in der Liste _presidents_lst_.
presidents_lst = [re.search('-([A-Za-z]*)\.', speech).group(1) for speech in speeches]
presidents_lst[0:10]
['Washington', 'Washington', 'Adams', 'Jefferson', 'Jefferson', 'Madison', 'Madison', 'Monroe', 'Monroe', 'Adams']
Auf die Rohversion der Reden greifen wir mit der Methode raw(), indem wir den Dateinamen angeben. Wir gucken uns als Beispiel die ersten 500 Zeichen der Antrittsrede von Präsident Obama im Jahr 2013 an.
Obama_2013_raw = inaugural.raw("2013-Obama.txt")
Obama_2013_raw[0:500]
'Thank you. Thank you so much.\n\nVice President Biden, Mr. Chief Justice, Members of the United States Congress, distinguished guests, and fellow citizens:\n\nEach time we gather to inaugurate a President we bear witness to the enduring strength of our Constitution. We affirm the promise of our democracy. We recall that what binds this Nation together is not the colors of our skin or the tenets of our faith or the origins of our names. What makes us exceptionalâ\x80\x94what makes us Americanâ\x80\x94is our alle'
Der Text enthält noch Steuerungszeichen ("\n\n" für einen Zeilenumbruch). Außerdem gibt ein Encoding-Problem ("â\x80\x94" statt " - "). Für diese eine Rede können wir es direkt austauschen.
Obama_2013_str = Obama_2013_raw.replace("â\x80\x94", " - ")
Obama_2013_str[0:500]
'Thank you. Thank you so much.\n\nVice President Biden, Mr. Chief Justice, Members of the United States Congress, distinguished guests, and fellow citizens:\n\nEach time we gather to inaugurate a President we bear witness to the enduring strength of our Constitution. We affirm the promise of our democracy. We recall that what binds this Nation together is not the colors of our skin or the tenets of our faith or the origins of our names. What makes us exceptional - what makes us American - is our alle'
Wir wollen im Folgenden nicht zwischen Groß- und Kleinschreibung unterscheiden - also etwa zwischen "We" (am Satzanfang) und "we" (im Satz). Deshalb ändern wir den String komplett auf Kleinschreibung.
Obama_2013_lowcase_str = "".join([character.lower() for character in Obama_2013_str])
Obama_2013_lowcase_str[0:500]
'thank you. thank you so much.\n\nvice president biden, mr. chief justice, members of the united states congress, distinguished guests, and fellow citizens:\n\neach time we gather to inaugurate a president we bear witness to the enduring strength of our constitution. we affirm the promise of our democracy. we recall that what binds this nation together is not the colors of our skin or the tenets of our faith or the origins of our names. what makes us exceptional - what makes us american - is our alle'
Viele Text Mining-Methoden beruhen allein auf der Häufigkeit von Wörter (bag of words), nicht deren Reihenfolge. In unserem Beispiel wollen wir ebenfalls nur die Häufigkeit von Wörtern zählen. Dazu müssen wir den Text (eine lange Zeichenkette) in eine Liste von einzelnen Wörtern umwandeln. Wenn uns etwa die Anzahl der Wörter pro Satz interessieren würde, dann müssten wir zusätzlich den Text in Sätze unterteilen. Die Unterteilung einer Zeichenkette in seine Elemente (Sätze, Wörter) nennt sich tokenization. Mit der Methode word_tokenize() der Bibliothek nltk konvertieren wir die Obama-Rede (als Zeichenkette) in eine Liste einzelner Wörter.
Obama_2013_words = nltk.word_tokenize(Obama_2013_lowcase_str)
Obama_2013_words[0:25]
['thank', 'you', '.', 'thank', 'you', 'so', 'much', '.', 'vice', 'president', 'biden', ',', 'mr.', 'chief', 'justice', ',', 'members', 'of', 'the', 'united', 'states', 'congress', ',', 'distinguished', 'guests']
Viele dieser Wörter sind Satzzeichen, die wir nicht berücksichtigen möchten. Gleiches gilt für häufige Wörter ("Stoppwörter"; stop words), wie etwa Artikel. NLTK hat eine Liste häufiger Wörter, die wir um Satzzeichen ergänzen. Wir greifen darauf mit stopwords.words() zu und geben an, dass wir Stoppwörter für die englische Sprache benötigen.
stop_words = list(stopwords.words("English"))
stop_words[0:10]
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're"]
len(stop_words)
179
Es gibt 179 Stoppwörter in der NLTK-Liste
Dann suchen wir nach Satzzeichen - also Elementen unserer Liste, die nur aus einem Zeichen bestehen. Dazu benutzen wir wieder eine list comprehension, diesmal ergänzt um eine Filter-Klausel (if). Der Befehl set() reduziert die Liste auf die Menge eindeutiger Zeichen, damit entfernen wir redundante Nennungen des gleichen Elements.
set([words for words in Obama_2013_words if len(words)==1])
{',', '-', '.', '4', ':', ';', 'a', 'i'}
Die Satzzeichen, die wir so gefunden haben, nehmen wir in die Liste _stop_words_to_add_ auf und fügen sie zu der NTLK-Stoppwörterliste hinzu.
stop_words_to_add = [",", ".", ":", ";", "-"]
stop_words_extended = set(stop_words + stop_words_to_add)
len(stop_words_extended)
184
Die Gesamtliste (_stop_words_extended_) beinhaltet 184 Elemente (Wörter und Satzzeichen). Mit Hilfe einer list comprehension entfernen wir diese Stoppwörter aus der Liste der Wörter der Obama-Rede. Das Ergebnis ist _Obama_2013_clean_.
Obama_2013_clean = [words for words in Obama_2013_words if words not in stop_words_extended]
Obama_2013_clean[0:25]
['thank', 'thank', 'much', 'vice', 'president', 'biden', 'mr.', 'chief', 'justice', 'members', 'united', 'states', 'congress', 'distinguished', 'guests', 'fellow', 'citizens', 'time', 'gather', 'inaugurate', 'president', 'bear', 'witness', 'enduring', 'strength']
Mit FreqDist() können wir ein Lexikon mit der Häufigkeit der Wörter anlegen.
nltk.FreqDist(Obama_2013_clean)
FreqDist({'us': 21, 'must': 17, 'people': 11, "'s": 11, 'time': 10, 'america': 8, 'together': 7, 'country': 7, 'make': 7, 'every': 7, ...})
Das Wort "us" (was man auch als Stoppwort betrachten könnte) ist mit 21 Nennungen das häufigste Wort. "america" kommt 8 mal vor. Wir können auch direkt auf die Häufigkeit einzelner Wörter zugreifen.
Obama_2013_freq_dist = nltk.FreqDist(Obama_2013_clean)
Obama_2013_freq_dist["america"]
8
Die Aufbereitung des Textes, die wir an einem Beispiel demonstriert haben, können wir in einer Funktion zusammenfügen, um sie dann auf alle Texte anzuwenden.
def clean_text(corpus, file_id, stop_words_list):
"""
This method cleans a text and returns a list of (tokenized) words. In particular it
- removes stopwords
- normalizes words to lower case
Input:
- name of corpus
- name of file (file_id in NLTK)
- list of stop words
Output: cleaned text as a list
"""
text_raw = corpus.raw(file_id)
text_str = text_raw.replace("â\x80\x94", " - ")
text_lowcase_str = "".join([character.lower() for character in text_str])
text_words = nltk.word_tokenize(text_lowcase_str)
text_clean = [words for words in text_words if words not in stop_words_list]
return text_clean
test = clean_text(inaugural, "2013-Obama.txt", stop_words_extended)
test[0:15]
['thank', 'thank', 'much', 'vice', 'president', 'biden', 'mr.', 'chief', 'justice', 'members', 'united', 'states', 'congress', 'distinguished', 'guests']
Unsere neue Hilfsfunktion erlaubt es uns, die Aufbereitung und Analyse der Obama-Rede mit nur drei Zeilen für eine weitere Rede durchzuführen. Wir können beispielsweise gucken, wie häufig George Washington in 1789 das Wort "America" benutzt hat.
Washington_1789_clean = clean_text(inaugural, "1789-Washington.txt", stop_words_extended)
Washington_1789_freq_dist = nltk.FreqDist(Washington_1789_clean)
Washington_1789_freq_dist["america"]
0
Erstaunlicherweise hat George Washington kein einziges mal den Namen "America" benutzt. Tatsächlich spricht er an zwei Stellen von dem amerikanischen Volk ("American people"), aber eben nicht von dem Land selbst ("America").
Washington_1789_freq_dist["american"]
2
Wir können nun eine erste Antwort auf unsere Frage geben: wie häufig wurden die Wörter "America" und "Constitution". Dazu lassen wir in einer For-Schleife alle Texte durch unsere Hilfsfunktion zur Textbereinigung laufen und zählen anschließend die Nennung von "America" bzw. "Constitution". Die Häufigkeit pro Rede speichern wir jeweils in Listen (_america_frq_lst_ bzw. _constitution_frq_lst_) ab.
# initializing lists for word frequencies
america_frq_lst = []
constitution_frq_lst = []
# clean texts and append word frequencies to lists
for speech in speeches:
text_clean_lst = clean_text(inaugural, speech, stop_words_extended)
america_frq_lst.append(nltk.FreqDist(text_clean_lst)["america"])
constitution_frq_lst.append(nltk.FreqDist(text_clean_lst)["constitution"])
Die Listen mit den Worthäufigkeiten sowie die Listen mit den Meta-Daten (Jahr und Präsident) fügen wir nun zu einem pandas data frame zusammen.
## creating data frame
# as dictionary
freq_dict = {"Year": years_lst,
"President": presidents_lst,
"America": america_frq_lst,
"Constitution": constitution_frq_lst}
# as data frame
freq_df = pd.DataFrame(data = freq_dict, index = years_lst)
freq_df.info()
<class 'pandas.core.frame.DataFrame'> Index: 58 entries, 1789 to 2017 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Year 58 non-null object 1 President 58 non-null object 2 America 58 non-null int64 3 Constitution 58 non-null int64 dtypes: int64(2), object(2) memory usage: 2.3+ KB
freq_df.describe()
America | Constitution | |
---|---|---|
count | 58.000000 | 58.000000 |
mean | 3.655172 | 3.551724 |
std | 5.510920 | 6.500128 |
min | 0.000000 | 0.000000 |
25% | 0.000000 | 0.000000 |
50% | 1.000000 | 1.000000 |
75% | 6.000000 | 3.750000 |
max | 21.000000 | 36.000000 |
"America" kam im Durchschnitt 3,7 mal vor, im Schnitt marginal häufiger als das Wort "Constitution" (3.6). Bei der höchsten Anzahl an Nennungen liegt "constitution" mit 36 Nennungen klar vorne (America: 21).
Um zu analysieren, wie sehr sich die absoluten Häufigkeiten im Zeitverlauf geändert haben, können wir die Worthäufigkeiten als Liniendiagramm visualisieren.
freq_df.plot.line()
<AxesSubplot:>
Die Verfassung ("constitution") wurde zu Beginn der Republik und in der Mitte des 19. Jahrhunderts häuf erwähnt, im 20. und 21. Jahrhundert hingegen selten. Referenzen auf das Land gab es anfangs selten, aber im 20 und 21. Jahrhundert häufig.
Wir hatten oben gesehen, dass George Washington in seiner ersten Antrittsrede das Wort "America" kein einziges mal benutzt hat aber "American" zwei mal. Für unsere Analyse mag dieser Unterschied (Landesname als Substantiv oder Adjektiv) egal sein - wir möchten nur wissen, wie häufig es Verweise auf das Land oder seine Menschen, etc. gab. Grammatikalische Unterschiede zwischen Wörter (Singular vs. Plural, unterschiedliche Endungen auf Grund grammatikalischer Regeln, etc.) sind beim Text Mining meistens nicht sehr relevant, weil es uns primär um die Bedeutung von Wörtern geht.
Um Wörter auf ihren Wortstamm bzw. auf ihre grundlegende Bedeutung zurückzuführen gibt es zwei grundlegende Techniken:
Wir probieren zwei Formen von Stemming (Porter Stemmer, Snowball Stemmer) und eine Implementierung von Lemmatisierung aus. Unser erstes Beispiel sind Variationen des Wortes "constitution" (Substantiv im Singular, Substantiv im Plural, Adjektiv).
test_words = ["constitution", "constitutions", "constitutional"]
test_lemmatizer = WordNetLemmatizer()
test_lemmatized_words = [test_lemmatizer.lemmatize(word) for word in test_words]
print("Lemma: ", test_lemmatized_words)
#Create instances of both stemmers, and stem the words using them
stemmer_ps = PorterStemmer()
#an instance of Porter Stemmer
stemmed_words_ps = [stemmer_ps.stem(word) for word in test_words]
print("Porter Stemmer: ", stemmed_words_ps)
stemmer_sb = SnowballStemmer("english")
#an instance of Snowball Stemmer
stemmed_words_sb = [stemmer_sb.stem(word) for word in test_words]
print("Snowball Stemmer: ", stemmed_words_sb)
Lemma: ['constitution', 'constitution', 'constitutional'] Porter Stemmer: ['constitut', 'constitut', 'constitut'] Snowball Stemmer: ['constitut', 'constitut', 'constitut']
Lemmatisierung beseitigt den Unterschied zwischen Singular und Plural, unterscheidet aber zwischen der Wortart (Substantiv, Adjektiv). Die Stemmer führen alle Variationen auf "constitut" zurück. Für unsere Zwecke ist die zweite Variante sinnvoller.
Mit (normalisierten/klein geschriebenen) Variationen von "America" gelingt es keinem Stemmer bzw. Lemmatizer alle Wörter auf ein Wort(-stamm) zu reduzieren.
test_words = ["america", "american", "americans"]
test_lemmatizer = WordNetLemmatizer()
test_lemmatized_words = [test_lemmatizer.lemmatize(word) for word in test_words]
print("Lemma: ", test_lemmatized_words)
#Create instances of both stemmers, and stem the words using them.
stemmer_ps = PorterStemmer()
#an instance of Porter Stemmer
stemmed_words_ps = [stemmer_ps.stem(word) for word in test_words]
print("Porter Stemmer: ", stemmed_words_ps)
stemmer_sb = SnowballStemmer("english")
#an instance of Snowball Stemmer
stemmed_words_sb = [stemmer_sb.stem(word) for word in test_words]
print("Snowball Stemmer: ", stemmed_words_sb)
Lemma: ['america', 'american', 'american'] Porter Stemmer: ['america', 'american', 'american'] Snowball Stemmer: ['america', 'american', 'american']
Auch ein Vorziehen dieses Bearbeitungsschritts vor die Normalisierung (Umwandlung in Kleinschreibung) würde keinen Unterschied machen.
test_words = ["America", "American", "Americans"]
test_lemmatizer = WordNetLemmatizer()
test_lemmatized_words = [test_lemmatizer.lemmatize(word) for word in test_words]
print("Lemma: ", test_lemmatized_words)
#Create instances of both stemmers, and stem the words using them.
stemmer_ps = PorterStemmer()
#an instance of Porter Stemmer
stemmed_words_ps = [stemmer_ps.stem(word) for word in test_words]
print("Porter Stemmer: ", stemmed_words_ps)
stemmer_sb = SnowballStemmer("english")
#an instance of Snowball Stemmer
stemmed_words_sb = [stemmer_sb.stem(word) for word in test_words]
print("Snowball Stemmer: ", stemmed_words_sb)
Lemma: ['America', 'American', 'Americans'] Porter Stemmer: ['america', 'american', 'american'] Snowball Stemmer: ['america', 'american', 'american']
Deshalb tauschen wir die "Synonyme" einfach direkt mit "america" aus.
test_words = ["america", "constitution", "american", "constitutions", "americans", "constitutional"]
test_words_new = [word.replace('americans', 'america') for word in test_words]
test_words_new = [word.replace('american', 'america') for word in test_words_new]
test_words_new
['america', 'constitution', 'america', 'constitutions', 'america', 'constitutional']
Am Beispiel der Obama-Rede von 2013 sehen wir den Effekt davon.
print("America :", Obama_2013_freq_dist["america"])
print("American:", Obama_2013_freq_dist["american"])
print("Americans:", Obama_2013_freq_dist["americans"])
America : 8 American: 6 Americans: 4
Es gab insgesamt 18 Nennungen von "America", "American" oder "Americans".
def replace_synonyms_america(text, synonyms, new_word):
"""
Replaces a list of synonyms
"""
for index, word in enumerate(text):
if word in synonyms:
text[index] = new_word
return text
synonyms_america = ["americans", "american"]
Obama_2013_new = replace_synonyms_america(Obama_2013_clean, synonyms_america, "america")
nltk.FreqDist(Obama_2013_new)["america"]
18
Mit unserer Hilfsfunktion replace_synonyms_america() und einer Liste von "Synonymen" (für unsere Zwecke) des Wortes "America" kommen wir zu dem gleichen Ergebnis.
Wir möchten also sowohl ein Stemming als auch einen Austausch von Synonymen vornehmen. Dazu definieren wir noch eine Hilfsfunktion stem_text().
def stem_text(text):
"""
Stemming of text using SnowballStemmer
Input: list of (cleaned) words
Ouptut: list of stemmed words
"""
stemmer_sb = SnowballStemmer("english")
stemmed_words_sb = [stemmer_sb.stem(word) for word in text]
return stemmed_words_sb
Jetzt können wir in einer kurzen For-Schleife alle Bearbeitungsschritte (Bereinigung, Austausch Synonyme, Stemming) für alle Texte durchführen und Verweise auf das Land und die Verfassung zählen.
synonyms_america = ["americans", "american"]
america_w_synonyms_frq_lst = []
constitution_stmd_frq_lst = []
for speech in speeches:
text_clean_lst = clean_text(inaugural, speech, stop_words_extended)
text_clean_replaced = replace_synonyms_america(text_clean_lst, synonyms_america, "america")
text_final = stem_text(text_clean_replaced)
america_w_synonyms_frq_lst.append(nltk.FreqDist(text_final)["america"])
constitution_stmd_frq_lst.append(nltk.FreqDist(text_final)["constitut"])
Für die Datenanalyse generieren wir wieder einen pandas data frame.
## creating data frame
column_names = ["Year", "President", "America", "Constitution"]
freq_stmd_syn_dict = {"Year": years_lst,
"President": presidents_lst,
"America": america_w_synonyms_frq_lst,
"Constitution": constitution_stmd_frq_lst}
#
freq_stmd_syn_df = pd.DataFrame(data = freq_stmd_syn_dict, index = years_lst)
freq_stmd_syn_df.describe()
America | Constitution | |
---|---|---|
count | 58.000000 | 58.000000 |
mean | 7.810345 | 4.931034 |
std | 8.897973 | 8.273530 |
min | 0.000000 | 0.000000 |
25% | 1.250000 | 0.000000 |
50% | 4.500000 | 2.000000 |
75% | 11.000000 | 6.000000 |
max | 35.000000 | 45.000000 |
Die Häufigkeiten sind jetzt - nachdem wir grammatikalische Unterschiede ignorieren - höher. Im Durchschnitt gibt es häufiger Verweise auf das Land (oder seine Menschen, ...) als auf die Verfassung.
freq_stmd_syn_df.plot.line()
<AxesSubplot:>
Während des Kalten Krieges gab es eine steigende Tendenz von Referenzen auf "Amerika". Diese Anzahl ging nach dem Ende des Kalten Krieges für eine WEile zurück und ist zuletzt (letzte Rede: Trump 2017) wieder gestiegen.