Cleaning Text

This demo is about basic text cleaning.

We show how to remove unwanted character sequences, handle Twitter-specific tokens, and create a basic corpus object.

# Load required packages

library(data.table)
library(quanteda)
library(stringi)
library(stringr)

Disclaimer

Note that, in the following, we will work with data.table objects, an alternative to the more classic data.frame structure.

The syntax requires some getting used to, but then we notice that

  • data.table is a lot faster, which helps with the many text mining operations we conduct.
  • it is much more memory-friendly: rather than creating copies over and over, objects are modified in place – a property all the more valuable for large data sets, and common in most programming languages other than R.
  • the syntax comes natural to those with SQL background.
  • it integrates seamlessly with mlr3, a whole universe for machine learning in R which we will encounter later on.

This vignette provides a nice introduction to data.table.

Read data

Assuming we have concluded the initial step of scraping the data and stored them as a .csv file, we now import the data into R.

# path <- ... (individual file location)

# Read data

twitter_data <- data.table::fread(
  sprintf("%s/twitter_data.csv", path),
  encoding = "UTF-8", # standard encoding
  sep = ";") # standard for German-locale CSV files

# Define an ID variable that acts as a unique identifier for each tweet

# NB: data.table modifies twitter_data in place without requiring re-assignment (the `:=` notation tells it to create/modify a variable)

# If a user should indeed have posted several tweets with the exact same time stamp, number these tweets

twitter_data[
  , rank_timestamp := seq_len(.N),
  by = .(username, created_at)]

# Create ID variable from username, timestamp as float, and timestamp rank

twitter_data[, doc_id := paste(
  username,
  as.character(as.numeric(as.POSIXct(created_at))),
  rank_timestamp,
  sep = ""),
  by = seq_len(nrow(twitter_data))]

# Delete auxiliary timestamp rank

twitter_data[, rank_timestamp := NULL]

# Check whether ID is really unique

stopifnot(nrow(twitter_data) == length(unique(twitter_data$doc_id)))

# Set ID as identifying key for the data.table

data.table::setkey(twitter_data, doc_id) 

# Inspect

head(twitter_data)
##    last_name first_name                                      wahlkreis_name
## 1:  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 2:  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 3:  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 4:  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 5:  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 6:  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
##     party  bundesland unemployment_rate share_pop_migration  username
## 1: gruene Brandenburg                 6                 6.3 ABaerbock
## 2: gruene Brandenburg                 6                 6.3 ABaerbock
## 3: gruene Brandenburg                 6                 6.3 ABaerbock
## 4: gruene Brandenburg                 6                 6.3 ABaerbock
## 5: gruene Brandenburg                 6                 6.3 ABaerbock
## 6: gruene Brandenburg                 6                 6.3 ABaerbock
##    followers_count          created_at
## 1:          107693 2018-01-11 07:10:00
## 2:          107693 2018-03-14 06:20:00
## 3:          107693 2018-03-21 12:09:00
## 4:           94139 2018-05-02 04:23:00
## 5:          107693 2018-05-06 18:02:00
## 6:          107693 2018-06-07 13:25:00
##                                                                                                                                                                                                                                                                                                        full_text
## 1:                                      Habe mir das #GroKo-Sondierungspapier zu #Klima nochmal genau angeschaut. Krass: De facto wird sogar das Kyoto-Protokoll, der Meilenstein der #Klimadiplomatie für nichtig erklärt! Frau #Merkel, das geht so nicht! Nachbessern! #kohleausstieg https://t.co/tWIuwjAbUd
## 2:                                                                           Auch weltweit sieht man, dass Angela #Merkel s  #GroKo 3.0 nicht klimatauglich ist, sondern vielmehr das Pariser Abkommen unterläuft. Auch außenpolitisch fatal. Daher  Klimasofortprogramm jetzt auflegen! https://t.co/LH73sSW5gU
## 3:                        Der größten globalen Herausforderung, der #Klimakrise, 30 Sek. von 1 Std #Regierungserklärung zu widmen, unterstreicht die größte Leerstelle im #Koalitionsvertrag. Auch sozialpolitisch wird uns das auf die Füße fallen. Unter den Klimafolgen leiden gerade die ärmsten am meisten.
## 4:                         Wir brauchen eine andere Verkehrspolitik - weg von Benzinern und Diesel, hin zu emissionsarmer Mobilität. Das ist auch eine soziale Frage. Denn unter schlechter Luft leiden die, die sich die Wohnung an der verkehrsberuhigten Straße nicht leisten können. https://t.co/GhIrbSrcYP
## 5:                            Das ist die Leistung von Hunderten Wahlkämpfern und Politikern im Land, die draußen waren und gezeigt haben, dass sie dicht an dem sind, was die Menschen umtreibt. Und im Konkreten beweisen, wie lebensnah grüne Politik ist. #kwsh #kow18 #Kommunalwahl https://t.co/zYCeel8UpS
## 6: Luise #Amtsberg hat heute alles gesagt,was es zum #Familiennachzug  zu sagen gibt. Wer #Integration will, muss Geflüchteten Perspektiven auf  ein Zusammenleben mit ihren Familien geben. Das #Grundrecht auf Familie darf nicht beschnitten werden. Die #GroKo macht aber genau das. https://t.co/Dii2NWKAkO
##    favorite_count retweet_count    label               doc_id
## 1:            233           111 negative ABaerbock15156546001
## 2:             96            24 negative ABaerbock15210084001
## 3:            153            39 negative ABaerbock15216341401
## 4:            213            49 negative ABaerbock15252349801
## 5:            301            39 positive ABaerbock15256297201
## 6:            115            23 negative ABaerbock15283779001

