germeval02

textmining
tidymodels
germeval
sentiment
string
Published

November 16, 2023

Aufgabe

Führen Sie eine Sentiment-Analyse durch. Verwenden Sie verschiedene Verfahren.

Nutzen Sie die GermEval-2018-Daten.

Die Daten sind unter CC-BY-4.0 lizensiert. Author: Wiegand, Michael (Spoken Language Systems, Saarland University (2010-2018), Leibniz Institute for the German Language (since 2019)),

Die Daten sind auch über das R-Paket PradaData zu beziehen.

Hinweise:











Lösung

Setup

library(tidyverse)
library(tidytext)
library(tictoc)  # Zeitmessung
library(syuzhet)  # Sentimentanalyse

Daten

Nutzen Sie diesen Text-Datensatz, bevor Sie den größeren germeval-Datensatz verwenden:

text <- c("Abbau, Abbruch ist jetzt", "Test heute", "Abbruch morgen perfekt", "Abmachung lore ipsum", "boese ja", "böse nein", "hallo ?! hallo.", "gut schlecht")

n_emo <- c(2, 0, 2, 1, 1, 1, 0, 2)

test_text <-
  tibble(id = 1:length(text),
             text = text,
             n_emo = n_emo)

test_text
# A tibble: 8 × 3
     id text                     n_emo
  <int> <chr>                    <dbl>
1     1 Abbau, Abbruch ist jetzt     2
2     2 Test heute                   0
3     3 Abbruch morgen perfekt       2
4     4 Abmachung lore ipsum         1
5     5 boese ja                     1
6     6 böse nein                    1
7     7 hallo ?! hallo.              0
8     8 gut schlecht                 2

Wörterbücher laden:

data(sentiws, package = "pradadata")
#data(schimpfwoerter, package = "pradadata")

sentiws$word <- tolower(sentiws$word)  # wichtig!

GermEval-Datensatz:

data(germeval_train, package = "pradadata")
germeval_train <- as_tibble(germeval_train)  # schöneres Print am Bildschirm

Sentimentanalyse im Test-Datensatz

Sentimentanalyse mit Regex

Die Funktion count_lexicon stammt aus {prada}.

Tipp: Mit ?count_lexicon sehen Sie den Quelltext (jeder Funktion).

test_text  |> 
  mutate(n_emowords = map_int(text, prada::count_lexicon, sentiws$word))
# A tibble: 8 × 4
     id text                     n_emo n_emowords
  <int> <chr>                    <dbl>      <int>
1     1 Abbau, Abbruch ist jetzt     2          2
2     2 Test heute                   0          0
3     3 Abbruch morgen perfekt       2          2
4     4 Abmachung lore ipsum         1          1
5     5 boese ja                     1          0
6     6 böse nein                    1          1
7     7 hallo ?! hallo.              0          0
8     8 gut schlecht                 2          2
tic()
germeval_train |> 
  mutate(n_emowords = map_int(text, ~ prada::count_lexicon(.x, sentiws$word))) |> 
  head()
# A tibble: 6 × 5
     id text                                              c1    c2    n_emowords
  <int> <chr>                                             <chr> <chr>      <int>
1     1 @corinnamilborn Liebe Corinna, wir würden dich g… OTHER OTHER          2
2     2 @Martin28a Sie haben ja auch Recht. Unser Tweet … OTHER OTHER          2
3     3 @ahrens_theo fröhlicher gruß aus der schönsten s… OTHER OTHER          0
4     4 @dushanwegner Amis hätten alles und jeden gewähl… OTHER OTHER          1
5     5 @spdde kein verläßlicher Verhandlungspartner. Na… OFFE… INSU…          1
6     6 @Dirki_M Ja, aber wo widersprechen die Zahlen de… OTHER OTHER          4
toc()
61.739 sec elapsed

Puh! Viel zu langsam.

Sentimentanalyse mit unnest_tokens

Probieren wir es mit unnest_tokens:

Jaa,… aber die Strings ohne Treffer werden ignoriert.

test_text |> 
  unnest_tokens(word, text) |> 
  right_join(sentiws |> select(word)) |> 
  count(id)
# A tibble: 6 × 2
     id     n
  <int> <int>
1     1     2
2     3     2
3     4     1
4     6     1
5     8     2
6    NA  3461

Probieren wir es so:

#' Count words in a lexicon
#' 
#' Counts how many of the words of the character vector `text` are
#' found in a lexicon `lex` 
#' `text` is transformed via tolower.
#'
#' @param text corpus, character vector
#'
#' @return number of hits per element of the corpus
#' @export
#'
#' @examples
#' count_lex(my_text, my_lex)
count_lex <- function(text) {
  
  stopifnot(class(text) == "character")
  
  doc <- tibble(text = tolower(text),
                id = 1:length(text))
  
  doc1 <- 
    doc |> 
    tidytext::unnest_tokens(word, text) |> 
    dplyr::inner_join(sentiws |> dplyr::select(word), by = "word") |> 
    count(id)
  
  doc2 <-
    doc1 |> 
    dplyr::full_join(doc |> select(id), by = "id")
  
  doc2$n <- ifelse(is.na(doc2$n), 0,doc2$n)
  
  doc2 <- doc2 |> dplyr::arrange(id)
  
  doc2 |> pull(n)
}

