Ein konkreter und interessanter Nutzen der automatischen Textanalyse zeigt sich beim Twitter-Account TrumpOrNotBot. Basierend darauf, dass Trump früher ein Android-Handy benutzte von welchem aus er Tweets verschickte - während die langweiligeren Tweets von seinen Angestellten von einem iPhone aus versendet wurden - analysierte der Bot Trumps Schreibstil. Im Abgleich damit ordnet er nun jedem neuen Tweet eine Wahrscheinlichkeit zu, ob er von Trump persönlich komme.
Diese Klassifizierung habe ich im Folgenden benutzt, um einen möglichen Unterschied im Schreibstil zu identifizieren. Mit dem rtweet-package erhalten wir die letzten drei Tage. (Achtung! Dies hier ist ein sehr kleiner Datensatz; mit den folgenden Erkenntnissen ist nichts bewiesen, nur veranschaulicht.) Im nächsten Schritt habe ich alle Tweets mit einer Wahrscheinlichkeit über 50% als von Trump stammend betrachet; von den insgesamt 95 Tweets stammen somit 23 von Trump persönlich.
Die Uhrzeit der Trump-Tweets unterscheidet sich auch. Tatsächlich gibt es ja das Gerücht, dass sich Trump v.a. abends gerne mit Twitter beschäftigt.
Und es gibt Unterschiede im Schreibstill zwischen Trump und seinen Angestellten. Im Folgenden die Häufigkeit benutzter Begriffe:
Anhand dieser unterschiedlich verwendeten Begriffe lässt sich die statistische Ähnlichkeit zwischen den beiden Autorengruppen berechnen. Im Folgenden Schaubild sind die Begriffe nach ihrer Häufigkeit in Tweets von Trump (x-Achse) und seiner Angestellten (y-Achse) abgebildet; die Begriffe unter der roten Linie werden also eher von Trump benutzt. Insgesamt ergibt sich anhand der verwendeten Wörter eine signifikante Korrelation von ca. 0.4; heißt, dass die benutzten Wörter ähnlich sind (deswegen die Signifikanz), aber dann doch unterschiedlich (bei einem Wert von 1.0 wäre es identisch).
Dieser Umstand, dass die beiden Autorengruppen Wörter unterschiedlich häufig benutzen, lässt sich auch dafür benutzen, die - statistisch - jeweils wichtigsten Wörter zu identifizieren. Hier wird Zipf´s Law beschrieben, wonach von der Häufigkeit eines Worts auf seine Wichtigkeit rückgeschlossen werden kann. Im Folgenden die Wörter für die beiden Autorengruppen:
Auch die Sentimentwertanalyse habe ich schon häufiger vorgestellt; also ob ein Text eher positiv- oder negativ-konnotiert ist. Hier gibt es einen erheblichen Unterschied: Trumps eigene Tweets sind negativ geprägt, während sein Team positiv schreibt.
Auch die Emotionalität ist unterschiedlich. Manchen Begriffen ist eine Emotion zugeordnet; die gesamte Häufigkeit der Erwähnung ist im Folgenden durch die Anzahl der Tweets geteilt, um der unterschiedlichen Häufigkeit Rechnung zu tragen. Trump benutzt also häufiger emotional-konnotierte Begriffe, während sein Team evtl. auch nur sachliche Ankündigungen tweetet.
Zum Schluss können wir noch betrachten, ob es thematisch einen Unterschied gibt. Tatsächlich lassen sich genau zwei verschiedene Themen für die letzten drei Tage identifizieren, die auch jeweils v.a. von Trump oder v.a. von seinen Angestellten stammen; im Folgenden deren wichtigsten Wörter:
Diese kurze Untersuchung beweist zwar nichts, zeigt aber gut auf, wie man solch statistische Methoden der Textanalysen verwenden kann, um Autoren zu identifizieren.
Und hier noch der Code in RStudio
library(rtweet)
library(dplyr)
library(stringr)
library(ggplot2)
library(viridis)
library(tidytext)
library(gridExtra)
library(lubridate)
library(tidyr)
library(scales)
library(reshape)
# data prep
tweets <- get_timeline("TrumpOrNotBot", parse = TRUE, check = TRUE)
tweets_data <- tweets %>%
select(text, quoted_text, quoted_created_at, quoted_favorite_count, quoted_retweet_count) %>%
mutate(text = substring(text, 50,60)) %>% #to narrow down
mutate(text = as.numeric(gsub("\\D", "", text))) %>% #to get only numbers
mutate(author = ifelse(text >= 50, "Trump", "staff")) %>%
mutate(text = NULL) %>%
mutate(tweet_number = row_number()) %>%
mutate(quoted_text = str_replace_all(quoted_text, "[^\x01-\x7F]", ""),
quoted_text = str_replace_all(quoted_text, "\\.|[[:digit:]]+", ""),
quoted_text = str_replace_all(quoted_text, "https|amp|t.co", ""))
# Plot 1 - zeitl. Verlauf der Tweets
tweets_number <- tweets_data %>%
mutate(date = as.Date(quoted_created_at, format="%m/%d/%Y")) %>%
filter(date >= "2020-08-12") %>%
group_by(date) %>%
count(date, author)
tweets_number %>%
ggplot(aes(x=date, y=n)) +
geom_bar(aes(fill=author), stat="identity", position=position_dodge(preserve = "single"), na.rm=TRUE) +
scale_fill_viridis(discrete=TRUE, option="cividis", direction=-1) +
theme_minimal() +
labs(
x = NULL, y = NULL,
title = paste("Anzahl an Tweets"),
subtitle = ("von TrumpOrNotBot analysierte Trump tweets"),
caption = "Plot1")
tweets_number %>%
group_by(author) %>%
summarise(total_tweets = sum(n))
# Um wie viel Uhr getweetet?
tweets_number2 <- tweets_data %>%
#mutate(time = as.POSIXct(strptime(.$quoted_created_at, "%H:%M:%S"))) %>%
mutate(time = as.POSIXlt(.$quoted_created_at)$hour) %>%
#mutate(date = as.Date(quoted_created_at, format="%h %m")) %>%
group_by(time) %>%
count(time, author)
tweets_number2 %>%
ggplot(aes(x=author, y=time, fill=author)) +
geom_boxplot(outlier.shape=8) +
scale_fill_viridis(discrete=TRUE, option="cividis", direction=-1) +
theme_minimal() +
theme(legend.position="none") +
labs(
x=NULL, y=NULL,
title = "Unrzeit der Trump Tweets",
subtitle = "von TrumpOrNotBot analysierte Trump tweets",
caption = "Plot 2")
# Plot 3 - häufigsten Begriffe
custom_stop_words <- bind_rows(tibble(word = c("realdonaldtrump", "tco", "ive", "pm", "jxoffxyzed", "ag", "im", "met"),
lexicon = c("custom")),
stop_words)
tweets_data_token <- tweets_data %>%
unnest_tokens(word, quoted_text) %>%
anti_join(custom_stop_words, by = "word")
plot1 <- tweets_data_token %>%
filter(author=="staff") %>%
count(word, sort = TRUE) %>%
slice(1:15) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(x = word, y = n, fill = word))+
geom_col(show.legend = FALSE) +
coord_flip() +
theme_minimal() +
scale_fill_viridis(discrete = TRUE, option="cividis") +
labs(
subtitle = ("Tweets not from Trump"),
x = NULL, y = NULL,
caption = " ")
plot2 <- tweets_data_token %>%
filter(author == "Trump") %>%
count(word, sort = TRUE) %>%
slice(1:15) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(x = word, y = n, fill = word))+
geom_col(show.legend = FALSE) +
coord_flip() +
theme_minimal() +
scale_fill_viridis(discrete = TRUE, option="cividis") +
labs(
subtitle = ("Tweets from Trump"),
x = NULL, y = NULL,
caption = "Plot 3")
grid.arrange(plot1, plot2, ncol=2, nrow=1, top = "häufigste Begriffe der Tweets")
# Plot 4: Vergleich der häufigsten Wörter
Korr <- cor.test(frequency$Trump, frequency$staff, use="complete.obs") # Statist. Ähnlichkeit zwischen den beiden Autoren nach Worthäufigkeit
frequency <- tweets_data_token %>%
group_by(author) %>%
count(word, sort = TRUE) %>%
left_join(tweets_data_token %>%
group_by(author) %>%
summarise(total = n())) %>%
mutate(freq = n/total) %>%
select(author, word, freq) %>%
spread(author, freq) %>%
arrange(Trump, staff)
frequency %>%
ggplot(aes(Trump, staff)) +
geom_jitter(alpha = 0.1, size = 2.5, width = 0.25, height = 0.25) +
geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) +
scale_x_log10(labels = percent_format()) +
scale_y_log10(labels = percent_format()) +
geom_abline(color = "red") +
theme(plot.title = element_text(size = 5, hjust = 0.5)) +
theme_minimal() +
labs(
title = paste("Vergleich der häufigsten Wörter"),
subtitle = paste("Korrelation:", round(Korr$estimate,4), " & p-Value:", format.pval(Korr$p.value, digits = 4, nsmall = 3, eps = 0.001)),
caption = "Plot4")
# Plot 5 - wichtige Wörter (nach Zipf´s Law), welche die beiden Autorengruppen im Vergleich ausmachen
tweets_data_token %>%
count(author, word, sort = TRUE) %>%
ungroup() %>%
bind_tf_idf(word, author, n) %>%
arrange(desc(tf_idf)) %>%
mutate(word = factor(word, levels = rev(unique(word)))) %>%
group_by(author) %>%
arrange(desc(tf_idf)) %>%
slice(1:15) %>%
ungroup() %>%
ggplot(aes(word, tf_idf, fill = author)) +
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
scale_fill_viridis(discrete = TRUE, option="cividis", direction=-1) +
facet_wrap(~author, ncol = 2, scales = "free") +
coord_flip() +
labs(
x = NULL, y = NULL,
title = paste("Im Vergleich charakteristische, wichtige Wörter"),
subtitle = "von TrumpOrNotBot analysierte Trump tweets",
caption = "Plot 5")
# Plot 6 - Sentimentwerte
dat_sentiment1 <- tweets_data_token %>%
group_by(author) %>%
inner_join(get_sentiments("bing"), by="word") %>%
count(sentiment) %>%
spread(sentiment, n, fill = 0) %>%
mutate(sentiment_bing = positive - negative) %>%
ungroup()
dat_sentiment2 <- tweets_data_token %>%
group_by(author) %>%
inner_join(get_sentiments("afinn"), by="word") %>%
summarise(sentiment_afinn = sum(value)) %>%
ungroup()
dat_sentiment <- left_join(dat_sentiment1, dat_sentiment2, by=c("author"))
dat_sentiment %>%
select(author, sentiment_bing, sentiment_afinn) %>%
as.data.frame() %>%
melt(., id.vars=c("author")) %>%
ggplot(aes(x = variable, y = value, fill = author))+
geom_bar(aes(fill=author), stat="identity", position=position_dodge(preserve = "single"), na.rm=TRUE) +
scale_fill_viridis(discrete=TRUE, option="cividis", direction=-1) +
theme_minimal() +
labs(
x = NULL, y = NULL,
title = paste("Sentimentwerte nach Autor"),
subtitle = ("von TrumpOrNotBot analysierte Trump tweets"),
caption = "Plot6")
# Plot 7 - Emotionen
dat_sentiment3 <- tweets_data_token %>%
group_by(author) %>%
inner_join(get_sentiments("nrc"), by="word") %>%
count(sentiment) %>%
spread(sentiment, n, fill = 0) %>%
ungroup() %>%
as.data.frame() %>%
melt(., id.vars=c("author")) %>%
mutate(value = ifelse(.$author == "Trump", .$value / 23 , .$value / 72)) # Anzahl an Tweets
dat_sentiment3 %>%
ggplot(aes(x = variable, y = value, fill = author))+
geom_bar(aes(fill=author), stat="identity", position=position_dodge(preserve = "single"), na.rm=TRUE) +
scale_fill_viridis(discrete=TRUE, option="cividis", direction=-1) +
theme_minimal() +
labs(
x = NULL, y = NULL,
title = paste("die Häufigkeit von emotionalen Begriffe pro Tweet"),
subtitle = ("die Anzahl der Emotionen insgesamt geteilt durch die Anzahl an Tweets"),
caption = "Plot 7")
# Plot 8: zwei gemeinsame Themen in beiden Texten
texts_DTM <- tweets_data_token %>%
count(author, word) %>%
cast_dtm(author, word, n) %>%
LDA(., k=2, control = list(seed = 1234))
topics_prob <- tidy(topics, matrix = "beta") #probability of that term being generated from that topic for each line
topic_terms <- topics_prob %>%
group_by(topic) %>%
top_n(10, beta) %>% #hier anpassen
ungroup() %>%
arrange(topic, -beta)
topic_terms %>%
mutate(term = reorder_within(term, beta, topic)) %>%
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
scale_fill_viridis(discrete=TRUE, option="cividis", direction=-1) +
coord_flip() +
scale_x_reordered() +
theme_minimal() +
labs(
x = NULL, y = NULL,
title = paste("Themen in Trump Tweets"),
subtitle = ("statistisch gesehen stammt Thema 1 v.a. von den Angestellten und Thema 2 v.a. von Trump persönlich"),
caption = "Plot 8")
# gamma ist der Anteil an Wörtern, aus welchen jeweils ein Thema generiert wurde
# d.h. je mehr ein PDF ein Thema ausmacht, desto höher das Gamma
# -> thematischer Unterschied, wenn jeweils ein PDF zu einem Topic beiträgt
topic_documents <- tidy(topics, matrix = "gamma")
topic_documents
Photo by Carlos Herrero from Pexels