Remove umlauts

First, we remove all umlauts and ligature s, which are not part of the standard A-Z characters and might therefore cause problems, from the text.

Unfortunately, R supports several encodings for umlauts (e.g., there is a single unicode representation for the letter “ä” but also a composed form using “a” and a special character). We use stringi to convert all to a common format first.

# Transform all text to general Latin characters

twitter_data[, full_text := stringi::stri_trans_general(full_text, "Any-Latin")]

# Then, convert non-standard characters to standard ones

twitter_data[, full_text := stringr::str_replace_all(
  full_text,
  c("\u00c4" = "Ae",
    "\u00e4" = "ae",
    "\u00d6" = "Oe",
    "\u00f6" = "oe",
    "\u00dc" = "Ue",
    "\u00fc" = "ue",
    "\u00df" = "ss"))]

# Inspect

head(twitter_data, 10)$full_text
##  [1] "Habe mir das #GroKo-Sondierungspapier zu #Klima nochmal genau angeschaut. Krass: De facto wird sogar das Kyoto-Protokoll, der Meilenstein der #Klimadiplomatie fuer nichtig erklaert! Frau #Merkel, das geht so nicht! Nachbessern! #kohleausstieg https://t.co/tWIuwjAbUd"                                               
##  [2] "Auch weltweit sieht man, dass Angela #Merkel s  #GroKo 3.0 nicht klimatauglich ist, sondern vielmehr das Pariser Abkommen unterlaeuft. Auch aussenpolitisch fatal. Daher  Klimasofortprogramm jetzt auflegen! https://t.co/LH73sSW5gU"                                                                                    
##  [3] "Der groessten globalen Herausforderung, der #Klimakrise, 30 Sek. von 1 Std #Regierungserklaerung zu widmen, unterstreicht die groesste Leerstelle im #Koalitionsvertrag. Auch sozialpolitisch wird uns das auf die Fuesse fallen. Unter den Klimafolgen leiden gerade die aermsten am meisten."                           
##  [4] "Wir brauchen eine andere Verkehrspolitik - weg von Benzinern und Diesel, hin zu emissionsarmer Mobilitaet. Das ist auch eine soziale Frage. Denn unter schlechter Luft leiden die, die sich die Wohnung an der verkehrsberuhigten Strasse nicht leisten koennen. https://t.co/GhIrbSrcYP"                                 
##  [5] "Das ist die Leistung von Hunderten Wahlkaempfern und Politikern im Land, die draussen waren und gezeigt haben, dass sie dicht an dem sind, was die Menschen umtreibt. Und im Konkreten beweisen, wie lebensnah gruene Politik ist. #kwsh #kow18 #Kommunalwahl https://t.co/zYCeel8UpS"                                    
##  [6] "Luise #Amtsberg hat heute alles gesagt,was es zum #Familiennachzug  zu sagen gibt. Wer #Integration will, muss Gefluechteten Perspektiven auf  ein Zusammenleben mit ihren Familien geben. Das #Grundrecht auf Familie darf nicht beschnitten werden. Die #GroKo macht aber genau das. https://t.co/Dii2NWKAkO"           
##  [7] "#Maassen nun also „rechte“ Hand von #Seehofer. Es gibt nicht genug Adjektive um das Drama dieser Grossen Koalition zu beschreiben. Aber ehrlich gesagt, mach ich mir nach heute einfach nur noch mehr Sorgen um die Zukunft unserer Demokratie. \nUmso mehr gilt: #WhenTheyGoLowWeGoHigh"                                 
##  [8] "„Ob Kohleausstieg, CO2-Preis, Verkehrswende oder die Einhaltung eigener CO2-Minderungsziele. Beim #Klimaschutz hakt es in Deutschland an allen Ecken und Enden.“ Das muss sich dringend aendern. #COP24  https://t.co/1PZQXb2gBI"                                                                                         
##  [9] "Ein wirkliches Recht auf Information zum Schwangerschaftsabbruch fuer alle Frauen sowie Rechtssicherheit fuer Aerztinnen &amp; Aerzte sollte selbstverstaendlich sein! In 30 Staedten sind wir Frauen heute auf der Strasse, damit es endlich kommt. #wegmit219a #mybodymychoice @GrueneBundestag https://t.co/UgPq4TtRb1"
## [10] "@haenel_kh , Natascha Nicklaus + @NoraSzasz1 kaempfen fuer etwas das groesser ist als ein Bulletpoint auf ihrer Praxis Website. Sie kaempfen fuer das #Selbstbestimmungsrecht von uns Frauen. Dafuer gibts zu Recht den  #AnneKleinFrauenpreis der @boell_stiftung \n#Trustwomen #wegmit219a https://t.co/pKKgZ3sPzV"