Mit dem Paket box kann man Funktionen, die nicht in Paketen stehen, importieren.

count_lex(test_text$text)
[1] 2 0 2 1 0 1 0 2

Als neue Spalte im Datensatz:

test_text |> 
  mutate(n_emowords = count_lex(text))
# A tibble: 8 × 4
     id text                     n_emo n_emowords
  <int> <chr>                    <dbl>      <dbl>
1     1 Abbau, Abbruch ist jetzt     2          2
2     2 Test heute                   0          0
3     3 Abbruch morgen perfekt       2          2
4     4 Abmachung lore ipsum         1          1
5     5 boese ja                     1          0
6     6 böse nein                    1          1
7     7 hallo ?! hallo.              0          0
8     8 gut schlecht                 2          2

Sentimentanalyse mit {syuzhet}

Mit dem Lexicon nrc

get_nrc_sentiment(test_text$text, language = "german")
  anger anticipation disgust fear joy sadness surprise trust negative positive
1     0            0       0    0   0       0        0     0        0        0
2     0            0       0    0   0       0        0     0        0        0
3     0            3       0    0   1       0        0     1        0        1
4     0            0       0    0   0       0        0     0        0        0
5     0            0       0    0   0       0        0     0        0        1
6     0            0       0    0   0       0        0     0        1        0
7     0            0       0    0   0       0        0     0        0        0
8     2            1       2    2   1       3        1     1        4        1

Tja, nicht so viele Treffer …

In der Zusammenfassung:

get_nrc_values(text, language = "german")
# A tibble: 1 × 10
  anger anticipation disgust  fear   joy negative positive sadness surprise
  <dbl>        <dbl>   <dbl> <dbl> <dbl>    <dbl>    <dbl>   <dbl>    <dbl>
1     0            0       0     0     0        0        0       0        0
# ℹ 1 more variable: trust <dbl>

Tja, leider keine Treffer. Merkwürdig.

get_sentiment(text,
              method = "nrc",
              language = "german")
[1]  0  0  1  0  1 -1  0 -3

Naja, ok.

Mit einem eigenen Lexikon

Beispiel vom Autor des Pakets:

my_text <- "I love when I see something beautiful.  I hate it when ugly feelings creep into my head."
char_v <- get_sentences(my_text)
method <- "custom"
custom_lexicon <- data.frame(word=c("love", "hate", "beautiful", "ugly"), value=c(1,-1,1, -1))
my_custom_values <- get_sentiment(char_v, method = method, lexicon = custom_lexicon)
my_custom_values
[1]  2 -2
get_sentiment(text,
              method = "custom",
              lexicon = sentiws)
[1] -0.0628  0.0000  0.7251  0.0040  0.0000  0.0000  0.0000 -0.3990

Sentimentanalyse im GermEval-Datensatz

Test

tic()
sentiments <-
  get_sentiment(germeval_train$text,
              method = "custom",
              lexicon = sentiws)
toc()
6.76 sec elapsed
length(sentiments)
[1] 5009
head(sentiments)
[1]  0.1025 -0.3426  0.0000  0.0040 -0.0048 -0.3460

Die Geschwindigkeit scheint deutlich besser zu sein, als bei den Regex-Ansätzen.

Als Spalte in die Tabelle

tic()
d <-
  germeval_train |> 
  mutate(n_emo = get_sentiment(germeval_train$text,
              method = "custom",
              lexicon = sentiws))
toc()
6.635 sec elapsed
head(d)
# A tibble: 6 × 5
     id text                                                 c1    c2      n_emo
  <int> <chr>                                                <chr> <chr>   <dbl>
1     1 @corinnamilborn Liebe Corinna, wir würden dich gern… OTHER OTHER  0.103 
2     2 @Martin28a Sie haben ja auch Recht. Unser Tweet war… OTHER OTHER -0.343 
3     3 @ahrens_theo fröhlicher gruß aus der schönsten stad… OTHER OTHER  0     
4     4 @dushanwegner Amis hätten alles und jeden gewählt..… OTHER OTHER  0.004 
5     5 @spdde kein verläßlicher Verhandlungspartner. Nachk… OFFE… INSU… -0.0048
6     6 @Dirki_M Ja, aber wo widersprechen die Zahlen denn … OTHER OTHER -0.346 

Fazit

syuzhet bietet den besten Ansatz unterm Strich (von den hier vorgestellten Methoden) für eine Sentimentanalyse in deutscher Sprache.

Insgesamt ist die Sentimentanalyse relativ rechenintensiv.


Categories:

  • textmining
  • tidymodels
  • germeval
  • sentiment
  • string