17 Einführung und Grundbegriffe

Besonders in den Sozialwissenschaften hat die automatisierte Inhaltsanalyse großer Textmengen in den vergangenen Jahren stark an Bedeutung gewonnen, da mit den zugehörigen Verfahren ganz neue Datenbestände – oder endlich in gebührendem Umfang – analysiert werden können. Zugleich haben sich in den vergangenen Jahren einige Packages als Standardwerkzeug in R empfehlen können und somit einige Prozesse vereinheitlicht.

Zu den beiden relevantesten Packages für die automatisierte Inhaltsanalyse zählen quanteda (für Quantitative Analysis of Textual Data) und tidytext (in Anlehnung an das Tidyverse), die insbesondere das Handling von Textdaten sowie die vorbereitenden Schritte (auch als Preprocessing bezeichnet) für spezifischere Verfahren deutlich erleichtern. Quanteda hat dabei den größeren Funktionsumfang und wird daher auch unser Primärpackage in den nächsten Kapiteln sein. Tidytext enthält zwar auch Funktionen für die zentralsten Handlings- und Vorbereitungsschritte, zeigt seinen Wert aber vor allem in der Konvertierung von Textdaten in tidy data und wird somit für uns insbesondere dann relevant, wenn Ergebnisse z. B. mittels ggplot2 visualisiert werden sollen.

Zunächst installieren wir beide Packages:

install.packages(c("quanteda", "tidytext"))

Und natürlich müssen wir diese auch mit dem bekannten library()-Befehl laden. Wir laden zudem erneut das Tidyverse:

library(tidyverse)
library(tidytext)
library(quanteda)

Als Beispiel-Datensatz verwenden wir Tweets von Donald Trump und Joe Biden, die diese dieses Jahr (bis einschließlich 24. Juni) abgesetzt haben, bereinigt um Retweets. Die Daten sind über Moodle als trump_biden_tweets_2020.csv verfügbar.

tweets <- read_csv("data/trump_biden_tweets_2020.csv")
tweets
## # A tibble: 4,153 x 7
##       id account  link                        content                                                                   date        retweet_count favorite_count
##    <dbl> <chr>    <chr>                       <chr>                                                                     <chr>               <dbl>          <dbl>
##  1     1 JoeBiden https://twitter.com/JoeBid~ "Our final fundraising deadline of 2019 is just hours away and we need y~ 2020/01/01~           278            975
##  2     2 JoeBiden https://twitter.com/JoeBid~ "Every single human being deserves to be treated with dignity. Everyone.~ 2020/01/01~          2523          11900
##  3     3 JoeBiden https://twitter.com/JoeBid~ "With just over one month until the Iowa Caucus, we need all hands on de~ 2020/01/02~           382           1509
##  4     4 JoeBiden https://twitter.com/JoeBid~ "This election is about the soul of our nation — and Donald Trump is poi~ 2020/01/02~         10545          45928
##  5     5 JoeBiden https://twitter.com/JoeBid~ "Every day that Donald Trump remains in the White House puts the future ~ 2020/01/02~          2071           9825
##  6     6 JoeBiden https://twitter.com/JoeBid~ "It was a privilege to work with @JulianCastro during the Obama Administ~ 2020/01/02~          2359          17534
##  7     7 JoeBiden https://twitter.com/JoeBid~ "Like Vicky said, we need a president who will restore integrity to the ~ 2020/01/02~          1344           6835
##  8     8 JoeBiden https://twitter.com/JoeBid~ "I'm excited to share that we raised $22.7 million this last quarter — o~ 2020/01/02~          2448          14719
##  9     9 JoeBiden https://twitter.com/JoeBid~ "If you're a teacher or a firefighter, you're probably paying more in ta~ 2020/01/02~          3043          18565
## 10    10 JoeBiden https://twitter.com/JoeBid~ "Before the holidays, Jill walked across the Gateway International Bridg~ 2020/01/03~          1165           3231
## # ... with 4,143 more rows