Remove unwanted symbols

Looking better already! Now, let’s remove all symbols and special characters that carry no particular meaning (e.g., URLs or leftovers from ampersand conversion). Emojis and hashtags are not removed just yet; we will tackle these in a second.

# Replace all "\n" from line breaks with a simple whitespace

twitter_data[, full_text := stringr::str_replace_all(
  full_text, 
  pattern = "\\n", # with additional backslash to escape the special character
  replacement = " ")]

# Create pattern for symbols we want to discard altogether (including various types of quotes, sequences 
# resulting from unicode conversion, hyperlinks, ...), concatenated with the logical OR operation

pattern <- stringr::str_c(c(
  "\U0022", 
  "\U0027", 
  "\U2018", 
  "\U2019", 
  "\U201C", 
  "\U201D", 
  "\U201E", 
  "\U201F",
  "&amp;",
  "&lt;", 
  "&gt;",
  "%",
  " http([^ ]*)",
  "http([^ ]*)",
  "\\\n"), 
  collapse = "|")

twitter_data[, full_text := stringr::str_remove_all(full_text, pattern)]

# Remove additional whitespaces induced by removal

twitter_data[, full_text := stringr::str_squish(full_text)]

# Inspect

head(twitter_data, 10)$full_text
##  [1] "Habe mir das #GroKo-Sondierungspapier zu #Klima nochmal genau angeschaut. Krass: De facto wird sogar das Kyoto-Protokoll, der Meilenstein der #Klimadiplomatie fuer nichtig erklaert! Frau #Merkel, das geht so nicht! Nachbessern! #kohleausstieg"                                            
##  [2] "Auch weltweit sieht man, dass Angela #Merkel s #GroKo 3.0 nicht klimatauglich ist, sondern vielmehr das Pariser Abkommen unterlaeuft. Auch aussenpolitisch fatal. Daher Klimasofortprogramm jetzt auflegen!"                                                                                   
##  [3] "Der groessten globalen Herausforderung, der #Klimakrise, 30 Sek. von 1 Std #Regierungserklaerung zu widmen, unterstreicht die groesste Leerstelle im #Koalitionsvertrag. Auch sozialpolitisch wird uns das auf die Fuesse fallen. Unter den Klimafolgen leiden gerade die aermsten am meisten."
##  [4] "Wir brauchen eine andere Verkehrspolitik - weg von Benzinern und Diesel, hin zu emissionsarmer Mobilitaet. Das ist auch eine soziale Frage. Denn unter schlechter Luft leiden die, die sich die Wohnung an der verkehrsberuhigten Strasse nicht leisten koennen."                              
##  [5] "Das ist die Leistung von Hunderten Wahlkaempfern und Politikern im Land, die draussen waren und gezeigt haben, dass sie dicht an dem sind, was die Menschen umtreibt. Und im Konkreten beweisen, wie lebensnah gruene Politik ist. #kwsh #kow18 #Kommunalwahl"                                 
##  [6] "Luise #Amtsberg hat heute alles gesagt,was es zum #Familiennachzug zu sagen gibt. Wer #Integration will, muss Gefluechteten Perspektiven auf ein Zusammenleben mit ihren Familien geben. Das #Grundrecht auf Familie darf nicht beschnitten werden. Die #GroKo macht aber genau das."          
##  [7] "#Maassen nun also rechte Hand von #Seehofer. Es gibt nicht genug Adjektive um das Drama dieser Grossen Koalition zu beschreiben. Aber ehrlich gesagt, mach ich mir nach heute einfach nur noch mehr Sorgen um die Zukunft unserer Demokratie. Umso mehr gilt: #WhenTheyGoLowWeGoHigh"          
##  [8] "Ob Kohleausstieg, CO2-Preis, Verkehrswende oder die Einhaltung eigener CO2-Minderungsziele. Beim #Klimaschutz hakt es in Deutschland an allen Ecken und Enden. Das muss sich dringend aendern. #COP24"                                                                                         
##  [9] "Ein wirkliches Recht auf Information zum Schwangerschaftsabbruch fuer alle Frauen sowie Rechtssicherheit fuer Aerztinnen Aerzte sollte selbstverstaendlich sein! In 30 Staedten sind wir Frauen heute auf der Strasse, damit es endlich kommt. #wegmit219a #mybodymychoice @GrueneBundestag"   
## [10] "@haenel_kh , Natascha Nicklaus + @NoraSzasz1 kaempfen fuer etwas das groesser ist als ein Bulletpoint auf ihrer Praxis Website. Sie kaempfen fuer das #Selbstbestimmungsrecht von uns Frauen. Dafuer gibts zu Recht den #AnneKleinFrauenpreis der @boell_stiftung #Trustwomen #wegmit219a"

