Direkt zum Hauptbereich

Sentiment-Analyse von deutschen Texten in R


Eine Sentiment-Analyse funktioniert im Grunde wiefolgt: die einzelnen Wörter werden eines Textes werden mit bestimmten Bibliotheken abgeglichen und eine Einteilung in "positiv/negativ" oder ein hinterlegter Sentiment-Wert abgegriffen. Die Summe dieser Werte ergibt schließlich den Sentiment-Score des ganzen Texts. Für englische Texte sind in R bereits Bibliotheken hinterlegt (z.B. im Package tidytext). Für deutsche Texte habe ich auf meiner Recherche die Bibliothek SentiWS der Universität Leipzig gefunden. Die rund 16.000 positiven und 18.000 negativen Wörter sind mit einer Wertspanne von -1 zu 1 hinterlegt.
Das Problem ist, dass diese in zwei Textdateien kommen, deren Format erst aufbereitet werden muss. So sieht die Bibliothek beim Einlesen aus:
Mit folgendem Code habe ich mir die Bibliothek operationalisiert:


library(dplyr)
# SentiWS - Dateien hier runterladen: https://wortschatz.uni-leipzig.de/en/download

# a) negative Wörter
# die Textdatei einlesen
negativ <- read.table("~/.../Sentiment Analysis/SentiWS_v2.0_Negative.txt", fill = TRUE)
# zuerst separieren wir die Wörter in V1
neg1 <- negativ %>%
  select(V1, V2) %>% #wir brauchen nur diese beiden Spalten
  mutate(V1 = as.character(V1)) %>%  #benötigt für den nächsten Schritt
  mutate(V1 = sub("\\|.*","\\",V1)) %>% #bereinigt ohne den Anhang nach "|"
  `colnames<-`(c("word", "sentiment")) #Spalten werden umbenannt
# nun separieren wir die Wörter in V2
einzel_negativ <- strsplit(as.character(negativ$V3), split = ",") #die aufgelisteten Wörter werden getrennt
neg2 <- data.frame(V1 = rep(negativ$V2, sapply(einzel_negativ, length)), V3 = unlist(einzel_negativ)) %>% #und mit den Werten in V2 wieder zusammengefügt
  `colnames<-`(c("sentiment", "word")) #Spalten werden umbenannt

# b) positive Wörter
# die Textdatei einlesen
positiv <- read.table("~/.../Sentiment Analysis/SentiWS_v2.0_Positive.txt", fill = TRUE)
# zuerst separieren wir die Wörter in V1
pos1 <- positiv %>%
  select(V1, V2) %>% #wir brauchen nur diese beiden Spalten
  mutate(V1 = as.character(V1)) %>%  #benötigt für den nächsten Schritt
  mutate(V1 = sub("\\|.*","\\",V1)) %>% #bereinigt ohne den Anhang nach "|"
  `colnames<-`(c("word", "sentiment")) #Spalten werden umbenannt
# nun separieren wir die Wörter in V2
einzel_positiv <- strsplit(as.character(positiv$V3), split = ",") #die aufgelisteten Wörter werden getrennt
pos2 <- data.frame(V1 = rep(positiv$V2, sapply(einzel_positiv, length)), V3 = unlist(einzel_positiv)) %>% #und mit den Werten in V2 wieder zusammengefügt
  `colnames<-`(c("sentiment", "word")) #Spalten werden umbenannt (Achtung, andere Reihenfolge)

# c) gemeinsames Lexikon aus den vier Dataframes
SentiWS_df <- rbind(neg1 %>%
                      mutate(Polarität = "negative"),
                    neg2%>%
                      mutate(Polarität = "negative"),
                    pos1 %>%
                      mutate(Polarität = "positive"), 
                    pos2 %>%
                      mutate(Polarität = "positive")) %>%
  mutate("word" = as.character(word))
SentiWS_df <- SentiWS_df[!duplicated(SentiWS_df$word),] #manche Wörter kommen durch die Umwandlung dopppelt vor; jeweils der erste wird behalten

Nun haben wir folgendes Format, mit dem wir arbeiten können:


Im Folgenden benutzen wir diese Bibliothek für die Analyse der Tweets des Manager Magazins und der Wirtschaftswoche für 2020. Mal schauen, was wir über die allgemeine Wirtschaftslage in Zeiten von Corona herausfinden können:

Interessant ist, dass die positiven Wörter konstant häufiger benutzt werden als negative Wörter wie Plot 1 zeigt. Doch bedenken wir, dass das Lexikon mehr negative als positive Wörter hat, so macht ein Blick auf die relative Frequenz Sinn. Mit Blick auf das Verhältnis der positiven zu negativen Wörtern können wir zugleich die Wörter ohne Ausprägung auslassen. Da wir uns für die Stimmung interessieren, macht dies Sinn. Plot 2 zeigt, dass es einzelne Tage sind, an denen die Anzahl positiver Wörter signifikant höher ist als die negativer Wörter.
Interessant wird Plot 3, wo wir auf die Verwendung beider Polaritäten verzichten und die negative Polarität als die Invertierung der Anzahl positiver Wörter benutzen und vice versa. Die skalierten Werte ergibt sich ähnelnde Verläufe mit durchwegs negativem Score. Der Verlauf ähnelt dem eigentlichen Sentiment-Wert, mit welchem die Wörter hinterlegt sind. Wir sehen den negativen Verlauf - trotz Ausreißer mit negativem Trend in Plot 4. Dies bedeutet, dass die wenigen negativen Wörter erheblicher ins Gewicht fallen (also gravierender sind) als die positiven. Dieses neue Bild passt auch gut zur aktuellen Wirtschaftslage.
Kann man aus den Scores noch mehr lesen? Ich habe geschaut, wie der Zusammenhang zum DAX im gleichen Zeitraum aussieht. Die Korrelationsmatrix zeigt allerdings einen schwachen (blau - positiv & rot - negativ) und nicht-signifikanten Zusammenhang (Größe und Transparenz). Somit kann man die Scores zwar nicht als Stimmungsbarometer der deutschen Wirtschaft nehmen; die Intention ist aber auch nicht gegeben.





Und hier noch der Code in RStudio
# für die Verbindung zu Twitter
library(rtweet)
# für basics
library(dplyr)
# für die Textanalyse
library(stopwords)
library(tidytext)
library(scales)
# für die Data Prep
library(tidyverse)
# für die Aktiendaten
library(quantmod)
library(ISOweek)
# für die Korrelation
library(Hmisc)
library(corrplot)

# Twitter-Daten laden & aufbereiten
#tweets <- search_tweets("Lufthansa", n = 18000, lang="de") ## search for tweets containing the word data
#tweets_lufthansa <- tweets
tweets <- get_timelines(c("manager_magazin","wiwo"), n = 10000)
custom_stop_words <- bind_rows(tibble(word = c("twitter", "tco"), lexicon = c("custom")),
                               tibble(word = stopwords("de"), lexicon = c("stopwords")))
tweets_words <- tweets %>%
  mutate(tweet_number = row_number())%>%
  select(tweet_number, text, created_at)%>%
  as_tibble() %>%
  mutate(text = str_replace_all(text, "[^\x01-\x7F]", ""),
         text = str_replace_all(text, "\\.|[[:digit:]]+", ""),
         text = str_replace_all(text, "https|amp|t.co", ""),
         text = gsub("http.*","", text),
         text = gsub("https.*","", text),
         text = str_replace_all(text,"&amp;|&lt;|&gt;", ""))
tweets_words <- tweets_words %>%  
  unnest_tokens(word, text) %>%
  anti_join(custom_stop_words, by = "word")

# Sentiment-Analyse
tweets_sentiment <- tweets_words %>%
  left_join(SentiWS_df, by="word") 
# Plot1 - Anzahl positiver & negativer Wörter pro Tag
tweets_sentiment %>%
  drop_na() %>%
  mutate("created_at" = as.Date(created_at)) %>%
  group_by(created_at) %>%
  count(Polarität) %>%
  ggplot(aes(x=created_at, y=n, group=Polarität, color=Polarität)) +
  geom_line(size=0.6, alpha=0.6)+
  geom_smooth(span=0.2, se=FALSE, size=0.8)+
  scale_colour_brewer(palette = "Set1") +
  theme_minimal() +
  labs(
    x = NULL, y = NULL,
    title = "Anzahl positiver & negativer Wörter",
    subtitle = "aggregiert pro Tag",
    caption = "Plot 1"
  )
# Plot2 - Verhältnis positiver zu negativen Wörtern pro Tag
tweets_sentiment %>%
  drop_na() %>%
  mutate("created_at" = as.Date(created_at)) %>%
  group_by(created_at) %>%
  count(Polarität) %>%
  spread(Polarität, n, fill=0) %>%
  mutate(relation = positive/negative) %>%
  ggplot(aes(x=created_at, y=relation, group=1)) +
  geom_line(size=1, color="#004C99")+
  theme_minimal() +
  labs(
    x = NULL, y = NULL,
    title = "Verhältnis positiver zu negativen Wörtern",
    subtitle = "aggregiert pro Tag",
    caption = "Plot 2"
  )
# Plot3 - Sentiment-Score pro Tag
tweets_sentiment2 <-tweets_sentiment %>%
  drop_na() %>%
  mutate("created_at" = as.Date(created_at)) %>%
  group_by(created_at) %>%
  count(Polarität) %>%
  spread(Polarität, n, fill=0)