Wie wir sehen, ist die Fallebene der einzelne Tweet. Für jeden Tweet haben wir eine numerische id, den account (realDonaldTrump oder JoeBiden), den link zum Tweet, den Text des Tweets (content), Veröffentlichungsdatum und -uhrzeit (date) sowie die Anzahl der retweets und favorites. Insgesamt liegen uns 4153 Tweets, davon 2654 von Trump und 1499 von Joe Biden, vor.

Wir setzen uns nun zunächst mit einigen Grundbegriffen und -konzepten auseinander, bevor wir in den kommenden Kapiteln unterschiedliche Analyseverfahren an den Daten ausprobieren.

17.1 Korpora und Dokumente

Ziel der Inhaltsanalyse ist die Untersuchung mehrerer Textdokumente, wobei es sich dabei um Bücher, Artikel, Redetranskripte etc., in unserem Fall um Tweets, handeln kann. Die Sammlung aller Dokumente, die wir in unsere Analyse einbeziehen möchten, wird als Korpus bezeichnet.

In Quanteda gibt es für Korpora einen spezifischen Objekttypen, den wir mit der Funktion corpus() erzeugen können. Wenn wir hierfür einen Dataframe bzw. ein Tibble an Texten verwenden möchten, geben wir mit den Argumenten docid_field die Spalte an, in der die ID des Dokuments steht, und identifzieren den Text des jeweiligen Dokuments über das Argument text_field:

tweets_corpus <- corpus(tweets, docid_field = "id", text_field = "content")

Das so erzeugte corpus-Objekt enthält für jedes Dokument einen Eintrag. Alle anderen Variablen aus dem Ursprungsdatensatz werden automatisch als sogenannte docvars hinterlegt und können jederzeit über die Funktion docvars() abgerufen werden.

tweets_corpus
## Corpus consisting of 4,153 documents and 5 docvars.
## 1 :
## "Our final fundraising deadline of 2019 is just hours away an..."
## 
## 2 :
## "Every single human being deserves to be treated with dignity..."
## 
## 3 :
## "With just over one month until the Iowa Caucus, we need all ..."
## 
## 4 :
## "This election is about the soul of our nation — and Donald T..."
## 
## 5 :
## "Every day that Donald Trump remains in the White House puts ..."
## 
## 6 :
## "It was a privilege to work with @JulianCastro during the Oba..."
## 
## [ reached max_ndoc ... 4,147 more documents ]

corpus-Objekte verfügen über eine eigene summary()-Methode, mit der wir uns bereits erste Statistiken über jedes Dokument im Korpus und die zugehörigen Docvars ausgeben lassen können:

summary(tweets_corpus, n = 5) %>% # Anzeige auf die ersten 5 Dokumente beschränken
  as_tibble() 
## # A tibble: 5 x 9
##   Text  Types Tokens Sentences account  link                                                    date                retweet_count favorite_count
##   <chr> <int>  <int>     <int> <chr>    <chr>                                                   <chr>                       <dbl>          <dbl>
## 1 1        43     49         3 JoeBiden https://twitter.com/JoeBiden/status/1212180387260010496 2020/01/01 01:15:00           278            975
## 2 2        33     45         4 JoeBiden https://twitter.com/JoeBiden/status/1212442112219844609 2020/01/01 18:35:00          2523          11900
## 3 3        38     39         2 JoeBiden https://twitter.com/JoeBiden/status/1212524152608833536 2020/01/02 00:01:00           382           1509
## 4 4        17     20         1 JoeBiden https://twitter.com/JoeBiden/status/1212540258681851905 2020/01/02 01:05:00         10545          45928
## 5 5        33     37         2 JoeBiden https://twitter.com/JoeBiden/status/1212556035283705858 2020/01/02 02:07:41          2071           9825

Wir bereiten unseren Textkorpus nun für weitere Analysen vor; die folgenden Schritte werden dabei auch als Preprocessing bezeichnet.

17.2 Tokenization, Stopwords und n-Gramme