Extract Twitter-specific symbols

After handling standard text cleaning issues, we remove the Twitter-specific sequences from the plain text, namely

  • emojis (using the list rtweet provides),
  • hashtags, and
  • tags.

This time, though, we keep the extracted parts and store them into separate variables since they might be useful for sentiment analysis.

Note that emojis are a bit iffy to handle: concatenating the entire rtweet list as a regex pattern produces a super long string that will cause R to crash. We therefore use a workaround: first, we extract all emoji candidates, and afterwards we match them witht the rtweet list.

First, specify patterns with regex:

# Specify emoji candidate pattern: all non-printable characters

pattern_emoji <- "[^ -~]"

# Specify hashtag pattern: # symbol, followed by anything alphanumeric

pattern_hashtag <- "(#)[[:alnum:]]+"

# Specify tag pattern: @ symbol, followed by anything but a space

pattern_tag <- "@\\S+"

Extract all matching sequences and store them in separate columns:

# Ensure unicode characters are displayed correctly

twitter_data[, full_text := stringi::stri_unescape_unicode(full_text)]

# Extract

twitter_data[, emojis := stringr::str_extract_all(full_text, pattern_emoji)]

emojis_real <- lapply(
  twitter_data$emojis, 
  function(i) unlist(i)[unlist(i) %in% rtweet::emojis$code])