tweets_sentiment2$score_p <- rescale(tweets_sentiment2$positive, to=c(-1,1)) #bei dieser Anwendungen wird die negative Polarität schlicht die Invertierung des positiven Sentiments
tweets_sentiment2$score_n <- rescale(tweets_sentiment2$negative, to=c(-1,1)) #bei dieser Anwendungen wird die negative Polarität schlicht die Invertierung des positiven Sentiments
tweets_sentiment2 %>%
  ggplot(aes(x=created_at, y=score_n, group=1)) +
  geom_line(size=0.6, alpha=0.4, color="#004C99")+
  geom_line(aes(y=score_p, group=1),size=0.6, alpha=0.4, color="#990000")+
  geom_smooth(span=0.2, se=FALSE)+
  geom_smooth(aes(y=score_p),span=0.2, se=FALSE, color="#990000")+
  labs(
    x = NULL, y = NULL,
    title = "Sentiment-Score",
    subtitle = "aggregiert pro Tag; blau - Invertierung der positiven Wörter & rot - Invertierung der negativen Wörter",
    caption = "Plot 3"
  )
# Plot 4 - Sentiment-Wert pro Tag
tweets_sentiment %>%
  drop_na() %>%
  mutate("created_at" = as.Date(created_at)) %>%
  group_by(created_at) %>%
  summarise(sentiment = sum(sentiment)) %>%
  ggplot(aes(x=created_at, y=sentiment, group=1)) +
  geom_line(size=0.6, alpha=0.6, color="#004C99")+
  geom_smooth(span=0.2, se=FALSE)+
  theme_minimal() +
  labs(
    x = NULL, y = NULL,
    title = "Sentiment-Wert",
    subtitle = "aggregiert pro Tag",
    caption = "Plot 4"
  )

# Zusammenhang zum Dax
l<- c("^GDAXI")
start_date = "2019-11-05"
getSymbols(l,src="yahoo", from=start_date)
l[1] <- "GDAXI"
GDAXI <- data.frame(date=index(GDAXI), coredata(GDAXI))
# den Sentiment-Score zum Dataset mit den Sentiment-Scores dazu
data_sent <- tweets_sentiment %>%
  select(created_at, sentiment) %>%
  drop_na() %>%
  mutate("created_at" = as.Date(created_at)) %>%
  group_by(created_at) %>%
  summarise(sentiment = sum(sentiment))
joined_data <- left_join(tweets_sentiment2, data_sent, by="created_at")
joined_data <- left_join(joined_data, GDAXI, by=c("created_at" = "date")) %>%
  filter(complete.cases(GDAXI.Close)) 
# Plot 5 - Correlation Matrix
res2 <- rcorr(as.matrix(joined_data[2:ncol(joined_data)]))
#Positive correlations in blue, negative  in red Color intensity & size of circle proportional to correlation coefficients
corrplot(res, type = "upper", order = "hclust", 
         tl.col = "black", tl.srt = 45)

Notiz: in wissenschaftlichen Artikeln soll das Lexikon wiefolgt zitiert werden:

R. Remus, U. Quasthoff & G. Heyer: SentiWS - a Publicly Available German-language Resource for Sentiment Analysis.