Unter Tokenization versteht man die Aufspaltung eines Textstrings in kleinere Bestandteile. In den meisten Fällen wird als Token das einzelne Wort gewählt, wir können aber beispielsweise Texte auch in Sätze oder einzelne Zeichen aufteilen. Sehen wir uns die obige Ausgabe nochmals an, so sehen wir, dass für jeden Tweet bereits die Anzahl der Tokens (in diesem Fall also Wörter) sowie der Types (einzigartige Wörter) und Sentences (Sätze) angegeben ist.

Die meisten Verfahren, die wir noch kennenlernen werden, arbeiten nach dem sogenannten Bag-of-Words-Modell, womit Texte als – bildlich gesprochen – Wortbeutel betrachtet werden, in denen die einzelnen Wörter und deren Anzahl eine Rolle spielen, nicht jedoch deren syntaktischen und grammatikalischen Zusammenhänge. Für all diese Verfahren müssen Texte daher zunächst in einzelne Wörter tokenisiert werden. In Quanteda erledigen wir dies mit der Funktion tokens(), die standardmäßig nach Wörtern tokenisiert:

tweet_tokens <- tokens(tweets_corpus)
tweet_tokens
## Tokens consisting of 4,153 documents and 5 docvars.
## 1 :
##  [1] "Our"         "final"       "fundraising" "deadline"    "of"          "2019"        "is"          "just"        "hours"       "away"        "and"        
## [12] "we"         
## [ ... and 37 more ]
## 
## 2 :
##  [1] "Every"    "single"   "human"    "being"    "deserves" "to"       "be"       "treated"  "with"     "dignity"  "."        "Everyone"
## [ ... and 33 more ]
## 
## 3 :
##  [1] "With"   "just"   "over"   "one"    "month"  "until"  "the"    "Iowa"   "Caucus" ","      "we"     "need"  
## [ ... and 27 more ]
## 
## 4 :
##  [1] "This"     "election" "is"       "about"    "the"      "soul"     "of"       "our"      "nation"   "-"        "and"      "Donald"  
## [ ... and 8 more ]
## 
## 5 :
##  [1] "Every"   "day"     "that"    "Donald"  "Trump"   "remains" "in"      "the"     "White"   "House"   "puts"    "the"    
## [ ... and 25 more ]
## 
## 6 :
##  [1] "It"             "was"            "a"              "privilege"      "to"             "work"           "with"           "@JulianCastro"  "during"        
## [10] "the"            "Obama"          "Administration"
## [ ... and 43 more ]
## 
## [ reached max_ndoc ... 4,147 more documents ]

Wie wir sehen, wurden die Tweets in einzelne Wörter (und Symbole) aufgeteilt. Der Standard-Tokenizer von Quanteda ist hier insofern komfortabel, als dass Mentions und Hashtags (siehe z. B. Tweet 6) beibehalten werden. Allerdings sind auch noch Bestandteile enthalten, die wir im Sinne des Bag-of-Words-Ansatzes nicht benötigen. Darunter fallen beispielsweise Satzzeichen, Ziffern und Symbole sowie URLs. Wir können diese Bestandteile beim Tokenisieren durch entsprechende remove_-Argumente entfernen.

tweet_tokens <- tokens(tweets_corpus, 
                       remove_punct = TRUE,   # Entfernt Satzzeichen
                       remove_numbers = TRUE, # Entfernt Ziffern
                       remove_symbols = TRUE, # Entfernt Symbole (darunter auch Emojis)
                       remove_url = TRUE)     # Entfernt URLs