twitter_data$emojis <- emojis_real

twitter_data[, hashtags := stringr::str_extract_all(full_text, pattern_hashtag)]
twitter_data[, tags := stringr::str_extract_all(full_text, pattern_tag)]

# Inspect

tail(twitter_data)
##    last_name first_name            wahlkreis_name party    bundesland
## 1:     Perli     Victor Salzgitter – Wolfenbüttel linke Niedersachsen
## 2:    Nastic     Zaklin        Hamburg-Eimsbüttel linke       Hamburg
## 3:    Nastic     Zaklin        Hamburg-Eimsbüttel linke       Hamburg
## 4:    Nastic     Zaklin        Hamburg-Eimsbüttel linke       Hamburg
## 5:    Nastic     Zaklin        Hamburg-Eimsbüttel linke       Hamburg
## 6:    Nastic     Zaklin        Hamburg-Eimsbüttel linke       Hamburg
##    unemployment_rate share_pop_migration     username followers_count
## 1:               8.1                18.2  victorperli            5426
## 2:               7.1                28.3 zaklinnastic            4208
## 3:               7.1                28.3 zaklinnastic            4208
## 4:               7.1                28.3 zaklinnastic            4208
## 5:               7.1                28.3 zaklinnastic            4208
## 6:               7.1                28.3 zaklinnastic            4208
##             created_at
## 1: 2020-06-06 09:59:00
## 2: 2018-02-26 16:18:00
## 3: 2018-10-15 09:49:00
## 4: 2019-03-05 08:58:00
## 5: 2019-10-29 19:12:00
## 6: 2019-11-12 13:50:00
##                                                                                                                                                                                                                                                                                   full_text
## 1: Die Autobosse sind selbst Schuld an ihrem miserablen Ruf. Wer Schlagzeilen mit Abgasbetrug, Manipulation und ueblen Insta-Videos macht, verliert Vertrauen der Kunden gefaehrdet Millionen Jobs. In der Krise ueber 5 Mrd Dividende auszahlen und dann nach Staatshilfe rufen, ist gaga!
## 2:                           #SalihMuslim war erst letzte Woche in @Linksfraktion und berichtete ueber die schwere Situation in #Afrin und #Tuerkei ,jetzt wurde er festgenommen.Morgen beginnt der Prozess in Prag.Freiheit fuer Salih Muslim,statt Unterstuetzung fuer Diktator #Erdogan!
## 3:                                                                                  Richtiger Kommentar @SZ! #SaudiArabien kann nicht beweisen das #Khashoggi noch lebt. Nun sollen #Menschenrechte wieder #Wirtschaftsinteressen geopfert werden. #BuReg, beenden sie dieses #Trauerspiel.
## 4:                                                                                                  Ich kann diese Wut gegen den #Klerus verstehen! Uebergriffige #Priester muessen fuer ihre Taten weltlich belangt werden! #Kinderrechte sind #Menschenrechte! #Missbrauch @Linksfraktion
## 5:  In #Chile geht Praesident Milliardaer #Piñera mit Militaer gegen Demonstranten vor (1 Mio alleine in der Hauptstadt). Die #Bundesregierung schweigt zu den Vorgaengen im Vorzeigeland des #Neoliberalismus. Wir kennen diese Doppelmoral ja schon bzgl #Katalonien @Linksfraktion @KRLS
## 6:                                                                                             Herzlichen Glueckwunsch @Amira_M_Ali zur #Wahl als Fraktionsvorsitzende @Linksfraktion Gemeinsam moechten wir dem globalisierten Kapitalismus eine starke Linke im Parlament entgegensetzen.
##    favorite_count retweet_count    label                  doc_id emojis
## 1:            224            59 negative  victorperli15914375401       
## 2:             12             8 negative zaklinnastic15196618801       
## 3:              3             0 negative zaklinnastic15395969401       
## 4:             15             3 negative zaklinnastic15517762801       
## 5:            116            66 negative zaklinnastic15723763201       
## 6:             15             2 positive zaklinnastic15735666001       
##                                                                               hashtags
## 1:                                                                                    
## 2:                                               #SalihMuslim,#Afrin,#Tuerkei,#Erdogan
## 3: #SaudiArabien,#Khashoggi,#Menschenrechte,#Wirtschaftsinteressen,#BuReg,#Trauerspiel
## 4:                         #Klerus,#Priester,#Kinderrechte,#Menschenrechte,#Missbrauch
## 5:                        #Chile,#Piñera,#Bundesregierung,#Neoliberalismus,#Katalonien
## 6:                                                                               #Wahl
##                           tags
## 1:                            
## 2:              @Linksfraktion
## 3:                        @SZ!
## 4:              @Linksfraktion
## 5:        @Linksfraktion,@KRLS
## 6: @Amira_M_Ali,@Linksfraktion

