Skip to content
Snippets Groups Projects
stringr-grammar.Rmd 23.14 KiB
title: "Textdaten bearbeiten mit **stringr**"
author: "B. Philipp Kleer"
date: "11. Oktober 2021"
output: 
  html_document:
      toc: true
      toc_float:
        toc_collapsed: true
        smooth_scroll: true
      toc_depth: 3
      widescreen: true
      highlight: pygments
      theme: readable
      css: styles/style.css
      df_print: paged
      mathjax: default
      self_contained: false
      incremental: false #True  dann jedes Bullet einzeln
      collapse: true # means the text output will be merged into the R source code block
      includes:
        after_body: ./styles/footer.html
        before_body: ./styles/header.html
library("knitr")
library("rmarkdown")
library("tidyverse")

gbbo <- readRDS("../datasets/gbbo.rds")

opts_chunk$set(#fig.path = 'pics/s6-', # path for calculated figures
               fig.align = 'center',  # alignment of figure (also possible right, left, default)
               fig.show = 'hold', # how to show figures: hold -> direct at the end of code chunk; animate: all plots in an animation
               fig.width = 6,   # figure width
               fig.height = 6,  # figure height
               echo = TRUE,     # Code is printed
               eval = FALSE,    # Code is NOT evaluated
               warning = FALSE, # warnings are NOT displayed
               message = FALSE, # messages are NOT displayed
               size = "tiny",  # latex-size of code chunks
               background = "#E7E7E7", # background color of code chunks
               comment = "", # no hashtags before output
               options(width = 80),
               results = "markdown",
               rows.print = 15
)

htmltools::tagList(
  xaringanExtra::use_clipboard(
    button_text = "<i class=\"fa fa-clipboard\"></i>",
    success_text = "<i class=\"fa fa-check\" style=\"color: #90BE6D\"></i>",
    error_text = "<i class=\"fa fa-times-circle\" style=\"color: #F94144\"></i>"
  ),
  rmarkdown::html_dependency_font_awesome()
)

Starten wir!

:::: {class="flex-container"}

::: {class="flex-text"} In diesen Slides wollen wir uns mit Textdaten beschäftigen. Hieru lernen wir grundlegende Funktionen im Umgang mit Textdaten kennen. Eins vorweg: Textdaten sind immer schwieriger zu händeln als numerische Daten. Hierz werden im späteren Teil auch regular expressions eingeführt. Dies bleibt aber auf einem Grundlevel, weiterführend wird dazu sicherlich im Workshop zur quantitativen Textanalyse eingegangen.

In R gibt es innerhalb des tidyverse ein spezielles Paket zur Bearbeitung von Textdaten: stringr. Gemeinsam mit regular expressions können viele Transformationen und Bereinigungen durchgeführt werden.

Dieser Teil des Workshops bedarf aber einer erhöhten Lernbereitschaft (im Vergleich zu anderen Paketen und Funktionen). Das Cheat-Sheet zu stringr gibt es hier. :::

::: {class="flex-picture"} stringr :::

::::

Wie bekommen wir Textdaten?

Als mögliche Textdaten fallen einem vermutlich sofort Tweets ein, oder für die Offline-Leute z.B. Wahlprogramme. In anderen Kursen der MethodenTage wird die Erhebung von Social Media-Daten über die sogenannte API (Application Programming Interference) erklärt. Wir überspringen jetzt diesen Schritt, da es hier erstmal nur um die grundlegenden Funktionen der Bearbeitung von Textvariablen in R geht.

Dafür habe ich die letzten Tweets der hervorragenden Sendung The Great British Bake Off über die Twitter-API geladen (Stand: 22. September 2021). Wir werden im nachfolgenden verschiedene Schritte durchführen, wie wir Textdaten mit den Funktionen des Pakets stringr bearbeiten können.

Package und Daten laden

Zuerst laden wir wieder tidyverse bzw. installieren es, wenn es noch nicht installiert ist:

# install.packages("tidyverse")
library("tidyverse")

# alternativ: 
# install.packages("stringr")
# library("stringr")

Anschließend laden wir den Datensatz gbbo ins environment.

gbbo <- readRDS("../datasets/gbbo.rds")
 # oder eigenen Pfad, wenn nicht in Cloud

Schauen wir uns nun den Datensatz mal an:

gbbo

Der Datensatz umfasst in Gänze 90 Variablen, für uns ist in diesem Fall aber nur eine einzige Variable erstmal relevant - nämlich text. In text befindet sich der Text des jeweiligen Tweets.

Der einfachheithalber speichern wir diese eine Variable als einzelnen Vektor, schaffen eine neue ID-Variable und erstellen den Datensatz tweet.

tweet <- gbbo %>% 
  select(text)

tweet$id <- seq(1, 
                862, 
                1
                )

head(tweet,
     n = 100
     )

Basisfunktionen in stringr

Die Basisfunktionen, die wir jetzt zu Beginn kennenlernen, sind folgende ...

  • str_length()

  • str_sub()

  • str_dup()

  • str_pad()

  • str_trunc()

  • str_trim()

  • str_to_upper() / str_to_lower() / str_to_title()

str_length()