tweet_tokens
## Tokens consisting of 4,153 documents and 5 docvars.
## 1 :
##  [1] "Our"         "final"       "fundraising" "deadline"    "of"          "is"          "just"        "hours"       "away"        "and"         "we"         
## [12] "need"       
## [ ... and 30 more ]
## 
## 2 :
##  [1] "Every"    "single"   "human"    "being"    "deserves" "to"       "be"       "treated"  "with"     "dignity"  "Everyone" "The"     
## [ ... and 24 more ]
## 
## 3 :
##  [1] "With"   "just"   "over"   "one"    "month"  "until"  "the"    "Iowa"   "Caucus" "we"     "need"   "all"   
## [ ... and 23 more ]
## 
## 4 :
##  [1] "This"     "election" "is"       "about"    "the"      "soul"     "of"       "our"      "nation"   "and"      "Donald"   "Trump"   
## [ ... and 5 more ]
## 
## 5 :
##  [1] "Every"   "day"     "that"    "Donald"  "Trump"   "remains" "in"      "the"     "White"   "House"   "puts"    "the"    
## [ ... and 22 more ]
## 
## 6 :
##  [1] "It"             "was"            "a"              "privilege"      "to"             "work"           "with"           "@JulianCastro"  "during"        
## [10] "the"            "Obama"          "Administration"
## [ ... and 37 more ]
## 
## [ reached max_ndoc ... 4,147 more documents ]

Unsere so erstellten Tokens lassen sich nun noch weiter verfeinern. Falls Groß-/Kleinschreibung nicht explizit zum Forschungsinteresse gehört, ist es sinnvoll, alle Tokens in Kleinschreibung zu konvertieren, sodass beispielsweise "trump", "Trump" und "TRUMP" als derselbe Token gezählt werden. Hierfür wenden wir auf die Tokens die Funktion tokens_tolower() an:

tweet_tokens_LC <- tweet_tokens %>% 
  tokens_tolower()

tweet_tokens_LC
## Tokens consisting of 4,153 documents and 5 docvars.
## 1 :
##  [1] "our"         "final"       "fundraising" "deadline"    "of"          "is"          "just"        "hours"       "away"        "and"         "we"         
## [12] "need"       
## [ ... and 30 more ]
## 
## 2 :
##  [1] "every"    "single"   "human"    "being"    "deserves" "to"       "be"       "treated"  "with"     "dignity"  "everyone" "the"     
## [ ... and 24 more ]
## 
## 3 :
##  [1] "with"   "just"   "over"   "one"    "month"  "until"  "the"    "iowa"   "caucus" "we"     "need"   "all"   
## [ ... and 23 more ]
## 
## 4 :
##  [1] "this"     "election" "is"       "about"    "the"      "soul"     "of"       "our"      "nation"   "and"      "donald"   "trump"   
## [ ... and 5 more ]
## 
## 5 :
##  [1] "every"   "day"     "that"    "donald"  "trump"   "remains" "in"      "the"     "white"   "house"   "puts"    "the"    
## [ ... and 22 more ]
## 
## 6 :
##  [1] "it"             "was"            "a"              "privilege"      "to"             "work"           "with"           "@juliancastro"  "during"        
## [10] "the"            "obama"          "administration"
## [ ... and 37 more ]
## 
## [ reached max_ndoc ... 4,147 more documents ]

In der Regel möchten wir mittels automatisierter Verfahren Wörter bzw. Tokens herausarbeiten, die in gewisser Weise distinkt für bestimmte Dokumente bzw. Gruppen von Dokumenten in dem untersuchten Korpus sind. Das heißt auch, dass bestimmte Worttypen keinen Informationsgewinn für uns liefern, da sie vielfach in allen Texten vorkommen, z. B. Artikel, Konjunktionen und Präpositionen. Man bezeichnet diese Wörter auch als Stopwords. Quanteda enthält etablierte Stopwords-Sammlungen für unterschiedliche Sprachen, die wir über die Funktion stopwords() abrufen können:

stopwords("english")
##   [1] "i"          "me"         "my"         "myself"     "we"         "our"        "ours"       "ourselves"  "you"        "your"       "yours"     
##  [12] "yourself"   "yourselves" "he"         "him"        "his"        "himself"    "she"        "her"        "hers"       "herself"    "it"        
##  [23] "its"        "itself"     "they"       "them"       "their"      "theirs"     "themselves" "what"       "which"      "who"        "whom"      
##  [34] "this"       "that"       "these"      "those"      "am"         "is"         "are"        "was"        "were"       "be"         "been"      
##  [45] "being"      "have"       "has"        "had"        "having"     "do"         "does"       "did"        "doing"      "would"      "should"    
##  [56] "could"      "ought"      "i'm"        "you're"     "he's"       "she's"      "it's"       "we're"      "they're"    "i've"       "you've"    
##  [67] "we've"      "they've"    "i'd"        "you'd"      "he'd"       "she'd"      "we'd"       "they'd"     "i'll"       "you'll"     "he'll"     
##  [78] "she'll"     "we'll"      "they'll"    "isn't"      "aren't"     "wasn't"     "weren't"    "hasn't"     "haven't"    "hadn't"     "doesn't"   
##  [89] "don't"      "didn't"     "won't"      "wouldn't"   "shan't"     "shouldn't"  "can't"      "cannot"     "couldn't"   "mustn't"    "let's"     
## [100] "that's"     "who's"      "what's"     "here's"     "there's"    "when's"     "where's"    "why's"      "how's"      "a"          "an"        
## [111] "the"        "and"        "but"        "if"         "or"         "because"    "as"         "until"      "while"      "of"         "at"        
## [122] "by"         "for"        "with"       "about"      "against"    "between"    "into"       "through"    "during"     "before"     "after"     
## [133] "above"      "below"      "to"         "from"       "up"         "down"       "in"         "out"        "on"         "off"        "over"      
## [144] "under"      "again"      "further"    "then"       "once"       "here"       "there"      "when"       "where"      "why"        "how"       
## [155] "all"        "any"        "both"       "each"       "few"        "more"       "most"       "other"      "some"       "such"       "no"        
## [166] "nor"        "not"        "only"       "own"        "same"       "so"         "than"       "too"        "very"       "will"

Über die Funktion tokens_remove() können wir eigens definierte Tokens aus den erstellten Tokens entfernen, eben beispielsweise Stoppwörter. Zu beachten ist, dass dadurch bestimmte Zusammenhänge in den Texten nicht mehr erkennbar sind; wie oben jedoch angesprochen, folgen die meisten automatischen Verfahren dem Bag-of-Words-Modell, sodass diese Zusammenhänge keine Berücksichtigung finden würden. Allerdings kann für bestimmte Dokumente jeglicher Inhalt verloren gehen, wie hier im Beispiel der fünfte Tweet zeigt:

tweet_tokens_reduced <- tweet_tokens_LC %>% 
  tokens_remove(stopwords("english"))
tweet_tokens_reduced
## Tokens consisting of 4,153 documents and 5 docvars.
## 1 :
##  [1] "final"       "fundraising" "deadline"    "just"        "hours"       "away"        "need"        "help"        "every"       "donation"    "big"        
## [12] "small"      
## [ ... and 16 more ]
## 
## 2 :
##  [1] "every"        "single"       "human"        "deserves"     "treated"      "dignity"      "everyone"     "poor"         "powerless"    "marginalized"
## [11] "vulnerable"   "least"       
## [ ... and 4 more ]
## 
## 3 :
##  [1] "just"     "one"      "month"    "iowa"     "caucus"   "need"     "hands"    "deck"     "talk"     "folks"    "stake"    "election"
## [ ... and 5 more ]
## 
## 4 :
## [1] "election" "soul"     "nation"   "donald"   "trump"    "poison"   "soul"    
## 
## 5 :
##  [1] "every"   "day"     "donald"  "trump"   "remains" "white"   "house"   "puts"    "future"  "planet"  "risk"    "beat"   
## [ ... and 7 more ]
## 
## 6 :
##  [1] "privilege"      "work"           "@juliancastro"  "obama"          "administration" "true"           "honor"          "talented"       "field"         
## [10] "candidates"     "led"            "historic"      
## [ ... and 11 more ]
## 
## [ reached max_ndoc ... 4,147 more documents ]