Seems to do the job. We will now deal with one more particularity that requires some regex wrangling: frequently, hashtags use camel case to concatenate several words (e.g., #CamelCase), a phenomenon we would like to trace back to the original words:

# Define pattern for camel case hashtag (#, followed by at least one character of any kind, a capital letter, and at least two lowercase letters)

pattern_camelcase_hashtag <- "#(.)+[:upper:][:lower:]{2,}"

# Define pattern to split along (lowercase, directly followed by capital letter)

pattern_split_camelcase <- "(?<=[:lower:])(?=[:upper:])"

# Example

camel <- stringr::str_extract("Don't use #CamelCase in R please", pattern_camelcase_hashtag)
stringr::str_split(camel, pattern_split_camelcase)
## [[1]]
## [1] "#Camel" "Case"
# Find and split cases in Twitter data

twitter_data[, full_text := lapply(
  .I, # apply to each row
  function(i) {
    components <- unlist(stringr::str_split(full_text[i], " ")) # split text into tokens separated by space
    case_numbers <- which(stringr::str_detect(
      components, pattern_camelcase_hashtag)) # find camel case
    cases <- components[case_numbers] # collect cases
    solved_cases <- sapply(
      stringr::str_split(cases, pattern_split_camelcase),
      function(j) paste0(c(j), collapse = " ")) # split and concatenate with spaces in between
    components[case_numbers] <- solved_cases # insert back
    paste0(c(components), collapse = " ")})] # unsplit text

# Inspect

tail(twitter_data)$full_text
## [[1]]
## [1] "Die Autobosse sind selbst Schuld an ihrem miserablen Ruf. Wer Schlagzeilen mit Abgasbetrug, Manipulation und ueblen Insta-Videos macht, verliert Vertrauen der Kunden gefaehrdet Millionen Jobs. In der Krise ueber 5 Mrd Dividende auszahlen und dann nach Staatshilfe rufen, ist gaga!"
## 
## [[2]]
## [1] "#Salih Muslim war erst letzte Woche in @Linksfraktion und berichtete ueber die schwere Situation in #Afrin und #Tuerkei ,jetzt wurde er festgenommen.Morgen beginnt der Prozess in Prag.Freiheit fuer Salih Muslim,statt Unterstuetzung fuer Diktator #Erdogan!"
## 
## [[3]]
## [1] "Richtiger Kommentar @SZ! #Saudi Arabien kann nicht beweisen das #Khashoggi noch lebt. Nun sollen #Menschenrechte wieder #Wirtschaftsinteressen geopfert werden. #Bu Reg, beenden sie dieses #Trauerspiel."
## 
## [[4]]
## [1] "Ich kann diese Wut gegen den #Klerus verstehen! Uebergriffige #Priester muessen fuer ihre Taten weltlich belangt werden! #Kinderrechte sind #Menschenrechte! #Missbrauch @Linksfraktion"
## 
## [[5]]
## [1] "In #Chile geht Praesident Milliardaer #Piñera mit Militaer gegen Demonstranten vor (1 Mio alleine in der Hauptstadt). Die #Bundesregierung schweigt zu den Vorgaengen im Vorzeigeland des #Neoliberalismus. Wir kennen diese Doppelmoral ja schon bzgl #Katalonien @Linksfraktion @KRLS"
## 
## [[6]]
## [1] "Herzlichen Glueckwunsch @Amira_M_Ali zur #Wahl als Fraktionsvorsitzende @Linksfraktion Gemeinsam moechten wir dem globalisierten Kapitalismus eine starke Linke im Parlament entgegensetzen."

Et voilà, we seem to have caught and stored all special symbols, so we can eventually remove them from the text.

# Collect all patterns to remove

patterns_to_remove <- stringr::str_c(
  c(pattern_emoji, "#", pattern_tag),
  collapse = "|")

# Remove patterns and unwanted whitespace

twitter_data[, full_text := stringr::str_remove_all(full_text, patterns_to_remove)]
twitter_data[, full_text := stringr::str_squish(full_text)]

# Inspect

head(twitter_data, 10)$full_text
##  [1] "Habe mir das Gro Ko-Sondierungspapier zu Klima nochmal genau angeschaut. Krass: De facto wird sogar das Kyoto-Protokoll, der Meilenstein der Klimadiplomatie fuer nichtig erklaert! Frau Merkel, das geht so nicht! Nachbessern! kohleausstieg"                                             
##  [2] "Auch weltweit sieht man, dass Angela Merkel s GroKo 3.0 nicht klimatauglich ist, sondern vielmehr das Pariser Abkommen unterlaeuft. Auch aussenpolitisch fatal. Daher Klimasofortprogramm jetzt auflegen!"                                                                                  
##  [3] "Der groessten globalen Herausforderung, der Klimakrise, 30 Sek. von 1 Std Regierungserklaerung zu widmen, unterstreicht die groesste Leerstelle im Koalitionsvertrag. Auch sozialpolitisch wird uns das auf die Fuesse fallen. Unter den Klimafolgen leiden gerade die aermsten am meisten."
##  [4] "Wir brauchen eine andere Verkehrspolitik - weg von Benzinern und Diesel, hin zu emissionsarmer Mobilitaet. Das ist auch eine soziale Frage. Denn unter schlechter Luft leiden die, die sich die Wohnung an der verkehrsberuhigten Strasse nicht leisten koennen."                           
##  [5] "Das ist die Leistung von Hunderten Wahlkaempfern und Politikern im Land, die draussen waren und gezeigt haben, dass sie dicht an dem sind, was die Menschen umtreibt. Und im Konkreten beweisen, wie lebensnah gruene Politik ist. kwsh kow18 Kommunalwahl"                                 
##  [6] "Luise Amtsberg hat heute alles gesagt,was es zum Familiennachzug zu sagen gibt. Wer Integration will, muss Gefluechteten Perspektiven auf ein Zusammenleben mit ihren Familien geben. Das Grundrecht auf Familie darf nicht beschnitten werden. Die GroKo macht aber genau das."            
##  [7] "Maassen nun also rechte Hand von Seehofer. Es gibt nicht genug Adjektive um das Drama dieser Grossen Koalition zu beschreiben. Aber ehrlich gesagt, mach ich mir nach heute einfach nur noch mehr Sorgen um die Zukunft unserer Demokratie. Umso mehr gilt: When They Go Low We Go High"    
##  [8] "Ob Kohleausstieg, CO2-Preis, Verkehrswende oder die Einhaltung eigener CO2-Minderungsziele. Beim Klimaschutz hakt es in Deutschland an allen Ecken und Enden. Das muss sich dringend aendern. COP24"                                                                                        
##  [9] "Ein wirkliches Recht auf Information zum Schwangerschaftsabbruch fuer alle Frauen sowie Rechtssicherheit fuer Aerztinnen Aerzte sollte selbstverstaendlich sein! In 30 Staedten sind wir Frauen heute auf der Strasse, damit es endlich kommt. wegmit219a mybodymychoice"                   
## [10] ", Natascha Nicklaus + kaempfen fuer etwas das groesser ist als ein Bulletpoint auf ihrer Praxis Website. Sie kaempfen fuer das Selbstbestimmungsrecht von uns Frauen. Dafuer gibts zu Recht den Anne Klein Frauenpreis der Trustwomen wegmit219a"

Create corpus object

In the end we convert our nice and clean data to a corpus, the most basic text object in quanteda.

A corpus carries documents identified by an ID variable and containing text, plus additional variables on document level if needed:

# Convert

twitter_corpus <- quanteda::corpus(
  twitter_data,
  docid_field = "doc_id",
  text_field = "full_text")

# Inspect

twitter_corpus
## Corpus consisting of 1,215 documents and 16 docvars.
## ABaerbock15156546001 :
## "Habe mir das Gro Ko-Sondierungspapier zu Klima nochmal genau..."
## 
## ABaerbock15210084001 :
## "Auch weltweit sieht man, dass Angela Merkel s GroKo 3.0 nich..."
## 
## ABaerbock15216341401 :
## "Der groessten globalen Herausforderung, der Klimakrise, 30 S..."
## 
## ABaerbock15252349801 :
## "Wir brauchen eine andere Verkehrspolitik - weg von Benzinern..."
## 
## ABaerbock15256297201 :
## "Das ist die Leistung von Hunderten Wahlkaempfern und Politik..."
## 
## ABaerbock15283779001 :
## "Luise Amtsberg hat heute alles gesagt,was es zum Familiennac..."
## 
## [ reached max_ndoc ... 1,209 more documents ]
head(quanteda::docvars(twitter_corpus))
##   last_name first_name                                      wahlkreis_name
## 1  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 2  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 3  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 4  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 5  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
## 6  Baerbock   Annalena Potsdam – Potsdam-Mittelmark II – Teltow-Fläming II
##    party  bundesland unemployment_rate share_pop_migration  username
## 1 gruene Brandenburg                 6                 6.3 ABaerbock
## 2 gruene Brandenburg                 6                 6.3 ABaerbock
## 3 gruene Brandenburg                 6                 6.3 ABaerbock
## 4 gruene Brandenburg                 6                 6.3 ABaerbock
## 5 gruene Brandenburg                 6                 6.3 ABaerbock
## 6 gruene Brandenburg                 6                 6.3 ABaerbock
##   followers_count          created_at favorite_count retweet_count    label
## 1          107693 2018-01-11 07:10:00            233           111 negative
## 2          107693 2018-03-14 06:20:00             96            24 negative
## 3          107693 2018-03-21 12:09:00            153            39 negative
## 4           94139 2018-05-02 04:23:00            213            49 negative
## 5          107693 2018-05-06 18:02:00            301            39 positive
## 6          107693 2018-06-07 13:25:00            115            23 negative
##   emojis                                                       hashtags tags
## 1             #GroKo, #Klima, #Klimadiplomatie, #Merkel, #kohleausstieg     
## 2                                                       #Merkel, #GroKo     
## 3                #Klimakrise, #Regierungserklaerung, #Koalitionsvertrag     
## 4                                                                           
## 5                                          #kwsh, #kow18, #Kommunalwahl     
## 6        #Amtsberg, #Familiennachzug, #Integration, #Grundrecht, #GroKo
# Save corpus object for further analysis

saveRDS(twitter_corpus, sprintf("%s/twitter_corpus.RDS", path))

With this, we have solid base to work on.