Furchtlose Grammatiker – Textmining im tidyverse

David Schlepps Blog, Data Science

Das methodische Schaffenswerk Karl Pearsons ist durchaus bekannt – kaum ein Student einer Disziplin mit quantitativen Spielarten wird am Namen des ersten Statistik-Lehrstuhlinhabers der Welt vorbeikommen. Durchaus weniger bekannt ist jedoch Pearsons Werk The Grammar of Science und seine Anschauungen zum – ihm zufolge vornehmlich deskriptiven statt erklärenden – Wesen der wissenschaftlichen Methode. Diese vielfältigen Beiträge zur Erkenntnistheorie inspirierten nachweislich die Arbeit Albert Einsteins, ebenso wie die anderer namenhafter Wissenschaftler.

Karl Pearson 1910

Zugegeben, das 540-Seiten-starke Buch liest sich nicht unbedingt in der Mittagspause – können uns moderne Data Science Methoden dabei helfen, uns dem mehr als 200-Jahre-alten Grundsatzwerk schneller anzunähern? Das Manuskript des berühmten UCL-Professors ist frei im Netz zugänglich und stellt damit die ideale Textgrundlage für unsere ersten Textmining-Schritte im tidyverse dar. In diesem Beitrag wollen wir uns um vorbereitende Schritte im Textmining kümmern und unsere ersten Gehversuche im Bereich der Aufbereitung und Visualisierung machen.

Mit dem tidytext-Paket von Julia Silge und David Robinson gehen diese aber wirklich sehr schnell (ihre großartige Einführung in das Paket und das Thema Textmining finden Sie hier). Nach dem Laden (bzw. Installieren) der Pakete tidytext, dplyr (zur allgemeinen Datenaufbereitung) und tibble (gemeinsam enthalten im Paket tidyverse) sind wir bereit, die ersten Textaufbereitungsschritte zu gehen. Ein Download von The Grammar of Science als .txt-File lässt sich ganz angenehm mit R durchführen.

# load packages 
library(tidytext) 
library(tidyverse) 

# tell R where your .txt-File should go, dowload the file and give it a name 
setwd("/Users/obiwan/jedi_documents") 
download.file("https://archive.org/stream/grammarofscience00pearuoft/
               grammarofscience00pearuoft_djvu.txt",
              destfile = "grammar.txt")

Anschließend lesen wir mit readChar() das .txt-File aus und bereinigen im Text vorhandene Absätze (durch “\n” gekennzeichnet). Wichtig für die weitere Verarbeitung ist auch, dass wir einen tibble mit einer character-Variable erstellen, mit der wir im Folgenden weiterarbeiten.

# read the .txt contents and clean line breaks 
grammar_os_text <- readChar('grammar.txt', file.info('grammar.txt')$size) 
grammar_os_text <- gsub("\n", "", paste(grammar_os_text, collapse = "\n")) 

# create a tibble with a character vector 
grammar_os_df <- tibble(content = grammar_os_text)

Im Weiteren zerlegen wir mit der tidytext-Funktion unnest_tokens() den Text in Tokens – in unserem Fall die einzelnen Worte im Text, die Standardeinstellung der Funktion. Die Funktion ist allerdings deutlich vielseitiger und kann beispielsweise Paragraphen, Sätze oder Wortteile nach regulären Ausdrücken zerlegen und ausgeben. Ein Blick auf den Datensatz zeigt, dass wir die ersten Worte der Titelseite (sowie den restlichen Inhalt des Buches) als Zeilen in unserem Datensatz angelegt haben.

# zerlegen in einzelne Woerter
grammar_os_df <- grammar_os_df %>% unnest_tokens(word, content) 
head(grammar_os_df) 

# Ausgabe
       word 
1       the 
1.1 grammar 
1.2      of 
1.3 science 
1.4   first 
1.5 edition 

Selbstverständlich sind wir mit der Bereinigung der word-Variable noch am Anfang. Nach dem Laden des tokenizers-Paketes, haben wir nun die Möglichkeit, sogenannte Stop Words ("a", "an", "and", "are", etc.) aus dem Datensatz herauszufiltern. Hierbei ist uns die dplyr-Funktion anti_join() behilflich, welche hier alle Reihen aus dem ersten Argument (unser grammar_os_df) ausgibt, welche nicht im zweiten Argument (ein Datensatz aus Stop Words) vorhanden sind.

# Stop words
library(tokenizers) 

data(stop_words) 
grammar_os_df <- anti_join(grammar_os_df, tibble(word = stopwords("en"))) 

Nachdem wir die ersten Bereinigungen unserer Daten vorgenommen haben, sind wir nun bereit, Sentiment-Lexika zu verwenden – wenn man so will Wörterbücher. Praktischerweise steht bei tidytext hierfür direkt der Befehl get_sentiments() zur Verfügung. Dieser erlaubt es uns, vier unterschiedliche Sentiment-Bibliotheken im tidy-Format zu laden, bei denen pro Eintrag eine Zeile besteht.

Zur Auswahl steht erstens bing, ein Lexikon mit zwei Klassen: Positive und negative Worte. Zweitens afinn, ein Lexikon mit Ratings auf einer Skala von minus fünf (negativ) bis fünf (positiv). Die letzten beiden Möglichkeiten sind nrc und loughran, zwei Lexica welche etwas nuanciertere Klassifikationen bieten. Das Lexikon nrc bietet zehn Kategorien (joy, fear, disgust, anticipation, anger, trust, surprise, sadness, positive, negative), während loughran sechs Kategorien (litigious, constraining, superfluous, uncertainty, sowie positive und negative) bietet. Eine beispielhafte Klassifizierung aus unserem tidytext sieht wie folgt aus:

word sentiment
displeased sadness
superficial negative
university anticipation

Eine sehr simple Form der Analyse unserer Textdaten beruht dementsprechend auf einem simplen inner_join() des aufbereiteten Datensatzes und den zuvor genannten Lexika. Mit einem so gematchen Datensatz können wir aufschlussreiche erste Visualisierungen machen. Im Folgenden Code-Snippet erledigen wir folgende Schritte:

  • Aufrufen des nrc-Lexikons, sowie inner_join() von nrc-Lexikon und dem grammar_os_df in einem Schritt
  • Auszählung von Worten gruppiert nach Sentiment (auch dplyr::count() könnte hier als eine Kurzform der Kombination von group_by() und tally() verwendet werden)
  • Erstellung einer Termmatrix, d.h. Konversion des Long- zu einem Wide-Datensatz, in dem eine Spalte pro Sentiment erstellt wird und leere Zeilen mit Nullen gefüllt werden. Da das wordcloud-Paket eine Matrix mit Zeilennamen benötigt, müssen wir den tibble, welchen spread() ausgibt, noch entsprechend umwandeln.
  • Visualisierung der Termmatrix in Form einer Comparison Cloud – eine Wordcloud gruppiert nach Klassen. Als letztere Klassen verwenden wir die zuvor gebildeten Sentimentspalten, die Größe der Worte wird – wie bei Wordclouds üblich – über die Anzahl bestimmt. Als zusätzliche Pakete verwenden wir wordcloud, sowie den RColorBrewer, um eine zehnstufige Farbpalette zu bilden, bei der die benachbarten Farben deutlich genug unterscheidbar sind.
inner_join(grammar_os_df, get_sentiments("nrc")) %>% 
  group_by(., word, sentiment) %>% tally(., sort = TRUE) %>% 
  spread(., key = sentiment, value = n, fill = 0)  %>%  
  remove_rownames(.) %>%  
  column_to_rownames(., "word") %>%  
  comparison.cloud(data.matrix(.), scale=c(3.5,.75),
                   colors = brewer.pal(10, "Paired"),
                   max.words = 1000)

Wordcloud Grammar of sciende

Zum Vergleich erstellen wir den selben Plot noch einmal mit dem zuvor genannten loughran-Lexikon:

Wordcloud Grammar of sciende - loughran

Die beiden Comparison-Clouds zeigen hierbei bereits die deutlichen Unterschiede in den Lexika auf. Erstere könnte auf eine unterrepräsentierte Furchtdimension im Text hinweisen – eine Darstellung die den eingefleischten Pearson-Fans nicht widerstreben dürfte. Wie korrekt die einzelnen Klassifizierungen wirklich erscheinen und ob sich erstere Wordcloud wirklich für Einschätzungen über die Furchtlosigkeit in Pearsons Werk eignet, das wollen wir an dieser Stelle allerdings offenlassen.

Soviel zu unseren ersten Gehversuchen im Tidyverse der Textanalyse. Während sich dieser Beitrag nur mit allerersten Schritten der Vorbereitung und Exploration von Textdaten beschäftigt, wollen wir in den nächsten Beiträgen natürlich tiefer in die Materie eindringen: Dabei werden wir – ganz im Sinne des Goethe-begeisterten Pearson – einen Ausblick zur Verarbeitung von deutschen Texten geben und entsprechende deutsche Lexika vorstellen. Zusätzlich werden wir auch in fortgeschrittene Analysemethoden vordringen und versuchen, mit Hilfe statistischer Modelle Bedeutungsstrukturen zu erkunden.

Wem die Arbeit mit epistemologischer Literatur etwas zu staubig erscheint - dem sei ganz dringend der Blog meines Kollegen Lukas ans Herz gelegt: Dort geht gibt es alles Wissenswerte zum effektiven Anzapfen von Twitter als Datenoase.

Referenzen

  1. Erich Neuwirth (2014). RColorBrewer: ColorBrewer Palettes. R package version 1.1-2. https://CRAN.R-project.org/package=RColorBrewer
  2. Hadley Wickham, Romain Francois, Lionel Henry and Kirill Müller (2017). dplyr: A Grammar of Data Manipulation. R package version 0.7.4. https://CRAN.R-project.org/package=dplyr
  3. Ian Fellows (2014). wordcloud: Word Clouds. R package version 2.5. https://CRAN.R-project.org/package=wordcloud
  4. Kirill Müller and Hadley Wickham (2017). tibble: Simple Data Frames. R package version 1.3.4. https://CRAN.R-project.org/package=tibble
  5. Lincoln Mullen (2016). tokenizers: A Consistent Interface to Tokenize Natural Language Text. R package version 0.1.4. https://CRAN.R-project.org/package=tokenizers
  6. Porter, T. (2017). Karl Pearson. In Encyclopædia Britannica. Retrieved from https://www.britannica.com/biography/Karl-Pearson
  7. Silge, J., & Robinson, D. (2017). Text mining with R: a tidy approach. Sebastopol, CA: OReilly Media.
Über den Autor
David Schlepps

David Schlepps

I am a data scientist at STATWORX and while I am working on machine learning problems during the daytime, I dream about the beauty of Shiny and the Tidyverse at night.