Die Funktion ist ähnlich zur Funktion nchar() aus baseR. Sie gibt die Länge der jeweiligen Zeichenkette an. Wir können dies für alle ausgeben lassen oder für einzelne Tweets. Wenn wir es für alle ausgeben lassen, ist die Ausgabe wieder ein Vektor, aber eben numerisch.

str_length(tweet$text)

# oder für einen spezifischen
str_length(tweet$text[23])

# oder mit piping
tweet %>% 
  filter(id == 275) %>% 
  select(text) %>% 
  str_length()

str_sub()

Wir können uns auch einzelne Zeichen aus einem String ausgeben lassen. So z.B. das zweite bis fünfte Zeichen aus dem 23. Tweet.

# hier der 23. tweet
str_sub(tweet$text[23], 
        2, 
        5
        )

tweet %>% 
  filter(id == 23) %>% 
  select(text) %>% 
  str_sub(2, 
          5
          )

Natürlich kann man sich auch einzelne Buchstaben ausgeben lassen, dazu setzt man den Anfang (zweite Argument) und das Ende der Extraktion (dritte Argument) gleich. Will man von hinten zählen nimmt man einfach ein - zur Hilfe.

# hier der 23. tweet
str_sub(tweet$text[23], 
        25, 
        25
        )

str_sub(tweet$text[23], 
        -23, 
        -2
        )

Eine kleine Denkaufgabe: Warum ist die erste Ausgabe leer?

Alternativ kann man so auch bestimmte Stellen im Text ersetzen, sofern man deren numerische Stellen kennt. Wie dies einfacher mit regular expression geht, lernen wir später.

tweet$text[23]

str_sub(tweet$text[23], 
        26, 
        26
        ) <- "M"

tweet$text[23]

str_dup()

Anstatt Zeichen zu löschen oder zu ersetzen, können wir Zeichen auch duplizieren. Wiederholen wir einfach den Text von Tweet 23 viermal.

str_dup(tweet$text[23],
        4
        )

Man kann das ganze auch verketten und wieder direkt pipen mit tidyverse.

tweet %>% 
  filter(id == 23) %>% 
  select(text) %>% 
  str_sub(1,
          8
          ) %>% 
  str_dup(4)

Kleine Denkaufgabe: Was wird hier wiederholt?

str_pad()

Oft ist es von Vorteil, wenn Zeichenketten dieselbe Länge aufweisen, z.B. wenn man sie gleichmäßig grafisch darstellen möchte. Dies können wir mit str_pad() machen: Die Zeichenkette soll 100 Zeichen lang werden. Zuerst erstellen wir dazu aber einfach einen Character-Vektor, der nur zwei Tweets beinhaltet (Nr. 23 und 731).

partOfTweet  <- tweet$text[c(23,
                             731
                             )
                           ]

partOfTweet

Wir haben jetzt also einen einzelnen Vektor, der zwei Tweets beinhaltet. Jetzt wollen wir die Länge auf 100 festsetzen:

partOfTweet <- str_pad(partOfTweet, 
                       100, 
                       side = "both"
                       )

partOfTweet

Kleine Denkaufgabe: Was passiert hier nun beim zweiten Tweet?

Anscheinend nichts. Und das ist auch gut so! Denn ansonsten hätte die Funktion den Tweet gekürzt und dies macht die Funktion eben nicht. Ist ein Text länger als es in str_pad() angegeben ist, bleibt der Tweet so wie er ist, erhalten.

Eleganter Pipen

Oben haben wir ja bereits mit tidyverse pipes benutzt, dies machen wir jetzt aber eleganter. Die Funktionen aus stringr haben alle kein Datenargument, daher sind sie auch nicht direkt zu pipen. Das führt immer zu Fehlermeldungen.

tweet <- tweet %>% 
  str_pad(text, 
          100, 
          side = "both"
          )

Die Funktionen aus stringr sind allesamt direkt Mutationen eines Vektors. Mithilfe von mutate() können wir diese aber auch mit Pipes benutzen. Wir definieren eine neue Variable und dieser Variable ordnen wir die Mutation über die stringr-Funktion zu.

tweet <- tweet %>% 
  mutate(shortT = str_pad(text, 
                          100, 
                          side = "both"
                          )
         )

head(tweet$shortT, 
     n = 50
     )

Es ist zwar ein Umweg, aber ein guter: Denn so bleibt die Ursprungsvariable immer im Datensatz erhalten. Nicht zu empfehlen ist es die Originalvariable direkt in mutate() zu überschreiben.

str_trunc()

Um gleichzeitig die Tweets zu kürzen, benutzt man die Funktion str_trunc(). Hiermit kann die maximale Zeichenkettenzahl gesetzt werden. So könnten wir zum Beispiel sagen, uns interessiert nur der Beginn der Tweets, also die ersten 50 Zeichen.

tweet <- tweet %>% 
  mutate(truncT = str_trunc(text, 
                            50
                            ),
         truncT = str_pad(truncT, 
                          60, 
                          side = "both"
                          )
         )

head(tweet$truncT, 
     n = 50
     )

Wie man sieht, werden Tweets die länger als 50 Zeichen sind mit ... am Ende gekennzeichnet.

str_trim()