Weitere häufig durchgeführte Preprocessing-Schritte sind Stemming oder Lemmatization. Beim Stemming werden Wörter um Prefixe und Suffixe bereinigt und somit auf ihren Wortstamm reduziert (z. B. werden aus “Beispiel”, “Beispiele” und “[des] Beispiels” jeweils “Beispiel”). Dies wird meist über Algorithmen erreicht, die auf eher heuristischen Regeln basieren, beispielsweise alle “-ing”-Endung etc. abschneiden. So können z. B. Singular- und Plurarlformen desselben Wortes auf einen gemeinsamen Stamm reduziert werden und anschließend als derselbe Token behandelt werden; allerdings scheitern diese Algorithmen häufig an unregelmäßigen Verben oder auch Eigennamen und die Interpretation einzelner Tokens kann bisweilen erschwert werden. Über die Funktion tokens_wordstem() bietet Quanteda verschiedene Stemming-Algorithmen an

tweet_tokens_reduced %>% 
  tokens_wordstem()
## Tokens consisting of 4,153 documents and 5 docvars.
## 1 :
##  [1] "final"    "fundrais" "deadlin"  "just"     "hour"     "away"     "need"     "help"     "everi"    "donat"    "big"      "small"   
## [ ... and 16 more ]
## 
## 2 :
##  [1] "everi"     "singl"     "human"     "deserv"    "treat"     "digniti"   "everyon"   "poor"      "powerless" "margin"    "vulner"    "least"    
## [ ... and 4 more ]
## 
## 3 :
##  [1] "just"   "one"    "month"  "iowa"   "caucus" "need"   "hand"   "deck"   "talk"   "folk"   "stake"  "elect" 
## [ ... and 5 more ]
## 
## 4 :
## [1] "elect"  "soul"   "nation" "donald" "trump"  "poison" "soul"  
## 
## 5 :
##  [1] "everi"  "day"    "donald" "trump"  "remain" "white"  "hous"   "put"    "futur"  "planet" "risk"   "beat"  
## [ ... and 7 more ]
## 
## 6 :
##  [1] "privileg"      "work"          "@juliancastro" "obama"         "administr"     "true"          "honor"         "talent"        "field"        
## [10] "candid"        "led"           "histor"       
## [ ... and 11 more ]
## 
## [ reached max_ndoc ... 4,147 more documents ]

Lemmatization (bzw. Lemmatisierung) ist die anspruchsvollere Variante und führt Tokens auf ihren morphologischen Wortstamm (d.h., die Form, in der das jeweilige Wort im Wörterbuch zu finden ist) zurück (so würden also “ist”, “bin”, “bist” etc. allesamt auf “sein” zurückgeführt werden). Lemmatization ist daher die deutlich validere Variante, allerdings auch entsprechend aufwändiger und nur durch – oftmals auch selbst erstellte – Dictionaries zu bewältigen, in denen für alle relevanten Wörter die jeweilige morphologische Grundform hinterlegt ist und anhand derer dann Tokens ersetzt werden (in Quanteda mit der Funktion tokens_replace()). Wir werden diese beiden Preprocessing-Schritte jedoch vorerst nicht anwenden, da sich oftmals auch ohne sie bereits recht gute Ergebnisse erzielen lassen.

Um auch im Bag-of-Words-Ansatz Zusammenhänge zwischen Begriffen abbilden zu können, können weitere n-Gramme definiert werden. n-Gramme bezeichnen die Anzahl an aufeinanderfolgenden Text-Fragmenten, die beim Tokenisieren berücksichtigt werden sollen. Trennen wir unseren Text also in einzelne Wörter, betrachten wir Unigramme. Wir können jedoch auch angeben, das zusätzlich Bigramme (Abfolgen von zwei Wörtern), Trigramme (Abfolgen von drei Wörtern) etc. berücksichtigt werden sollen. Dies ist sinnvoll, wenn wir annehmen, dass auch Wortkombinationen distinkt für bestimmte Dokumente sind (sich beispielsweise die Trump-Tweets nicht nur durch “MAGA”, sondern auch “Crooked Hillary” auszeichnen.).