In: Proceedings of the 7th International Language Ressources and Evaluation (LREC'10), 2010

Photo by Ingo Joseph from Pexels

Beliebte Posts aus diesem Blog

Was ist fremd?

brandy74 "Malstunde" Some rights reserved. www.piqs.de Der Begriff Fremdheit wird benutzt zur Charakterisierung einer Beziehung. Immer muss etwas bekannt sein um es auch als fremd zu bezeichnen; andernfalls kann es nicht beschrieben werden. Wissenschaftlich wird die Fremdheit oft auch als die Gleichzeitigkeit von Nähe und Entferntheit, von Verbundenheit und Getrenntheit charakterisiert. Wer demnach etwas als fremd bezeichnet, unterscheidet die Welt an dieser Stelle in ein Innen und ein Außen. Das Fremde sei jenseits einer einer imaginären Grenze. Diese Grenzen können unterschiedlich lokalisiert werden. Bei der kulturellen Fremdheit werden andere kulturelle Verhaltensweisen und Ansichten identifiziert und als fremd bezeichnet. Bei der sozialen Fremdheit ist der Fremde hingegen Teil der eigenen Gesellschaft, der eigenen Gemeinschaft. Durch die Zuschreibung der sozialen Fremdheit wird er aus dem eigenen Bereich, also dem eigenen sozialen Milieu, exkludiert. Drückt sich

Migration und Bevölkerungsentwicklung: Solidarität und Selbsthilfe

Aus: Neue Potenziale - zur Lage der Nation in Deutschland , Juni 2014,  Berlin-Institut für Bevölkerung und Entwicklung Vor ein paar Wochen war ich auf einem sehr spannenden Vortrag am ifo-Institut in München von Herrn Dr. Klingholz, Direktor des Berlin Instituts für Bevölkerung und Entwicklung. Der Vortrag widmete sich einerseits der Zusammensetzung und dem Bildungs- wie Integrationsgrad deutscher Migranten und andererseits der zukünftigen Bevölkerungsentwicklung in Teilen der Welt und deren Auswirkungen auf die Migration in Europa, bzw. Deutschland. Polarisierend Unterteilt man die Migranten(1) nach Gruppen hinsichtlich ihrer Herkunftsländer, so zeigt sich oft eine starke Polarisierung des Bildungsgrades. Beispiel Rumänien und Polen. Zwar ist der Anteil der Migranten aus Rumänien und Polen ohne Bildungsabschluss wesentlich höher als der Anteil der Einheimischen. Umgekehrt ist der Anteil an Akademikern bei Migranten aus Rumänien und Polen höher als bei Einheimischen. Auch

die Hot-Dog-Ökonomie

Diego Torres Silvestre " Ice Creams, Hot Dogs & Pretzels" Some rights reserved. www.piqs.de Man stelle sich eine Wirtschaft vor, in der nur zwei Güter hergestellt würden: Würstchen und Brötchen. Konsumenten würden Hotdogs kaufen; also jeweils ein Brötchen mit einer Wurst. Die Fertigung geschieht durch Menschenhand. So fing Paul Krugman 1997 einen Artikel für das Online-Magazine Slate an, in welchem er den Zusammenhang von Technologie, Jobs und Kapitalismus erklären will. Er fährt fort, dass in dieser Wirtschaft 120 Millionen Arbeiter beschäftigt sind, was einer Vollbeschäftigung entspreche. Zur Herstellung einer Wurst oder eines Brötchens benötige es zwei Arbeitstage. Die 60 Millionen Angestellten in der Brötchenproduktion und genauso viele in der Wurstfabrikationen produzieren demnach täglich 30 Millionen Brötchen und Würste. Angenommen es komme eine verbesserte Technologie auf, mit deren Hilfe ein Arbeiter zur Herstellung einer Wurst nur noch einen Tag

die schöne Welt von Red Bull

Till Krech "wroooom" Some rights reserved. www.piqs.de Red Bull – vom Marktführer für Energiegetränke zum kommenden Medienimperium? Das Magazin Fast Company vergab in der Liste „The World´s 50 Most Innovative Companies“ den 29. Platz an Red Bull für genau diese Entwicklung. Gebündelt unter dem Dach der Red Bull Media House GmbH besitzt der Konzern mittlerweile verschiedene Medienbeteiligungen und Neugründungen. Kritiker bezeichnen es als eine gewaltige Marketingmaschine. Rund ein Drittel des Umsatzes wird für die Pflege des Marktauftritts ausgegeben. Eine firmeninterne Nachrichtenagentur sammelt Inhalte zu einen der vielen weltweiten aufsehenerregenden Red-Bull-Ereignisse, um sie externen Medien gebündelt und aufbereitet zur Verfügung zu stellen. Über eigene Medien werden die Konsumenten sogar direkt erreicht. Das 2007 gegründete Hochglanzmagazin "Red Bulletin" hat bereits eine Auflage von 5 Millionen Heften erreicht und wird mehrspraching in zwö

Verspargelung der Landschaft

FZ 18: "Mount Idarkopf" www.piqs.de Some Rights reserved. Vielleicht ist es, weil ich erst 22 Jahre alt bin. Vielleicht weil es bei meiner Heimatstadt schon seit mehr als zehn Jahren ein Windrad gibt. Aber das Argument einer Verspargelung der Landschaft durch Windräder zählt für mich nicht. Ich komme aus Baden-Württemberg. Insofern verfolgt mich das Argument der Verspargelung der Landschaft durch den ehemaligen baden-württembergischen Ministerpräsidenten Erwin Teufel fast genauso lange wie das Windrad vor meiner Haustür. Das Argument wird immer wieder von jenen hervorgebracht, welche gegen die Aufstellung von Windrädern sind. Die einen fürchten um die Landschaft, andere finden sie einfach nicht schön und noch andere bringen es nur als Vorwand. Besonders die Nähe zur Atomwirtschaft fällt einem bei der hießigen CDU auf. In Baden-Württemberg ist der Fall bei den Windrädern vielleicht ein bisschen spezieller. Wenn man hier die Windenergie effizient nutzen will, so