quanteda
In diesem Tutorial behandeln wir Textmetriken und Textvergleiche. Cornelius Puschmann hat zu diesem Thema bereits eine sehr gute Übersicht geliefert. Von daher werde ich seine Erklärungen, seinen Code und seine Darstellungen zu großen Teilen übernehmen und mit eigenen Daten anreichern.
Ihr werdet in diesem Tutorial lernen eure Texte kennenzulernen. Dafür werden wir uns Worthäufigkeiten und die Ähnlichkeit von Texten anschauen. Diese grundlegenden Statistiken sind relevant für die weiterführenden Methoden, die wir im Laufe des Seminars noch näher kennenlernen, und sie helfen uns bereits bei der Beantwortung unserer Fragestellungen. So habe ich in meiner eigenen Arbeit zum Beispiel die Textähnlichkeit von Pressemitteilungen des Bundesverfassungsgericht und Zeitungsartikeln als abhängige Variable verwendet um die Berichterstattung über Gerichtsentscheidungen zu analysieren.
Die Grundlage für unsere weiteren Schritte bildet die Dokument-Feature-Matrix (DFM) bzw. Dokument-Term-Matrix (DTM).
Hierfür werden wir die Antrittsreden der US Präsidenten aus dem quanteda
-Paket verwenden (eine Liste aller in quanteda
gespeicherten Korpora findet ihr hier: quanteda.corpora).
library(quanteda)
library(quanteda.textplots)
library(quanteda.textstats)
inaug_speeches <- data_corpus_inaugural # der Datensatz data_corpus_inaugural kommt aus quanteda und wird automatisch durch den library-Befehl mitgeladen
inaug_speeches
## Corpus consisting of 59 documents and 4 docvars.
## 1789-Washington :
## "Fellow-Citizens of the Senate and of the House of Representa..."
##
## 1793-Washington :
## "Fellow citizens, I am again called upon by the voice of my c..."
##
## 1797-Adams :
## "When it was first perceived, in early times, that no middle ..."
##
## 1801-Jefferson :
## "Friends and Fellow Citizens: Called upon to undertake the du..."
##
## 1805-Jefferson :
## "Proceeding, fellow citizens, to that qualification which the..."
##
## 1809-Madison :
## "Unwilling to depart from examples of the most revered author..."
##
## [ reached max_ndoc ... 53 more documents ]
speeches.stats <- summary(inaug_speeches) # Die einzelnen deskriptiven Metriken des Korpus werden wir später noch gebrauchen können
Jetzt erstellen wir eine DTM mittels einer Pipeline:
library(tidyverse)
dtm_speeches <- inaug_speeches %>%
tokens(remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE) %>%
tokens_remove(pattern = stopwords("english")) %>%
tokens_tolower() %>%
dfm() # wir entfernen alle Füllwörter, Interpunktion, Symbole, Zahlen
dtm_speeches
## Document-feature matrix of: 59 documents, 9,212 features (92.66% sparse) and 4 docvars.
## features
## docs fellow-citizens senate house representatives among
## 1789-Washington 1 1 2 2 1
## 1793-Washington 0 0 0 0 0
## 1797-Adams 3 1 0 2 4
## 1801-Jefferson 2 0 0 0 1
## 1805-Jefferson 0 0 0 0 7
## 1809-Madison 1 0 0 0 0
## features
## docs vicissitudes incident life event filled
## 1789-Washington 1 1 1 2 1
## 1793-Washington 0 0 0 0 0
## 1797-Adams 0 0 2 0 0
## 1801-Jefferson 0 0 1 0 0
## 1805-Jefferson 0 0 2 0 0
## 1809-Madison 0 0 1 0 1
## [ reached max_ndoc ... 53 more documents, reached max_nfeat ... 9,202 more features ]
Neben der klassischen Wordcloud lassen sich mit quanteda
natürlich andere Einblicke in unsere Texte realisieren. Vor allem ist die die KWIC-Methode (Key-Word-In-Context) zu nennen. Hierbei werden der Satzkontext von von euch definierten Begriffe extrahiert und dargestellt.
Mit quanteda
ist es möglich den Kontext einzeler Wörter oder auch auch ganze Sätze darzustellen. Als ersten Schritt erstellen wir ein tokens
-Objekt und danach führen wir eine kwic
-Analyse durch:
inaug_speeches_toks <- tokens(inaug_speeches)
kwic(inaug_speeches_toks, "war", window = 2) # der window befehl definiert die anzahl der kontextwörter die angezeigt werden (ich nehme hier aus darstellerischen gründen nur 2 wörter)
## Keyword-in-context with 181 matches.
## [1797-Adams, 191] the Revolutionary | war | , supplying
## [1801-Jefferson, 1380] moments of | war | , till
## [1805-Jefferson, 558] time of | war | , if
## [1805-Jefferson, 569] sometimes produce | war | , increased
## [1805-Jefferson, 624] past. | War | will then
## [1809-Madison, 401] nations at | war | by fulfilling
## [1813-Madison, 136] that the | war | with a
## [1813-Madison, 191] which this | war | is distinguished
## [1813-Madison, 309] of the | war | are staked
## [1813-Madison, 462] As the | war | was just
## [1813-Madison, 513] . The | war | has been
## [1813-Madison, 561] prisoners of | war | citizens of
## [1813-Madison, 577] usages of | war | . They
## [1813-Madison, 587] prisoners of | war | , and
## [1813-Madison, 628] and honorable | war | for the
## [1813-Madison, 879] the very | war | in which
## [1813-Madison, 907] of the | war | on our
## [1813-Madison, 995] bring the | war | to an
## [1813-Madison, 1118] an unavoidable | war | should have
## [1813-Madison, 1139] called for | war | , all
## [1813-Madison, 1201] render the | war | short and
## [1817-Monroe, 750] all. | War | became at
## [1817-Monroe, 1326] the late | war | ? The
## [1817-Monroe, 1558] involved in | war | , and
## [1817-Monroe, 1953] implements of | war | in a
## [1817-Monroe, 1966] event of | war | ; the
## [1817-Monroe, 2014] time of | war | , with
## [1817-Monroe, 2068] calamities of | war | and to
## [1817-Monroe, 2073] bring the | war | to a
## [1817-Monroe, 2277] prepared for | war | . With
## [1817-Monroe, 2559] event of | war | , unsought
## [1821-Monroe, 482] concluded a | war | with a
## [1821-Monroe, 501] of that | war | are too
## [1821-Monroe, 601] as the | war | had terminated
## [1821-Monroe, 1013] ships of | war | . By
## [1821-Monroe, 1056] event of | war | our whole
## [1821-Monroe, 1212] inseparable from | war | when it
## [1821-Monroe, 1244] to prevent | war | . I
## [1821-Monroe, 1400] . The | war | between Spain
## [1821-Monroe, 1466] a civil | war | in which
## [1821-Monroe, 1669] munitions of | war | , and
## [1821-Monroe, 1803] Should the | war | be continued
## [1821-Monroe, 2362] ships of | war | of the
## [1821-Monroe, 2866] the late | war | , are
## [1821-Monroe, 2909] the late | war | , and
## [1821-Monroe, 3156] the late | war | . I
## [1821-Monroe, 3789] prospect of | war | increasing.
## [1825-Adams, 265] peace and | war | incidental to
## [1825-Adams, 817] extremities of | war | ; and
## [1825-Adams, 1185] involved in | war | and the
## [1825-Adams, 1450] defenses of | war | ; that
## [1825-Adams, 2192] for defensive | war | ; to
## [1829-Jackson, 849] means of | war | , can
## [1837-VanBuren, 1958] with us | war | could never
## [1837-VanBuren, 2026] the last | war | , far
## [1841-Harrison, 1152] powerful in | war | , and
## [1841-Harrison, 5676] of the | War | of the
## [1841-Harrison, 6702] and civil | war | , and
## [1845-Polk, 1383] miseries of | war | , our
## [1845-Polk, 2439] during the | War | of 1812
## [1845-Polk, 3502] or in | war | , if
## [1845-Polk, 3801] miseries of | war | , our
## [1845-Polk, 3875] chances of | war | and opening
## [1857-Buchanan, 1976] and in | war | . After
## [1857-Buchanan, 2177] and in | war | , have
## [1857-Buchanan, 2250] to declare | war | ,"
## [1857-Buchanan, 2355] munitions of | war | may be
## [1857-Buchanan, 2377] of a | war | with a
## [1857-Buchanan, 2910] fortune of | war | against a
## [1861-Lincoln, 3089] go to | war | , you
## [1861-Lincoln, 3863] of civil | war | . The
## [1865-Lincoln, 167] impending civil | war | . All
## [1865-Lincoln, 196] Union without | war | , urgent
## [1865-Lincoln, 209] it without | war | - seeking
## [1865-Lincoln, 225] parties deprecated | war | , but
## [1865-Lincoln, 233] would make | war | rather than
## [1865-Lincoln, 246] would accept | war | rather than
## [1865-Lincoln, 255] and the | war | came.
## [1865-Lincoln, 303] of the | war | . To
## [1865-Lincoln, 327] even by | war | , while
## [1865-Lincoln, 352] for the | war | the magnitude
## [1865-Lincoln, 564] this terrible | war | as the
## [1865-Lincoln, 614] scourge of | war | may speedily
## [1869-Grant, 558] effects of | war | , but
## [1873-Grant, 496] lately at | war | with the
## [1873-Grant, 966] this or | war | of extermination
## [1873-Grant, 1412] throughout the | war | , and
## [1881-Garfield, 129] of the | war | for independence
## [1881-Garfield, 456] of civil | war | . We
## [1881-Garfield, 691] court of | war | by a
## [1881-Garfield, 865] danger of | war | and dissolution
## [1881-Garfield, 2227] necessities of | war | ; but
## [1889-Harrison, 1092] exercise in | war | , was
## [1889-Harrison, 2423] and in | war | , ready
## [1889-Harrison, 3600] of modern | war | ships and
## [1897-McKinley, 1271] time of | war | . The
## [1897-McKinley, 2596] to make | war | upon them
## [1897-McKinley, 2943] the Civil | War | . Commendable
## [1897-McKinley, 3167] aggression. | War | should never
## [1897-McKinley, 3185] preferable to | war | in almost
## [1897-McKinley, 3369] passion and | war | , controlling
## [1901-McKinley, 373] brink of | war | without the
## [1901-McKinley, 404] avert the | war | , but
## [1901-McKinley, 524] horrors of | war | . Intrusted
## [1901-McKinley, 766] by the | war | maps of
## [1901-McKinley, 2272] not waging | war | against the
## [1901-McKinley, 2287] are making | war | against the
## [1901-McKinley, 2401] who make | war | against us
## [1909-Taft, 2090] into any | war | with a
## [1909-Taft, 2169] to avoid | war | . But
## [1909-Taft, 2205] prepared for | war | , we
## [1909-Taft, 2424] the Spanish | war | and since
## [1909-Taft, 2819] of a | war | which might
## [1909-Taft, 3568] of the | War | Department and
## [1917-Wilson, 342] are at | war | . The
## [1917-Wilson, 371] . The | war | inevitably set
## [1917-Wilson, 489] of the | war | itself.
## [1917-Wilson, 965] whether in | war | or in
## [1921-Harding, 722] hatred of | war | into recommended
## [1921-Harding, 1011] probability of | war | , and
## [1921-Harding, 1269] wreckage of | war | . While
## [1921-Harding, 1585] to make | war | upon us
## [1921-Harding, 1630] attitude, | war | is again
## [1921-Harding, 1733] penny of | war | profit shall
## [1921-Harding, 1795] amid defensive | war | while another
## [1921-Harding, 1863] no staggering | war | debts,
## [1921-Harding, 1934] today. | War | never left
## [1921-Harding, 2003] strike at | war | taxation,
## [1921-Harding, 2221] fever of | war | activities.
## [1921-Harding, 2237] , because | war | invariably readjusts
## [1921-Harding, 2780] When World | War | threatened civilization
## [1925-Coolidge, 582] the Great | War | . When
## [1925-Coolidge, 1109] of fomenting | war | . This
## [1925-Coolidge, 1818] of aggressive | war | . But
## [1929-Hoover, 136] the Great | War | and the
## [1929-Hoover, 2274] renunciation of | war | as an
## [1933-Roosevelt, 843] of a | war | , but
## [1933-Roosevelt, 1844] wage a | war | against the
## [1937-Roosevelt, 364] the Revolutionary | War | ; they
## [1945-Roosevelt, 182] victory in | war | . We
## [1945-Roosevelt, 384] year of | war | , 1945
## [1949-Truman, 752] classes that | war | is inevitable
## [1949-Truman, 1122] not have | war | - that
## [1953-Eisenhower, 395] and of | war | to a
## [1953-Eisenhower, 1366] event of | war | . So
## [1953-Eisenhower, 1586] ) Abhorring | war | as a
## [1953-Eisenhower, 2674] sorrow of | war | . More
## [1957-Eisenhower, 515] recent World | War | , seek
## [1961-Kennedy, 213] tempered by | war | , disciplined
## [1961-Kennedy, 673] instruments of | war | have far
## [1961-Kennedy, 858] mankind's final | war | . So
## [1961-Kennedy, 1270] , and | war | itself.
## [1965-Johnson, 1455] and in | war | - they
## [1969-Nixon, 193] afraid of | war | , the
## [1969-Nixon, 650] caught in | war | , wanting
## [1969-Nixon, 1097] destruction of | war | abroad to
## [1973-Nixon, 57] seemingly endless | war | abroad and
## [1973-Nixon, 260] of World | War | II toward
## [1973-Nixon, 1422] most difficult | war | comes to
## [1977-Carter, 1322] weapons of | war | but on
## [1981-Reagan, 2636] win this | war | . Therefore
## [1985-Reagan, 2512] oppression and | war | . Every
## [1989-Bush, 1399] Second World | War | has come
## [1989-Bush, 1690] . That | war | cleaves us
## [1989-Bush, 1700] , that | war | began in
## [1993-Clinton, 198] the Cold | War | assumes new
## [1993-Clinton, 565] the Civil | War | , to
## [1997-Clinton, 222] long cold | war | ; and
## [2001-Bush, 800] depression and | war | , when
## [2009-Obama, 176] is at | war | , against
## [2009-Obama, 1708] of civil | war | and segregation
## [2013-Obama, 586] decade of | war | is now
## [2013-Obama, 1287] require perpetual | war | . Our
## [2013-Obama, 1368] just the | war | ; who
## [2021-Biden.txt, 363] and in | war | , we
## [2021-Biden.txt, 465] of World | War | II.
## [2021-Biden.txt, 888] the Civil | War | , the
## [2021-Biden.txt, 895] , World | War | , 9
## [2021-Biden.txt, 1151] for total | war | . And
## [2021-Biden.txt, 1221] the Civil | War | , when
## [2021-Biden.txt, 1756] this uncivil | war | that pits
Beachtet bitte das der KWIC-Befehl auf den Korpus und nicht auf die zuvor erstellte DTM angewendet wurde.
Mittels dieser einfachen Methode lassen sich jetzt weitere Analyseschritte realisieren. So können wir zum Beispiel die Häufigkeit und Verteilung von Tokens/Sequenzen berechnen, wie zum Beispiel für die Begriffe "freedom" und "war".
library(dplyr)
term1 <- kwic(inaug_speeches_toks, "freedom") %>% # KWIC mit dem Wort "freedom"
as.data.frame() %>% # Transformation in einen Datensatz
group_by(docname, keyword) %>% # Gruppierung
mutate(count = n()) %>% # Berechnung wie oft das Wort "Freedom" vorkommt
mutate(percentage = count/sum(speeches.stats$Tokens/100), search_term = "freedom") %>%
arrange(desc(percentage))
term2 <- kwic(inaug_speeches_toks, "war") %>% # KWIC mit dem Wort "war"
as.data.frame() %>% # Transformation in einen Datensatz
group_by(docname, keyword) %>% # Gruppierung
mutate(count = n()) %>% # Berechnung wie oft das Wort "war" vorkommt
mutate(percentage = count/sum(speeches.stats$Tokens/100), search_term = "war") %>%
arrange(desc(percentage))
Ohne Pipeline-Operator würde der Code für z.B. term1
wie folgt aussehen:
inaug_freedom <- kwic(inaug_speeches_toks, "freedom")
term1 <- as.data.frame(inaug_freedom)
term1 <- group_by(term1, docname, keyword)
term1 <- mutate(term1, count = n()) # Berechnung wie oft das Wort "Freedom" vorkommt
term1 <- mutate(term1, percentage = count/sum(speeches.stats$Tokens/100), search_term = "freedom")
term1 <- arrange(term1, desc(percentage))
So sehen unsere term1
und `term2 Objekte jetzt aus:
term1
## # A tibble: 185 x 10
## # Groups: docname, keyword [42]
## docname from to pre keyword post pattern count percentage search_term
## <chr> <int> <int> <chr> <chr> <chr> <fct> <int> <dbl> <chr>
## 1 2005-Bu… 135 135 ", A… freedom by s… freedom 23 0.0152 freedom
## 2 2005-Bu… 269 269 "is … freedom . We… freedom 23 0.0152 freedom
## 3 2005-Bu… 315 315 "wor… freedom in a… freedom 23 0.0152 freedom
## 4 2005-Bu… 579 579 "voi… freedom , an… freedom 23 0.0152 freedom
## 5 2005-Bu… 696 696 "is … freedom , wh… freedom 23 0.0152 freedom
## 6 2005-Bu… 804 804 "the… freedom and … freedom 23 0.0152 freedom
## 7 2005-Bu… 843 843 "by … freedom ever… freedom 23 0.0152 freedom
## 8 2005-Bu… 876 876 "Eve… freedom come… freedom 23 0.0152 freedom
## 9 2005-Bu… 1010 1010 ": \… freedom to o… freedom 23 0.0152 freedom
## 10 2005-Bu… 1206 1206 "of … freedom . An… freedom 23 0.0152 freedom
## # … with 175 more rows
term2
## # A tibble: 181 x 10
## # Groups: docname, keyword [58]
## docname from to pre keyword post pattern count percentage search_term
## <chr> <int> <int> <chr> <chr> <chr> <fct> <int> <dbl> <chr>
## 1 1821-Mo… 482 482 Unit… war with… war 16 0.0106 war
## 2 1821-Mo… 501 501 . Th… war are … war 16 0.0106 war
## 3 1821-Mo… 601 601 . As… war had … war 16 0.0106 war
## 4 1821-Mo… 1013 1013 be n… war . By… war 16 0.0106 war
## 5 1821-Mo… 1056 1056 prot… war our … war 16 0.0106 war
## 6 1821-Mo… 1212 1212 deva… war when… war 16 0.0106 war
## 7 1821-Mo… 1244 1244 can … war . I … war 16 0.0106 war
## 8 1821-Mo… 1400 1400 also… war betw… war 16 0.0106 war
## 9 1821-Mo… 1466 1466 by m… war in w… war 16 0.0106 war
## 10 1821-Mo… 1669 1669 expo… war , an… war 16 0.0106 war
## # … with 171 more rows
Jetzt können wir die zum Beispiel die absolute Häufigkeit der beiden Begriffe visualisieren:
kwic_terms <- bind_rows(term1, term2) # Wir verbinden beide Datensätze miteinander
ggplot(kwic_terms, aes(docname, count, group = search_term, col = search_term)) +
geom_line(size = 1) +
scale_colour_brewer(palette = "Set1") +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
ggtitle("Absolute Häufigkeit der Wörter \"freedom\" und \"war\" pro Antrittsrede") +
xlab("Antrittsrede") + ylab("Wörter (absolut)")
Neben den Häufigkeiten eines Wortes können wir auch die Position des Wortes in dem jeweiligen Text untersuchen. Dafür stellt quanteda
die Funktion textplot_xray
zur Verfügung:
textplot_xray(kwic(inaug_speeches_toks, "freedom")) +
ggtitle("Position des Bergiffs \"freedom\" in den Antrittsreden der US-Präsidenten")
Die x-Achse repräsentiert die Position des jeweiligen Wortes innhalb des Textes. Wie wir sehen hat George W. Bush im Jahr 2005 Freedom nicht nur am Häufigsten von allen Präsidenten verwendet (siehe Plot zur Häufigkeit), sondern auch über seine gesamte Rede hinweg immer mal wieder. Dieses erste deskriptive Ergebnisse könnten wir jetzt im Kontext der 2005 vorherrschenden politischen Entwicklungen (Irakkrieg, großflächige Abhörungen von US-Bürgern etc.) analysieren.
Mittels KWIC können wir, sehr umständlich, Worthäufigkeiten für spezielle Wörter untersuchen. Zum Glück bietet quanteda
mit der textstat_frequency
-Funktion einen deutlich einfacheren Weg. Anstelle einer umständlichen eigenen Berechnung der verschiedenen Metriken, gibt uns textstat_frequency
die Metadaten, die Anzahl der Dokumente, in denen das Wort vorkommt und vieles mehr aus:
word_freq <- textstat_frequency(dtm_speeches) # hier nutzen wir die dtm!
head(word_freq)
## feature frequency rank docfreq group
## 1 people 584 1 57 all
## 2 government 564 2 52 all
## 3 us 505 3 56 all
## 4 can 487 4 56 all
## 5 must 376 5 52 all
## 6 upon 371 6 47 all
Da unser Antrittsredenkorpus die Parteien der Präsidenten enthält, können wir mit textstat_frequency
die Häufigkeiten nach Parteien gruppieren und mittels arrange
ordnen:
party_freq <- textstat_frequency(dtm_speeches, groups = Party) %>% arrange(rank, group)
head(party_freq, 35) # schauen wir uns die ersten 35 Beobachtungen an
## feature frequency rank docfreq group
## 1 us 222 1 21 Democratic
## 5215 government 68 1 6 Democratic-Republican
## 8044 people 20 1 1 Federalist
## 8744 can 9 1 1 none
## 8745 every 9 1 1 none
## 8746 government 9 1 2 none
## 9284 people 264 1 23 Republican
## 15447 government 88 1 3 Whig
## 2 people 199 2 22 Democratic
## 5216 great 61 2 5 Democratic-Republican
## 8045 government 16 2 1 Federalist
## 9285 government 240 2 23 Republican
## 15448 states 61 2 3 Whig
## 3 can 173 3 21 Democratic
## 5217 states 56 3 6 Democratic-Republican
## 8046 may 13 3 1 Federalist
## 9286 can 228 3 23 Republican
## 15449 people 57 3 3 Whig
## 15450 power 57 3 3 Whig
## 4 government 143 4 17 Democratic
## 5218 war 51 4 7 Democratic-Republican
## 8047 nations 11 4 1 Federalist
## 8747 may 7 4 2 none
## 9287 us 218 4 23 Republican
## 5 must 138 5 21 Democratic
## 5219 may 49 5 7 Democratic-Republican
## 8048 country 9 5 1 Federalist
## 8049 can 9 5 1 Federalist
## 8050 states 9 5 1 Federalist
## 8051 nation 9 5 1 Federalist
## 8748 present 6 5 2 none
## 8749 country 6 5 2 none
## 8750 public 6 5 1 none
## 8751 shall 6 5 2 none
## 9288 must 201 5 23 Republican
Die häufigsten Begriffe können wir natürlich auch pro Partei einzeln visualisieren. Der Einfachheit halber werden wir nur die Republikaner und die Demokraten plotten.
Zu erst visualisieren wir die populärsten Begriffe in den Reden republikanischer Präsidenten (+ die Frequenz bei jedem Begriff für die jeweils andere ‘Seite’):
freqs_rep <- filter(party_freq, group == "Republican") %>% as.data.frame() %>% select(feature, frequency)
freqs_dems <- filter(party_freq, group == "Democratic") %>% as.data.frame() %>% select(feature, frequency)
freqs <- left_join(freqs_rep, freqs_dems, by = "feature") %>% head(25) %>% arrange(frequency.x) %>% mutate(feature = factor(feature, feature))
ggplot(freqs) +
geom_segment(aes(x=feature, xend=feature, y=frequency.x, yend=frequency.y), color="grey") +
geom_point(aes(x=feature, y=frequency.x), color = "red", size = 3 ) +
geom_point(aes(x=feature, y=frequency.y), color = "lightblue", size = 2 ) +
ggtitle("Häufige Begriffe in Reden \nrepublikanischer und demokratischer Präsidenten") +
xlab("") + ylab("Wortfrequenz") +
coord_flip()
Jetzt visualisieren wir die populärsten Begriffe in den Reden demokratischer Präsidenten (+ die Frequenz bei jedem Begriff für die jeweils andere ‘Seite’):
freqs <- left_join(freqs_rep, freqs_dems, by = "feature") %>% head(25) %>% arrange(frequency.y) %>% mutate(feature = factor(feature, feature))
ggplot(freqs) +
geom_segment(aes(x=feature, xend=feature, y=frequency.y, yend=frequency.x), color="grey") +
geom_point(aes(x=feature, y=frequency.y), color = "blue", size = 3 ) +
geom_point(aes(x=feature, y=frequency.x), color = "lightcoral", size = 2 ) +
ggtitle("Häufige Begriffe in Reden \ndemokratischer und republikanischer Präsidenten") +
xlab("") + ylab("Wortfrequenz") +
coord_flip()
Natürlich haben wir im Vorhinein nicht sehr viel Arbeit in das Identifizieren von weiteren Stoppwörtern und andere hochfrequente aber eher bedeutungslose Wörter investiert. Wäre das der Fall gewesen, dann hätten wir hier sicherlich noch deutlich aussagekräftigere Ergebnisse erhalten.
Welche Wörter treten häufig gemeinsam auf? Welche linguistische Nähe oder Distanz haben Texte zueinander? Um diese und weitere Fragen zu beantworten, schauen wir uns jetzt Textähnlichkeiten und weitere Textmetriken an. Hierbei handelt es sich zum Teil um Methoden, welche der Plagiatsprüfung sehr nahe kommen, und auch auch bereits in der Forschung angewendet wurden. Zum Beispiel analysieren Kaspar Welbers et al. den Einfluss von Nachrichtenagenturen mittels Textähnlichkeiten.
Schauen wir uns in einem ersten Schritt die Begriffe an, welche häufig gemeinsam auftreten. quanteda
bietet hierfür die textstat_collocations
-Funktion, welche wir im folgenden auf unseren Antrittsredenkorpus anwenden.
speeches_collocations <- textstat_collocations(inaug_speeches, min_count = 10) # wir werden unseren korpus (nicht die dtm an!) und definieren das die Begriffs-Nachbarn mindest 10x vorhanden sein müssen
head(arrange(speeches_collocations, desc(count))) # Absteigend nach Aufkommen sortiert
## collocation count count_nested length lambda z
## 1 of the 1770 0 2 1.5941437 53.247540
## 9 in the 819 0 2 1.7258909 40.115117
## 58 to the 725 0 2 0.9201353 21.889068
## 7 of our 627 0 2 2.1095558 42.195293
## 1049 and the 474 0 2 0.2250432 4.556788
## 2 it is 322 0 2 3.6847316 51.437525
head(arrange(speeches_collocations, desc(lambda))) # Absteigend nach Lambda sortiert
## collocation count count_nested length lambda z
## 278 chief magistrate 10 0 2 10.359152 11.59438
## 271 vice president 18 0 2 10.153434 11.82283
## 98 god bless 16 0 2 9.100153 17.62693
## 173 both sides 13 0 2 8.652016 14.18988
## 93 four years 26 0 2 8.549682 17.89670
## 223 i am 63 0 2 8.266480 12.80124
Der Lambda- und z-Wert (z-standardisiertes Lambda) geben die Assoziationsstärke der Begriffspaare an. Je höher der Lambdawert, desto höher ist die Wahrscheinlichkeit, dass die zwei Begriffe auf einander folgen.
Inwiefern ist dieser Wert von der absoluten Häufigkeit eines Wortes (siehe die erste Tabelle) zu differenzieren?
Im Gegensatz zu Textähnlichkeiten (welche wir später behandeln werden), müssen wir bei Wortähnlichkeiten Sätze bzw. Wörter als einzelne Dokumente behandeln. Dadurch können wir auch bei einer geringen Anzahl an Dokumenten zuverlässige Ähnlichkeitsmaße berechnen. Als Methode wählen wir die Cosine similarity, die von Kaspar Welbers et al. als der aktuelle Standart in den Computational Social Science bezeichnet wird. Cosine similarity hat einen kontinuierlichen Wertebereich von 1 (perfekte Ähnlichkeit/Übereinstimmung) bis 0 (pefekte Ungleichheit/Unähnlichkeit).
Die Berechnung der Wortähnlichkeiten wird durch die textstat_simil
-Funktion realisiert. Nutzt bitte die help-Funktion (?textstat_simil
um die weiteren Methoden zur Berechnung von Wort-/Textähnlichkeit kennenzulernen). Als Input für die Funktion erstellen wir eine DFM, in der jeder Satz einer Beobachtung (bzw. "einem Dokument") entspricht. Für unser Beispiel berechnen wir die Wortähnlichkeit zu den Begriffen "freedom" und "war".
sentences_corpus <- corpus_reshape(inaug_speeches, to = "sentences") # wir formen unserem corpus basierend auf den einzelnen Reden in einen corpus basierend auf den einzelnen Sätzen um
sentences_dfm <- sentences_corpus %>%
tokens(remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE) %>%
tokens_remove(pattern = stopwords("english")) %>%
tokens_tolower() %>%
dfm()
similarity_freedom <- textstat_simil(sentences_dfm, sentences_dfm[,"freedom"], margin = "features", method = "cosine")
head(similarity_freedom[order(similarity_freedom[,1], decreasing = T),], 10)
## freedom bulwarks lopped peaceable unprovided regulars
## 1.0000000 0.2065285 0.2065285 0.2065285 0.2065285 0.2065285
## lightly burthened handmaid arraignment
## 0.2065285 0.2065285 0.2065285 0.2065285
similarity_war <- textstat_simil(sentences_dfm, sentences_dfm[,"war"], margin = "features", method = "cosine")
head(similarity_war[order(similarity_war[,1], decreasing = T),], 10)
## war retained calamities peace termination prisoners
## 1.0000000 0.1968748 0.1928971 0.1875304 0.1574998 0.1574998
## saving deprecated perish foe
## 0.1574998 0.1574998 0.1574998 0.1524986
Was sehen wir hier? Das Wort mit der höchsten Ähnlichkeit zu "Freedom" ist "bulwarks" also Bollwerke (womöglich interpretierbar mit: "Amerika als Bollwerk des Friedens in der Welt") und das Wort mit der höchstens Ähnlichkeit zu "War" ist "retained" also Rücklage (interpretierbar mit den hohen Geldzahlungen und Schulden die ein Krieg zur Folge hat). Das Wort mit zweithöchsten Ähnlichkeit "calamaties" also Katastrophen macht ebenfalls Sinn. Man muss jedoch sagen, dass wir in Punkt 3.1 dieses Tutorials gesehen haben, dass beide Wörter nicht in jeder Rede ähnlich oft vorkommen.
Wie dem auch sei, auch hier zeigt sich, dass wir zumindest etwas mehr Arbeit in die Identifizierung und Entfernung weiterer unbrauchbarer Wörter hätten stecken müssen (also während des Pre-Processing).
Das Gegenteil zur textstat_simil
-Funktion ist die textstat_dist
-Funktion, mit der man die Distanz zwischen Wörtern berechnen kann. Hier hier lässt sich zwischen verschiedenen Distanzmaßen wählen (für mehr Infos wie immer: ?textstat_dist
). In unserem Beispiel nehmen wir die etablierte euklidische Distanz und schauen uns die Ergebnisse für das Wort "freedom" an:
distance_freedom <- textstat_dist(sentences_dfm, sentences_dfm[,"freedom"], margin = "features", method = "euclidean") # wir berechnen die euklidische Distanz
head(distance_freedom[order(distance_freedom[,1], decreasing = T),], 10)
## government people us can upon shall states
## 29.47881 28.89637 27.74887 27.23968 25.37716 25.23886 25.00000
## must may every
## 24.95997 24.77902 24.06242
Der Begriff "Government" ist nach diesem Eregbnis am weitestens von dem Begriff "freedom" entfernt, gefolgt von "people" and "US". Da wir aber von Kapitel 3.2 wissen, dass diese Begriffe auch die häufigsten Begriffe in unserem Korpus sind, ist dieses Ergebnis nicht verwunderlich. In einer Forschungsarbeit müssten wir diese Eregbnisse nutzen um unsere Preprocessing-Schritte verbessern und um damit unsere Eregbnisse zu verbessen.
In unseren Beispielen zur Wortähnlichkeit haben wir in den Funktionen den margins
-Befehl mit "features" also Wörten spezifiziert. Eine weitere Möglichkeit ist die Spezifikation auf "documents", also ganze Dokumente/Texte. Auf diese Weise werden nicht die Ähnlichkeiten von Wörtern oder Sätze verglichen, sondern die Ähnlichkeiten von Dokumenten.
Im folgenden Beispiel inspizieren wir die Ähnlichkeit der 2005er Antrittsrede von George W. Bush mit allen anderen Antrittsreden. Dafür erstellen wir in einem ersten Schritt einen Datensatz mit den Ähnlichkeitsmaßen und im zweiten Schritt visualisieren wir die Maße:
bush_2005_similarity <- data.frame(Text = factor(speeches.stats$Text, levels = rev(speeches.stats$Text)), as.matrix(textstat_simil(dtm_speeches, dtm_speeches["2005-Bush",], margin = "documents", method = "cosine")))
ggplot(bush_2005_similarity, aes(X2005.Bush, Text)) + # Der Variablenname "X2005.Bush" wurde durch unsere Datensatzkreation automatisch gewählt.
geom_point(size = 2.5) +
ggtitle("Textähnlichkeit der 2005er Antrittsrede von G.W. Bush") +
xlab("Cosine similarity") + ylab("")
Die "größte" Ähnlichkeit weisen die Reden von Reagan 1985, Bush 2001 und Obama 2013 auf.
Schauen wir uns jetzt beispielhaft die Antrittsrede von Trump aus dem Jahr 2017 an:
trump_2017_similarity <- data.frame(Text = factor(speeches.stats$Text, levels = rev(speeches.stats$Text)), as.matrix(textstat_simil(dtm_speeches, dtm_speeches["2017-Trump",], margin = "documents", method = "cosine")))
ggplot(trump_2017_similarity, aes(X2017.Trump, Text)) +
geom_point(size = 2.5) +
ggtitle("Textähnlichkeit der 2017er Antrittsrede von Donald Trump") +
xlab("Cosine similarity") + ylab("")
Wie ihr sehen könnt, lässt sich mit dieser Art der Darstellung gezielt die Ähnlichkeiten und Unterschiede für einzelne Text visualisieren. Natürlich gibt es noch viele weitere Wege der Visualisierung, wie z.B. Dendogramme oder Heatmaps.
Eine interaktive Heatmap z.B. lässt sich sehr schnell mit der Hilfe der Pakete heatR
und d3heatmap
erstellen:
library(d3heatmap)
library(heatR)
textstat_simil(dtm_speeches, margin = "documents", method = "cosine") %>%
as.matrix() %>%
heatR::corrheat()
Am besten ihr probiert selber mal etwas rum und sucht nach eigenen Wegen der Visualisierung von Textähnlichkeiten (ihr könntet euch zum Beispiel das RNewsflow
-Paket von Kaspar Welbers anschauen).
Das Konzept der Keyness wie stark oder schwach ein Begriff in einem Text im Vergleich zum gesamten Korpus vertreten ist. Postive Keynesswerte entsprechen einer Über- und negative Werte einer Unterrepräsentation eines Begriffs in einem Text im Vergleich zum Gesamtkorpus. Die Keyness untersucht also nicht die Position eines Begriffs in einem Text (wie es bei den Ähnlichkeitsmaßen der Fall ist), sondern die Verteilung von Begriffen in Texten.
Mittels der textstat_keyness
-Funktion untersuchen wir die Keyness-Werte für die 2017er Antrittsrede von Donald Trump und visualisieren sie mit der textplot_keyness
-Funktion:
keyness_trump <- textstat_keyness(dtm_speeches, target = "2017-Trump")
textplot_keyness(keyness_trump)
Im Vergleich dazu die 2013er Rede von Barack Obama:
keyness_obama <- textstat_keyness(dtm_speeches, target = "2013-Obama")
textplot_keyness(keyness_obama)
Bei diesen beiden Beispielen wird schnell klar, dass sich Trump vor allem an seinem Vorgänger Präsident Obama abgearbeitet hat und dass der Name auch sehr zentral in dieser Rede ist. Während für die Rede von Obama die Begriffe "creed" und "journey" sehr zentral sind. In einer Analyse z.B. wie unterschiedlich die US Präsidenten ihre Antrittsreden framen, wäre eine solche erste deskriptive Einsicht sicherlich sehr hilfreich. Die graue Referenzkategorie zeigt Begriffe an, welche in den anderen Reden auftreten, aber nicht in der von uns untersuchten Rede.
Wie lässt sich die Diversität von Texten messen und vergleichen? Haben einige Präsidenten eine geringere Vielfalt an Wörtern genutzt als andere? Stimmt die Annahme, dass Donald Trump eine einfachere Sprache nutzt als seine Amtsvorgänger?
Um diese und andere Fragen zu beantworten, nutzen wir die textstat_lexdiv
-Funktion von quanteda
:
speeches_diversity <- textstat_lexdiv(dtm_speeches, measure = "all")
tail(speeches_diversity)
## document TTR C R CTTR U S
## 54 2001-Bush 0.6424010 0.9335834 17.97575 12.71078 43.56987 0.9353212
## 55 2005-Bush 0.6176753 0.9306568 19.92900 14.09193 43.51472 0.9349294
## 56 2009-Obama 0.6828645 0.9460250 23.38748 16.53745 56.86513 0.9505226
## 57 2013-Obama 0.6605238 0.9402270 21.20888 14.99694 50.41169 0.9441226
## 58 2017-Trump 0.6409537 0.9322933 17.11478 12.10198 42.13894 0.9331289
## 59 2021-Biden.txt 0.5572316 0.9167855 18.70672 13.22765 36.67539 0.9221331
## K I D Vm Maas lgV0 lgeV0
## 54 32.86635 167.33399 0.002012066 0.03603559 0.1514980 7.538695 17.35849
## 55 32.93405 141.30178 0.002335033 0.04169166 0.1515940 7.674943 17.67221
## 56 23.61314 262.09191 0.001510086 0.03335977 0.1326102 8.959235 20.62940
## 57 29.09796 192.27239 0.001941748 0.03796535 0.1408427 8.319367 19.15605
## 58 41.64298 125.81265 0.002765652 0.04445351 0.1540488 7.353863 16.93290
## 59 37.18520 96.30867 0.002833723 0.04611034 0.1651249 7.005760 16.13136
Die textstat_lexdiv
-Funktion gibt uns eine Vielzahl unterschiedlicher Diversitäts-Metriken aus (?textstat_lexdiv
). Auf den ersten Blick finden sich auch bereits Unterschiede zwischen den Antrittsreden.
Wollen wir einen etwas genaueren Blick auf die Reden werfen, dann bietet sich eine zum Beispiel Visualisierung anhand der Parteilinien an. Der Einfachheit halber nutzen wir nur eine der viele Metriken.
speeches_diversity <- textstat_lexdiv(dtm_speeches, measure = "U")
speeches.U <- left_join(speeches_diversity, speeches.stats, by = c("document" = "Text")) %>%
group_by(Party, Year) %>%
summarise(meanU = mean(U))
ggplot(speeches.U, aes(Year, meanU, color = Party)) +
geom_line() +
scale_colour_brewer(name = "Typ", palette = "Set1") +
ggtitle("Textdiversität der Antrittsreden der U.S. Präsidenten über die Zeit") +
xlab("Tag") + ylab("U-Mittelwert")
Wir sehen das die Reden der ersten Präsidenten eine hohe lexikalische Vielfalt aufgewiesen haben. Gerade die beiden Reden von Georg Washington 1789 und 1793 und die Reden von Madison 1809 und 1813 stechen hervor. Wichtig hierbei ist zu wissen, dass die Verwendung von Eigennamen, von Jargon und auch die Textlänge einen Einfluss auf die Textdiversität haben. Umso erstaunlicher ist der Befund für die Reden von Washington, ist doch seine 1789er die kürzeste Rede im gesamten Korpus mit 147 Wörtern und vier Sätzen. Man muss also davon ausgehen, dass er in seiner Rede selten ein Wort wiederholt und viele Fachbegriffe oder Fremdwörter verwendet hat. Das hat sicherlich auch damit zu tun, dass Reden im modernen Medienzeitalter ein ganz anderes und vor allem vielfältigeres Publikum ansprechen müssen.
Wie schwierig ist ein Text zu verstehen? Sind Reden von bestimmten Präsidenten einfacher zu verstehen als von anderen? Wie verständlich sind Gerichtsentscheidungen für Rechtslaien?
Diese Fragen lassen sich mit bestimmten Metriken beantworten, welche textuelle Eigenschaften untersuchen. In quanteda
lassen diese sich recht einfach mit der Funktion textstat_readability
berechnen. Schauen wir uns die Komplexität der Antrittsreden im Vergleich an:
speeches_readability <- textstat_readability(inaug_speeches, measure = "all")
head(speeches_readability)
## document ARI ARI.simple ARI.NRI Bormuth.MC Bormuth.GP Coleman
## 1 1789-Washington 32.95551 106.69538 27.13895 -11.809906 4221063245 43.04266
## 2 1793-Washington 17.77389 76.41667 14.54444 -6.265581 632096054 41.81667
## 3 1797-Adams 32.82940 106.47725 26.85691 -11.905977 4324894954 40.41730
## 4 1801-Jefferson 21.97336 84.81767 17.89308 -7.886374 1259220715 45.51185
## 5 1805-Jefferson 25.49517 91.81200 20.97245 -9.038892 1894675891 43.38102
## 6 1809-Madison 29.51554 99.84711 24.21871 -10.579749 3035921279 42.87370
## Coleman.C2 Coleman.Liau.ECP Coleman.Liau.grade Coleman.Liau.short Dale.Chall
## 1 37.70898 37.52530 12.78187 12.78320 -3.0285325
## 2 38.61296 43.30799 11.19739 11.19852 18.1939815
## 3 35.32876 39.25280 12.30853 12.30985 -2.9004890
## 4 41.05809 42.77284 11.34402 11.34523 14.3477936
## 5 38.70928 39.93897 12.12051 12.12177 8.0248421
## 6 37.81890 39.33411 12.28625 12.28754 -0.4205207
## Dale.Chall.old Dale.Chall.PSK Danielson.Bryan Danielson.Bryan.2
## 1 10.727912 9.905231 10.484647 85.76939
## 2 9.053315 8.016478 7.448070 84.66330
## 3 10.673285 9.877219 10.349469 86.57501
## 4 9.142939 8.278966 8.181849 85.82507
## 5 9.807390 8.903467 8.958105 85.26919
## 6 10.695995 9.738012 9.733724 85.87015
## Dickes.Steiwer DRP ELF Farr.Jenkins.Paterson Flesch Flesch.PSK
## 1 -927.4506 1280.9906 22.91304 -93.65752 2.034033 10.255922
## 2 -567.6168 726.5581 12.75000 -64.77832 32.205417 7.972480
## 3 -924.2814 1290.5977 24.37838 -94.21009 0.900731 10.328766
## 4 -659.7519 888.6374 14.73171 -73.30431 31.874466 8.186297
## 5 -741.1150 1003.8892 17.60000 -79.35801 23.385207 8.780701
## 6 -845.7506 1157.9749 20.71429 -87.39730 11.582181 9.599194
## Flesch.Kincaid FOG FOG.PSK FOG.NRI FORCAST FORCAST.RGL Fucks
## 1 28.43216 32.90932 18.71394 622.22935 10.52411 10.006520 307.4783
## 2 17.15176 22.09259 11.19467 31.06875 10.66667 10.163333 160.0000
## 3 28.71751 32.91611 18.80304 1011.27149 10.82938 10.342322 304.9189
## 4 19.29592 23.02834 12.86614 475.72683 10.23699 9.690694 199.8293
## 5 21.95522 25.69839 14.53641 687.76933 10.48476 9.963241 233.6000
## 6 25.56764 29.62381 16.85274 447.16119 10.54376 10.028131 272.7619
## Linsear.Write LIW nWS nWS.2 nWS.3 nWS.4 RIX Scrabble
## 1 49.91304 88.42284 15.69335 16.31664 16.68058 20.33528 16.304348 1.706789
## 2 29.00000 61.15741 11.30941 11.88745 11.67994 13.16552 9.250000 1.721875
## 3 49.08108 90.13171 15.99819 16.56644 16.63140 20.33543 17.189189 1.624590
## 4 25.95122 64.96969 11.17279 11.79297 11.47960 13.73312 9.609756 1.678183
## 5 31.02222 73.38726 12.59360 13.18455 12.82918 15.51252 12.155556 1.663654
## 6 40.38095 82.72561 14.38292 14.98762 14.89960 18.13571 14.952381 1.637395
## SMOG SMOG.C SMOG.simple SMOG.de Spache Spache.old Strain
## 1 23.30914 22.32909 22.34807 17.34807 11.029513 12.592505 31.25217
## 2 18.51114 17.77505 17.74788 12.74788 7.415343 8.400713 16.80000
## 3 23.14025 22.16846 22.18614 17.18614 11.019138 12.588847 31.64595
## 4 17.68013 16.98891 16.95113 11.95113 8.437905 9.592212 19.77073
## 5 19.03841 18.27436 18.25341 13.25341 9.292182 10.571876 22.97333
## 6 21.28021 20.40079 20.40279 15.40279 10.297177 11.737466 27.50000
## Traenkle.Bailer Traenkle.Bailer.2 Wheeler.Smith meanSentenceLength
## 1 -958.4147 -289.0435 229.1304 62.21739
## 2 -592.7418 -265.6644 127.5000 33.75000
## 3 -959.2671 -286.1880 243.7838 62.72973
## 4 -694.3015 -266.3682 147.3171 42.19512
## 5 -775.9126 -276.4466 176.0000 48.13333
## 6 -879.7402 -286.6070 207.1429 56.04762
## meanWordSyllables
## 1 1.674354
## 2 1.659259
## 3 1.681603
## 4 1.561850
## 5 1.590951
## 6 1.635514
Ähnlich der Diversitätsmetriken gibt uns die Funktion wieder eine Vielzahl an unterschiedlichen Messmethoden aus. Schauen wir uns das etwas genauer an und analysieren nur die Antrittsreden von Carter bis Trump. Der Einfachhalb halber nutzen wir auch hier nur eine Messmethode:
corp_subset <- corpus_subset(inaug_speeches, President %in% c("Obama", "Bush", "Trump", "Clinton","Reagan","Carter")) # Wir subsetten unseren Corpus mittels der Nachnamen
ndoc(corp_subset)
## [1] 11
readability <- textstat_readability(corp_subset, measure = "Flesch.Kincaid")
speeches_readability <- left_join(readability, speeches.stats, by = c("document" = "Text"))
ggplot(speeches_readability, aes(document, Flesch.Kincaid)) +
geom_boxplot() +
scale_colour_brewer(name = "Typ", palette = "Set1") +
geom_jitter(aes(document, Flesch.Kincaid, colour = Party), position = position_jitter(width = 0.4, height = 0), alpha = 0.2, size = 0.3, show.legend = F) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
xlab("") + ylab("Flesch-Kincaid-Index") +
ggtitle("Lesbarkeit/Komplexität von Antrittsreden ausgewähler U.S. Präsidenten")
Wir sehen hier, dass die Reden von Carter und Obama 2013 im Vergleich zu den Reden von Bush senior und Donald Trump schwerer verständlich waren. Dieses Bild verhärtet sich, wenn wir uns die Reden aller Präsidenten, visualisiert nach Parteien, anschauen:
readability <- textstat_readability(inaug_speeches, measure = "Flesch.Kincaid") # wir nutzen hier den gesamten corpus
speeches_readability <- left_join(readability, speeches.stats, by = c("document" = "Text"))
ggplot(speeches_readability, aes(Party, Flesch.Kincaid)) +
geom_boxplot() +
scale_colour_brewer(name = "Typ", palette = "Set1") +
geom_jitter(aes(Party, Flesch.Kincaid, colour = Party), position = position_jitter(width = 0.4, height = 0), alpha = 0.2, size = 0.3, show.legend = F) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
xlab("") + ylab("Flesch-Kincaid-Index") +
ggtitle("Lesbarkeit/Komplexität von Antrittsreden der U.S. Präsidenten nach Parteien")
Wir sehen sehr deutlich, dass die Vertreter der modernen U.S. Parteien (Demokratien, Republikaner) deutlich weniger komplexe Reden gehalten haben als ihre historischen Counterparts. Damit steht dieses Ergebnis im Einklang mit der aktuellen Forschung z.B. von Benoit et al.. Die Autoren haben anschaulich zeigen können, dass die Wahrscheinlichkeit über Zeit steigt, dass eine "State of the Union Address" eines U.S. Präsidenten einfacher zu verstehen ist als ein grundlegender Text für SchülerInnen in der fünften Klasse.