In Quanteda können wir nach der initalen Tokenisierung auch weitere n-Gramme mit der Funktion tokens_ngrams erstellen lassen:

tweet_tokens_bigrams <- tweet_tokens_reduced %>% 
  tokens_ngrams(n = c(1, 2)) # Erzeuge Uni- und Bigramme
tweet_tokens_bigrams
## Tokens consisting of 4,153 documents and 5 docvars.
## 1 :
##  [1] "final"       "fundraising" "deadline"    "just"        "hours"       "away"        "need"        "help"        "every"       "donation"    "big"        
## [12] "small"      
## [ ... and 43 more ]
## 
## 2 :
##  [1] "every"        "single"       "human"        "deserves"     "treated"      "dignity"      "everyone"     "poor"         "powerless"    "marginalized"
## [11] "vulnerable"   "least"       
## [ ... and 19 more ]
## 
## 3 :
##  [1] "just"     "one"      "month"    "iowa"     "caucus"   "need"     "hands"    "deck"     "talk"     "folks"    "stake"    "election"
## [ ... and 21 more ]
## 
## 4 :
##  [1] "election"      "soul"          "nation"        "donald"        "trump"         "poison"        "soul"          "election_soul" "soul_nation"  
## [10] "nation_donald" "donald_trump"  "trump_poison" 
## [ ... and 1 more ]
## 
## 5 :
##  [1] "every"   "day"     "donald"  "trump"   "remains" "white"   "house"   "puts"    "future"  "planet"  "risk"    "beat"   
## [ ... and 25 more ]
## 
## 6 :
##  [1] "privilege"      "work"           "@juliancastro"  "obama"          "administration" "true"           "honor"          "talented"       "field"         
## [10] "candidates"     "led"            "historic"      
## [ ... and 33 more ]
## 
## [ reached max_ndoc ... 4,147 more documents ]

(Es werden hier immer nur die ersten Elemente bzw. Tokens des jeweiligen Textvektors angezeigt; in Tweet 4 sehen wir aber auch einige der erzeugten Bigramme.)

17.3 Dokument-Feature-Matrizen (DFMs)

Die meisten Verfahren, die wir kennenlernen werden, arbeiten mit sogenannten Dokument-Feature-Matrizen, kurz DFM, als Input. Hierfür wird eine Matrix erstellt, die in den Zeilen alle Dokumente im Korpus und in Spalten alle erzeugten Tokens enthält und in den Zellen dann festhält, wie häufig das jeweilige Token (bzw. Feature) im jeweiligen Dokument vorkommt. Wir erzeugen DFMs in Quanteda durch die Funktion dfm():

tweets_dfm <- dfm(tweet_tokens_bigrams)
tweets_dfm
## Document-feature matrix of: 4,153 documents, 49,341 features (99.9% sparse) and 5 docvars.
##     features
## docs final fundraising deadline just hours away need help every donation
##    1     1           1        1    1     1    1    1    2     1        1
##    2     0           0        0    0     0    0    0    0     1        0
##    3     0           0        0    1     0    0    1    0     0        0
##    4     0           0        0    0     0    0    0    0     0        0
##    5     0           0        0    0     0    0    0    0     1        0
##    6     0           0        0    0     0    0    0    0     0        0
## [ reached max_ndoc ... 4,147 more documents, reached max_nfeat ... 49,331 more features ]

Wie wir sehen erzeugt dies eine sehr große Matrix: die 4153 Tweets im Korpus enthalten insgesamt 49341 einzigartige Features (in unserem Fall Uni- und Bigramme). Wir sehen außerdem bereits in dieser abgeschnittenen Ansicht, dass die meisten Zellen eine 0 enthalten, d. h. pro Dokument kommt der Großteil der Features nicht vor. Den Anteil der leeren Zellen (bzw. 0-Zellen) wird auch als Sparsitiy der DFM bezeichnet – in unserem Fall enthalten 99.9% aller Zellen eine 0.

Diese DFM können wir nutzen, um bereits erste einfache Analysen durchzuführen. Die Funktion topfeatures() extrahiert beispielsweise die am häufigsten vorkommenden Features:

topfeatures(tweets_dfm)
##        great        trump    president       people        thank       donald         need         just          now donald_trump 
##          619          512          449          423          380          352          338          325          308          302

Etwas aussagekräftiger wird das Ergebnis, wenn wir mit dem Argument groups die Ausgabe gruppieren. Praktischerweise haben wir ja in den Docvars den Account gespeichert, sodass wir nun die Top-Features getrennt für Trump und Biden ausgeben lassen können:

topfeatures(tweets_dfm, groups = "account")
## $JoeBiden
##        trump       donald    president donald_trump         need       nation          one          can       people      country 
##          396          342          307          294          282          188          183          178          174          170 
## 
## $realDonaldTrump
##     great     thank    people      news      fake      just       now       big       new fake_news 
##       589       309       249       238       196       193       181       168       160       151

Wir sehen: Joe Biden twittert offenbar vor allem über Donald Trump, wohingegen der sich vor allem wohlbekannter Trumpisms (“great”, “big”, “fake news”) bedient.

Mittels dfm_select() können wir die DFM zudem anhand Mustern filtern und z. B. lediglich Mentions (beginnen mit @) auswählen, um zu sehen, auf wen sich beiden Kandidaten mit ihren Tweets am häufigsten beziehen:45

dfm_mentions <- dfm_select(tweets_dfm, "@*")
topfeatures(dfm_mentions, groups = "account")
## $JoeBiden
##             @nra         @drbiden   @petebuttigieg         @ewarren @realdonaldtrump         @teamjoe      @andrewyang    @senatemajldr        @nra_pass 
##               21               15                7                7                5                5                5                5                4 
##             @cnn 
##                4 
## 
## $realDonaldTrump
##        @foxnews  @foxandfriends        @nytimes          @usdot            @cnn    @seanhannity @mariabartiromo           @oann  @ingrahamangle @washingtonpost 
##             108              40              31              31              30              14              12              12              11              11

Im kommenden Kapitel werden wir uns basierend auf diesen Grundlagen detaillierter mit Text- und Wortmetriken auseinandersetzen, um Unterschiede und Gemeinsamkeiten in den Tweets der beiden Kandidaten zu analysieren.

17.4 Übungsaufgaben

Erstellen Sie für die folgenden Übungsaufgaben eine eigene Skriptdatei oder eine R-Markdown-Datei und speichern diese als ue17_nachname.R bzw. ue17_nachname.Rmd ab.

Laden Sie den Datensatz facebook_europawahl.csv und filtern Sie lediglich Posts der im Bundestag vertretenen Parteien.


Übungsaufgabe 17.1 Korpus erstellen:

Erstellen Sie mit Quanteda einen Korpus für die Facebook-Posts.


Übungsaufgabe 17.2 Tokenization:

Erstellen Sie Tokens für den Korpus. Dabei sollen:

  • alle URLs, Symbole, Satzzeichen und Ziffern entfernt werden
  • die Tokens in Kleinschreibung umgewandelt werden
  • deutsche Stoppwörter entfernt werden
  • Uni-, Bi- und Trigramme erstellt werden

Übungsaufgabe 17.3 DFMs:

Erstellen Sie eine DFM auf Basis der oben erzeugten Tokens. Beantworten Sie anhand der DFM folgende Fragen:

  • Was sind die häufigsten Features je Partei?
    • Hat es sich gelohnt, neben Uni- auch Bi- und Trigramme zu betrachten?
    • Fallen Ihnen Probleme auf?
  • Was sind die häufigsten Hashtags über alle Posts hinweg und nach Partei getrennt betrachtet?

  1. per Default erkennt dfm_select() sogenannte “glob-style wildcards”, mit denen etwa ein Asterisk * als einfacher Platzhalter definiert werden kann. Um RegEx-Muster (siehe Kapitel 12.2) zu nutzen, muss valuetype="regex" angegeben werden.↩︎