-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrump-tweet-analysis.Rmd
784 lines (582 loc) · 40 KB
/
trump-tweet-analysis.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
---
title: "Trump Tweet Text Analysis"
author: "Pascal Niggemeier"
date: "24.06.2019"
output:
html_notebook:
fig_caption: yes
fig_height: 7
fig_width: 10
number_sections: yes
toc: yes
toc_float: yes
---
---
Für die Dokumentation des Projekts wird eine R-Markdown Datei gewählt, weil sie das Reproduzieren des Projektes und der Ergebnisse für den Leser vereinfacht. Für die Präsentation der Ergebnisse liegt eine separate PowerPoint-Präsentation vor.
Der nachfolgende Code-Chunk lädt die benötigten Packages und die Textdaten von Twitter.
```{r results=FALSE}
if(!require('tidyverse')){
install.packages('tidyverse')}; library('tidyverse')
if(!require('tidytext')){
install.packages('tidytext')}; library('tidytext')
if(!require('textdata')){
install.packages('textdata')}; library('textdata')
if(!require('syuzhet')){
install.packages('syuzhet')}; library('syuzhet')
if(!require('lubridate')){
install.packages('lubridate')}; library('lubridate')
if(!require('topicmodels')){
install.packages('topicmodels')}; library('topicmodels')
if(!require('jsonlite')){
install.packages('jsonlite')}; library('jsonlite')
if(!require('scales')){
install.packages('scales')}; library('scales')
tweets <- as.tbl( fromJSON('src/tweets-2019-06-22.json') )
```
# Einleitung: Text-Mining am Beispiel der Twitter Posts von Donald Trump
Das Thema wurde für die Ausarbeitung, neben anderen Punkten, aufgrund des Unterhaltungswert der Projektauslegung auf der einen Seite und der Möglichkeit neue Methoden kennen zu lernen, gewählt.
Es soll die Hypothese von Todd Vaziri bestätigt werden. Es gibt dazu bereits zahlreiche Untersuchungen, jedoch wird es in dieser Ausarbeitung mit einigen neuen Akzenten nachvollzogen. Die Analyse wird dabei strukturiert anhand des *Tidy-Data*-Prinzips und zugehörigem Prozess durchgeführt. Sie soll helfen, die Frage nach der Erkennbarkeit der Stimmungslage (Sentiment) in einzelnen Tweets und über die Zeit zu beantworten. Zudem wird nach einer geeigneten Methode für das automatische Erkennen von Themen innerhalb von Texten gesucht.
Die Ausarbeitung legt ihren Schwerpunkt auf die Analyse der Themen mit denen sich der Twitter account *@realDonaldTrump* befasst, also [Topic modeling - Themenanalyse]. Daher wird dieses Kapitel etwas ausführlicher ausgelegt. Somit soll die intensive Auseinandersetzung mit der verwendeten Methode sichergestellt werden.
# Grundlagen: Textanalyse und Text-Mining mit R
Im Folgenden soll ein Einstieg in die Textanalyse mit R geboten werden. Die Inhalte bieten die Grundlage für das eigentliche Projekt *"Eine Analyse der Tweets von Donals Trump"*, alias [@realDonaldTrump](https://twitter.com/realdonaldtrump).
## Preprocessing & Exploration
*tidy data*-Prinzipien
Das *'Säubern'* der Textdaten
Ausgangspunkt ist der Text-Mining-Prozess aus der Abbildung:

Dieser Text-Mining-Prozess wird im Folgenden schrittweise ergänzt.
## Sentimentanalyse
Eine Sentimentanalyse (auch Stimmungsanalyse) ist eine spezielle Ausprägung des Text-Minings. Beim Text-Mining werden Daten in Form von Text mit verschiedenen quantitativen und qualitativen Methoden analysiert. Handelt es sich bei den Textdaten um natürliche Sprache, also durch Menschen formulierte und ausgedrückte Texte, spricht man von *Natural Language Processing* (NLP). Weiternoch geht es beim NLP um die Verarbeitung dieser Textdaten und vor allem um deren Interpretation. Die Interpretation kann bspw. durch die Aufnahme von Stimmen (gesprochene Sprache) und anschließende Übersetzung in textuelle Bausteine erfolgen. Interpretation kann außerdem eine Übersetzung von Stimm- oder Textdaten in eine gewisse Semantik dieser Daten bedeuten.
Sentimentanalyse ist das Feststellen der Stimmung bzw. der Polarität eines Textes und wird auch *Opinion Mining* genannt.
> Die Stimmungsanalyse ist eines der Felder der Natural Language Processing, das sich der Erforschung subjektiver Meinungen oder Gefühle widmet, die aus verschiedenen Quellen zu einem bestimmten Thema gesammelt wurden.
--- Volodymyr Bilyk
Dem Begriff *Sentiment* sind laut Duden die Bedeutungen *Empfindung* sowie *Gefühl* zuzuordnen und ist synonym verwendbar zu *Gemüt*, *Regung* oder *Sinn*. Letztlich ist die Implementierung von Sentimentanalysen verglichen mit anderen fortgeschrittenen statistischen Methoden einfach zu erledigen, wenn man sich lediglich auf die Zuordnung der sog. Sentimente zu den Textabschnitten bezieht. Dies wird lediglich durch bestimmte Arten von *Joins* durchgeführt (s. [Vorgehen und Methoden der Implementierung der Sentimentanalyse]). Sie wird erst durch die Interpretation, die statistische Auswertung und das adäquate Zusammenführen der Daten aus verschiedenen Quellen komplizierter. Komplexität erlangt die Thematik durch Folgendes:
* die Fragestellungen
* der Konzeption der Durchführung
* Auswahl der richtigen Methoden u. Vorgehensweisen
* Reduktion des Problems bzw. der komplexität auf das Wesentliche
* die Interpretaton der Ergebnisse
* Transfer der Ergebnisse aus den Methoden der Sentimentanalyse in weiterführende Methoden
Referenz: https://theappsolutions.com/blog/development/sentiment-analysis/
### Vorgehen und Methoden der Implementierung der Sentimentanalyse
Wenn sich menschliche Leser einem Text nähern, nutzen sie ihre Erfahrung oder Verständnis, das sie über die emotionale Absicht von Wörtern im Laufe ihres Lebens gelernt haben. Daraus leiten sie ab, ob ein Textabschnitt positiv oder negativ ist oder vielleicht durch ein anderes nuancierteres Gefühl wie Überraschung oder Ekel gekennzeichnet ist. Für die programmatische Verarbeitung von Wörtern, können die Werkzeuge des Text-Minings verwendet werden. Die nachfolgende Abbildung ergänzt den Text-Mining-Prozess um entsprechende Inhalte:

Eine Möglichkeit, das Stimmungsbild eines Textes zu analysieren, besteht darin, den Text als eine Kombination seiner einzelnen Wörter und den Stimmungsbildgehalt des gesamten Textes als die Summe des Stimmungsbildes der einzelnen Wörter zu betrachten. Dies ist nicht die einzg mögliche Methode oder Vorgehen, jedoch ist er weit verbreitet. Zudem ist es ein Ansatz mit dem naturgemäß die Vorteile des *'tidy'*-Ökosystems ausgenutzt werden können und außerdem zahlreiche Leitfäden zum Vorgehen vorliegen. Daher wird dieser Ansatz für diese Ausarbeitung gewählt.
### Sentiment-Lexika
Die Sentiment-Lexika enthalten Bewertungen oder *Scores* einzelner, ausgewählter Wörter. Sie spiegeln die Emotionen oder repräsentierte Meinungen wider, die sich als Information in Textdaten wiederfinden. Sie sind im `tidytext`-Package frei verfügbar. Das *nrc*-Lexikon wird mittlerweile nicht mehr unterstützt. Sentiment-Lexika verfolgen verschiedene Ansätze:
* **BING**: binär (positive, negative)
* **LOUGHRAN** oder **NRC**: Emotionen / nominal (Bsp. anger, fear, happiness, sadness, trust)
* **AFINN**: Score (Intervalle, Bsp. $[-5, 5]$)
```{r}
get_sentiments("bing")
```
```{r}
get_sentiments("afinn")
```
```{r}
get_sentiments("loughran")
```
### *Inner Join* mit Lexika
That‘s it! Nach den Methoden der „Tidy Data“- Prinzipien wird durch einen relativ einfachen „Join“ (`inner_join`) die Sentimentanalyse implementiert. Auf diese Weise werden aus der Originalquelle nur diejenigen Wörter erhalten, die in beiden Quellen, dem Text und dem Sentiment-Lexikon, enthalten sind.
```{r eval=FALSE}
data %>%
inner
```
Theoretisch könnte man an dieser Stelle stoppen. Das Interessante ist nachfolgend, wie man aus den Verknüpfungen zw. Token und Sentimenten Wissen generieren kann.
## Topic modeling - Themenanalyse
In dieser Ausarbeitung wird der zweite Schwerpunkt auf die Analyse der Themen gelegt, mit denen sich der Twitter account *@realDonaldTrump* befasst. Daher wird dieses Kapitel etwas ausführlicher ausgelegt. Somit soll die intensive Auseinandersetzung mit der verwendeten Methode sichergestellt werden.
Bisher wurde gezeigt, dass Wortzahlen und Visualisierungen etwas über Inhalte aussagen. Sentiment-Lexika wurden zum Feststellen der emotionalen Wertigkeit eines Dokuments verwendet. Nun soll über die Wortzahlen hinaus die zugrundeliegenden Themen in einer Sammlung von Dokumenten aufgedeckt werden. Dazu wird ein Wahrscheinlichkeitsmodell für *Dokumente* verwendet. **Latent Dirichlet allocation** (LDA) wurde von David Blei, Andrew Ng und Michael I. Jordan vorgestellt. Es darf nicht mit *Linear discriminant analysis*, ebenfalls abgekürzt mit LDA, verwechselt werden. In diesem Dokument bezieht sich LDA auf *Latent Dirichlet allocation*.
LDA ist ein Standard-Themen-Modell, durch das *topic modeling* implementiert werden kann. Es sucht nach Mustern von Wörtern, die innerhalb und über eine Sammlung von Dokumenten hinweg auftreten, auch bekannt als Korpus (Gesamtheit zusammengehöriger Textdaten). Dabei werden $k$ Themen innerhalb eines Korpus gefunden, indem für jedes Dokument eine separate Sammlung von Wörtern erstellt und Wörter ausgegeben werden, um nach Mustern zu suchen, in denen Wörter zusammengehörig vorkommen. Die Reihenfolge der Wörter ist dabei unerheblich.
Die ausgegebenen Themen sind eine Liste aller Wörter im Korpus, denen eine Auftrittswahrscheinlichkeit innerhalb der Themen zugeordnet wird. Worte, die häufig zusammen auftreten, bekommen höhere $p$-Values (genauer $\beta$-Werte) innerhalb eines oder mehrer Themen zugeordnet.
Da die Methode nach Mustern und Zusammenhängen sucht, statt sie vorherzusagen, kann sie den *Unsupervised Learning*-Algorithmen aus dem Bereich des Maschinellen Lernens zugeordnet werden. *Topic modeling* kann daher mit *Clustering* verwechselt werden. Sie lassen sich trotz ihrer Ähnlichkeit unterscheiden: Gängige Clustering-Techniken wie *k-means* und *hierarchical clustering* basieren auf dem Abstand zwischen Objekten, der ein kontinuierliches Maß ist. Darüber hinaus ist jedes zu gruppierende Objekt einem einzelnen Cluster zugeordnet. Themenmodelle wie LDA basieren auf Worthäufigkeiten, was hingegen ein diskretes Maß ist. Zudem ist jedes Objekt (hier ein Dokument innerhalb eines Korpus) ein Teilmitglied jedes Themas.
Die nachfolgende Grafik erweitert den Text-Mining-Prozess von zuvor um *Topic Modeling*

Das Trainieren des Modells kann mittels der `LDA()`-Funktion implementiert werden. Um es auszuführen muss zunächst eine *Document Term Matrix* (DTM) erstellt und an `LDA()` übergeben werden. Ein `matrix`-Object ist ähnlich aufgebaut zu einem `DataFrame`-Objekt. Jedoch werden die Bestandteile statt *Observation* und *Variable* mit *Zeilen* und *Spalten* bezeichnet, wobei jede Spalte aus einem Datentypen bestehen muss. Eine DTM hat eine Zeile für jedes Document (entspricht im Praxisbeispiel später Tweets) und eine Spalte für jedes einzigartige Word oder Begriff über alle Dokumente in einem Corpus hinweg. Die Werte innerhalb der DTM sind die Zählungen oder Zahl der Verwendung jedes Begriffes innerhalb des zugehörigen Dokuments. Das führt dazu, dass i.d.R. der Großteil aller Werte $0$ enspricht. In einem solchen Fall spricht man von einer *dünnbesetzten Matrix*. Eine DTM kann mithilfe der Funktion `cast_dtm()` erstellt werden. Als Eingabewert erhält sie das *tidy* DataFrame aus den vorherigen Schritten des Preprocessings, das die Wortzählungen enthält.
Das Trainieren des Modells kann mittels der `LDA()`-Funktion implementiert werden. Sie gibt `LDA`-Objekt zurück, welches die Information über die Auftrittswahrscheinlichkeiten enthält. Sie werden in $\beta$-Werte übersetzt, welche letztlich interpretiert werden können.
```{r eval=FALSE}
library(topicmodels)
lda_fitted <- LDA(
dtm_input,
k = 2,
method = "Gibbs",
control = list(seed = 42)
)
```
Der code-Chunk zeigt die Verwendung von `LDA()`. Das erste Argument ist die DTM. Das zweite Argument `k` gibt an, wie viele Themen bestimmt werden sollen. `method` gibt die zu verwendene Schätzmethode an. Der *Default* Wert hier wäre eine schnelle Annäherung. Wenn jedoch vollständigere Methode aber dafür mit längerer Laufzeit bevorzugt wird, sollte der *Gibbs*-Sampler verwendet werden. Es kann zudem ein optionaler Seed gesetzt werden, um Repruduzierbarkeit zu unterstützen. Die Laufzeit der Methode kann bis zu einige Stunden betragen.
Das trainierte Modell ist ein spezielles R-Objekt, das für das Package `topicmodels` kodiert ist, wie es auch bei dem DTM der Fall ist. Funktionen wie `str()`, `summary()` oder `glimpse()` können den Inhalt des Modells anzeigen.
```{r eval=FALSE}
glimpse(lda_fitted)
```
Die Evaluierung des Modell-Objekts kann mithilfe eines der Werkzeuge aus dem `tidytext` Package erledigt werden. Insbesondere sind die wichtigsten Ergebnisse eines Themenmodells die Themen selbst: das Wörterbuch aller Wörter im Korpus, sortiert nach der Wahrscheinlichkeit, mit der jedes Wort im Rahmen dieses Themas auftritt. In diesem Fall kann die `tidy()`-Funktion die Matrix mit Auftrittswahrscheinlichkeiten der Themen in eine Form umwandeln, die erneut den *Tidy-Data*-Prinzipien folgt und somit auf einfache Weise mit `ggplot2` zur Visualisierung verwendet werden kann. Die Auftrittswahrscheinlichkeiten $\beta$ sollten zudem absteigend sortiert werden:
```{r eval=FALSE}
lda_fitted %>%
tidy(matrix = "beta") %>%
arrange(desc(beta))
```
Es resultiert die geordnete Auflistung der Wahrscheinlichkeiten. Es steht nun die Frage aus, wie man entscheiden soll, wie $k$ zu definieren ist. $k$ gibt die Anzahl der Themen an, nach denen `LDA` suchen soll. Die Antwort auf diese Frage steht im direkten Zusammenhang mit der Interpretation des Modells. Ein *Topic Model* zu interpretieren ist ein subjectiver Prozess. Denn wie auch bei anderen *Unsupervised Learning* Methoden, wie bspw. *Clustering*, gibt der Output der Modelle an, woraus die Cluster bestehen. Es fehlt jedoch eine Beschreibung der Richtung, ein Hinweis auf die Bedeutung der Ergebnisse. Die Bedeutung kann nicht trennscharf durch die Methoden bestimmt werden. Hier könnten bspw. Missverständnisse je nach unterschiedlicher Kultur der Betrachter auftreten. Gleichwohl könenn ganz andare, unerwartete Dimensionen als Stöfaktor in die Bewertung eingehen. Die Entscheidung also, wie viele Themen zu bestimmen sind, hängt vom Betrachter ab. Die **Schlüsselerkenntnis** hier sollte sein, dass $k$ so gewählt sind, dass die Ergebnisse ausreichend unterschiedlich zueinander sind, sodass der Betrachter sinnhafte Erkenntnisse extrahieren kann. Die Themen sollten sich nicht wiederholen.
Grundsätzlich kann man sich an folgendes Muster halten:
* weitere Themen hinzuzunehmen ($k$ erhöhen), ist so lange in Ordnung, wenn sie unterschiedlich zueinander sind
* wenn Themen sich wiederholen, wird $k$ zu groß
* Themen sollten durch einen *Überschrift* oder ein *Name* beschrieben werden, um sie greifbarer für die Interpretation zu machen
Die Implementation und das Ergebnis einer *Latent Dirichlet Allocation* wird im Praxisteil deutlich.
Referenz: https://www.tidytextmining.com/topicmodeling.html
# Dokumentation der Durchführung der Textanalyse
## Beschreibung der Daten
Die Textdaten (engl. *dataset*)...
```{r}
glimpse(tweets)
```
## Preprocessing & Tidying
Beim ersten Inspizieren der geladenen Daten fällt zunächst auf, dass die Angabe über das Tweet-Erstellungsdatum (`created_at`) als `character` formatiert ist:
```{r}
class(tweets$created_at)
```
Diese Variable wird als Datumsangabe benötigt. Mit der Funktion `as.POSIXct` lassen sich Datumsangaben nach dem POSIX-Standard erzeugen, welche sich gut mit R verarbeiten lassen. Die Zeitangaben aus der Quelle lassen sich mittels `strptime` von `character` Repräsentationen in POSIX\*-Objekte konvertieren. Zusätzlich muss für die Tweet Analyse bedacht werden, dass es sich bei den Zeitangaben i.d.R. nicht um die Zeitzone handelt, die wir in Mitteleuropa normalerweise verwenden (GMT, bzw. UTC). Wenn wir die Tweets des Accounts *@realDonaldTrump* untersuchen, ist es jedoch sinnvoll, die Zeitangaben in der Zeitzone des Besitzers zu beschreiben, um die Zeitverschiebung zu berücksichtigen.
```{r}
# prev <- Sys.getlocale("LC_TIME") # falls abweichend von "German_Germany.1252"
german <- "German_Germany.1252"
Sys.setlocale("LC_TIME", "C") # auf windows system muss zunächst die locale angepasst werden
POSIX_times_formatted <- strptime(tweets$created_at, "%a %b %d %H:%M:%S %z %Y")
# Trumpian time
tweets$created_at <- with_tz(as.POSIXct(POSIX_times_formatted), "EST")
Sys.setlocale("LC_TIME", german)
```
```{r}
summary(tweets)
```
Ein großer Teil der Analysen zielen darauf ab, die Source des Tweets zu unterscheiden. Es wird häufig behauptet, dass Unterschiede im Posting-Verhalten zwischen Posts von einem iPhone und Android Device festgestellt werden können. Diese Analyse soll bestätigen, dass dem so ist. Man weiß, dass Donald Trump zumindest eine Zeit lang ein Android Device verwendete (https://www.theverge.com/2015/10/5/9453935/donald-trump-twitter-strategy). Dieser Zeitraum kann festgestellt werden:
```{r}
unique(tweets$source)
```
```{r}
min(tweets[tweets$source == "Twitter for Android",]$created_at)
max(tweets[tweets$source == "Twitter for Android",]$created_at)
```
Für den Twitter-Account *@realDonaldTrump* wurde im Zeitraum Februar 2013 bis März 2017 unter anderem ein Android Device verwendet. Das gesamte Dataset wird auf diesen Zeitraum eingeschränkt, um sicherszustellen, dass in die Analysen keine Tweets einfließen, zu dessen Zeitpunkt das Android Device nicht existierte. Datei wird von vollen Jahren ausgegangen, also 01.01.2014 bis 31.12.2016. Außerdem wird auf Android und iPhone Devices eingeschränkt. Zusätzlich wird das Dataset weiter "gesäubert":
```{r}
names(tweets) <- c("device", "text", "created", "retweet_count", "favorite_count", "is_retweet", "id")
names(tweets)
```
```{r}
tweets <- tweets %>%
mutate(device = str_replace(device, "Twitter for ", "")) %>%
filter(device %in% c("Android", "iPhone") &
created >= "2014-01-01" &
created <= "2016-12-31") %>%
select(id, device, created, text)
tweets$device <- as.factor(tweets$device)
tweets[1:5,]
```
## Exploration
Eine Betrachtung der *Absolut*-Zahlen / Zählungen der Posts je Device:
```{r}
tweets %>%
count(device) %>%
mutate(percent = n/sum(n))
```
Das Android Device wird wesentlich reger verwendet. Ein Hinweis darauf, dass wir in den folgenden Analysen, wo sinnvoll, relative Werte verwenden sollten, um somit dem Klassifizierungs-Überhang (engl. Prevalence) der Klasse *'Android'* entgegenzuwirken.
### Tweet-Uhrzeit
Wann Postet der Twitter Account *@realDonaldTrump* ? Die Zeiten wurden hierfür bereits in die richtige Form gebracht (s.o.), denn hier ist vor allem die Zeitzone zu beachten.
```{r fig.height=6, fig.width=9}
tweets %>%
mutate(hour = hour(created)) %>%
count(device, hour) %>%
group_by(device) %>%
mutate(percent = n / sum(n)) %>%
ungroup() %>%
ggplot(aes(hour, percent, color = device)) +
geom_line(size = 1.1) +
scale_y_continuous(labels = percent_format()) +
theme(legend.position = 'bottom') +
labs(title = "Anteilige Tweets pro Uhrzeit u. Device",
x = "Tageszeit (EST)",
y = "% Tweets p. Device",
color = "")
```
Darstellung als Boxplots:
```{r fig.height=7, fig.width=8}
tweets %>%
mutate(minute = minute(created),
hour = hour(created)) %>%
count(hour, minute, device) %>%
ggplot(aes(as.factor(hour), n, fill = device)) +
geom_boxplot() +
geom_jitter(width = 0.1, alpha = 0.1) +
theme(legend.position = 'bottom') +
labs(title = "Anteilige Tweets pro Uhrzeit u. Device",
x = "Tageszeit (EST)",
y = "Anzahl Teets p. Minute",
color = "")
```
### Retweets
Über den Account *@realDonaldTrump* werden Tweets anderer User retweetet. Ein Tweet wird als Retweet eingeordnet, wenn er mit doppelten Anführungszeichen (*"*) beginnt. Andere Twitter User machten die Beobachtung, dass *@realDonaldTrump* diese besondere Angewohnheit für das Retweeten hat (statt die dafür eigentlich vorgesehene Funktion oder den gängigen Kürzel '*RT*' zu verwenden). Die folgende Grafik zeigt, auf welche Devices sich diese Retweets verteilen:
```{r}
tweets %>%
count(device, quoted = ifelse(str_detect(text, '^"'), "RT", "nicht-RT")) %>%
ggplot(aes(device, n, fill = quoted)) +
geom_bar(stat = "identity", position = "dodge") +
theme(legend.position = 'bottom') +
labs(title = 'Analyse der Tweet Texte\n"-Zeichen zu Beginn',
x = "",
y = "Anzahl Tweets",
fill = "")
```
Es ist erkennbar, dass die meisten Retweets tatsächlich vom Android Device stammen. Das unterstützt weiterhin die Hypthese, dass ein Unterschied zwischen den
### Tippfehler
Ein weiterer signifikanter Unterschied:
```{r}
tweets %>%
filter(str_detect(text, pattern = "\\s\\s")) %>%
count(device) %>%
ggplot(aes(device, n, fill = device)) +
geom_col() +
theme(legend.position = 'bottom') +
labs(title = "Tweets mit doppelten Leerzeichen",
x = "",
y = "Anzahl Tweets",
fill = "")
```
Dem vermeintlichen Präsidenten unterläuft wesentlich häufiger der Fehler eines doppelten Leerzeichens (regex: `\s`).
### Links, Bilder, Ankündigungen
Ein weiterer Unterschied, der anhand der Daten belegt werden kann ist folgender: Vom Android Device wird selten bis nie ein Link oder ein Bild gepostet. Der Präsident scheint dazu zu tendieren, Tweets wie die folgenden zu schreiben:
```{r}
tweets %>% filter(str_detect(text, "Very dishonest!")) %>% .$text
```
Während *The Ghost* via iPhone bisweilen Ankündigungen, Danksagungen oder sonstige Grüße ausspricht:
```{r}
tweets %>% filter(str_detect(text, "Thank you W")) %>% .$text
```
Diese Tweets sind zudem in einem ähnlichen Muster geschrieben, fast so, als diene eine interne Vorgabe als Vorlage. Insgesamt wirkt es "professionell". So würde man es von einem Kampagnen-Helfer oder -Mitarbeiter erwarten.
Zudem ist der Anteil an Hashtags (#) wesentlich höher als in den Tweets des Präsidenten, was weiter unten noch untermauert wird. Hier zunächst die Darstellung der Zählungen der Tweets mit und ohne Links/Bildern:
```{r}
# Anzahl der Tweets mit und ohne Bild-Link pro Device bestimmen
tweets_pictures <- tweets %>%
filter(!str_detect(text, '^"')) %>%
mutate(picture = ifelse(
str_detect(text, "t.co"),
"Bild-Link",
"nicht Bild-Link")) %>%
count(device, picture)
# bar plot
ggplot(tweets_pictures, aes(device, n, fill = picture)) +
geom_col(position = position_dodge()) +
theme(legend.position = 'bottom') +
labs(title = "Analyse: Tweets mit Bildern pro Device",
x = "",
y = "Anzahl Tweets",
fill = "")
```
### Dig Deeper: Inhalte und Worte
Der Unterschied zwischen den beiden Devices ist hinreichend nachgestellt worden. Nun wird der Inhalt der Tweets betrachtet, um somit einer Aussage näher zu kommen, ob der Präsident einen oder mehrere *Ghosts* für seinen Twitter Account beschäftigt, so lautet schließlich die Hypothese.
Für diese Analyse werden die Tweets im strengen *Tidy*-Format benötigt. Dazu wird wie zuvor beschrieben die Funktion `unnest_token` verwendet. Es erfolgen weitere Schritte:
* Die Bild-Links werden mit `str_replace_all` entfernt (das regex lautet `"https://t.co/[A-Za-z\\d]+|&"`).
* Es werden außerdem mittels des DataFrames `stop_words` aus dem `tidytext` Package Wörter ohne relevante Aussagekraft aus dem Analyse-Dataset entfernt.
* Für `unnest_tokens` wird ein benutzerdefiniertes Muster verwendet: `([^A-Za-z\\d#@']|'(?![A-Za-z\\d#@]))`, welches in der Lage ist Erwähnungen anderer Nutzer (@...) und Hashtag-Nennungen (#...) zu erhalten. Die *default*-Werte der Funktion würden Sonderzeichen wie diese entfernen.
* Vorrangig Ziffern werden mit `str_detect(word, "[a-z]")` herausgefiltert.
```{r}
tweets_tidy <- tweets %>%
filter(!str_detect(text, '^"')) %>%
mutate(text = str_replace_all(text, "https://t.co/[A-Za-z\\d]+|&", "")) %>%
unnest_tokens(word, text, token = "regex", pattern = "([^A-Za-z\\d#@']|'(?![A-Za-z\\d#@]))") %>%
filter(!word %in% stop_words$word,
str_detect(word, "[a-z]"))
tweets_tidy[1:5,]
```
Nun können die häufigsten Wörter ausgegeben werden:
```{r}
tweets_tidy %>%
count(word, sort = T) %>%
top_n(15, n) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
coord_flip() +
ggtitle("Die häufigsten Wörter insgesamt")
```
```{r}
tweets_tidy %>%
count(device, word, sort = T) %>%
group_by(device) %>%
top_n(10, n) %>%
ungroup() %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
coord_flip() +
facet_wrap(~ device, scales = "free_y") +
labs(title = "Die häufigsten Wörter pro Device",
x = "",
y = "Anzahl Wörter")
```
### n-grams
```{r}
tweets %>%
filter(!str_detect(text, '^"')) %>%
mutate(text = str_replace_all(text, "https://t.co/[A-Za-z\\d]+|&", "")) %>%
unnest_tokens(word, text, token = "ngrams", n = 3) %>%
count(device, word, sort = T) %>%
drop_na() %>%
mutate(word = reorder(word,n)) %>%
top_n(13, n) %>%
ggplot(aes(word, n, fill = device)) +
geom_col() +
coord_flip() +
labs(title = "n-grams: 4",
y = "Anzahl Wörter",
x="", fill = "")
```
### Nachweis: Unterschiedliche Tweet-Autoren
Möchte man nun die Verwendung von Worten je Device betrachten, kann man sich das *Quotenverhältnis* zur Hilfe nehmen. Dies ist die bessere Variante als ein schlichter Vergleich der Zählungen, weil wie zuvor beschrieben somit der Überhang der Klassifizierung *'Android'* beachtet wird. David Robinson aus der Data Science Community dient als Referenz für die nachfolgende Statistik. Das Log-Quotenverhältnis (auch *Odd-Ratio*) berechnet sich wie folgt:
$$
\log_2
\left(
\frac
{\frac{\text{# von Android}+1}{\text{Gesamt # Wörter Android}+1}}
{\frac{\text{# von iPhone}+1}{\text{Gesamt # Wörter iPhone}+1}}
\right)
$$
Die Wörter werden für die Analyse auf ein Vorkommen von mindestens fünf Mal auf beiden Devices eingeschränkt. In einigen Fällen bedeutet das, dass weniger Vorkommen von dem Android Device als vom iPhone gezählt werden, jedoch nie weniger als fünf insgesamt. Wenn ein Wort auf dem Android einmalig auftritt, dann fallen die restlichen vier dem iPhone Device zu.
```{r}
tweets_ratios <- tweets_tidy %>%
count(word, device) %>%
group_by(word) %>%
filter(sum(n) >= 5) %>%
spread(device, n, fill = 0) %>%
ungroup() %>%
mutate_if(is.numeric, list(~ (. + 1) / sum(. + 1))) %>%
mutate(oddratio = log2(Android / iPhone)) %>%
arrange(desc(oddratio))
tweets_ratios[1:5,]
```
Im Anschluss können die meist verwendeten Wörter, geordnet nach den *Odd-Ratios*, geplottet werden. Eine explizite Unterscheidung der Device-Klassen ist nun nicht mehr möglich, weil die Raten über die Klassen hinweg erstellt wurden. Jedoch wurde die Berechnung so ausgelegt, dass die Quotenverhältnisse aus Sicht der Klasse *'Android'* definiert sind. Somit können Werte größer als Null dem Android Device und Werte kleiner Null iPhone zugeordnet werden:
```{r}
tweets_ratios %>%
group_by(oddratio > 0) %>%
top_n(15, abs(oddratio)) %>%
ungroup() %>%
mutate(word = reorder(word, oddratio)) %>%
ggplot(aes(word, oddratio, fill = oddratio < 0)) +
geom_bar(stat = "identity") +
coord_flip() +
ylab("Android / iPhone Log-Odd-Ratio") +
theme(legend.position = 'bottom') +
scale_fill_manual(name = "", labels = c("Android", "iPhone"),
values = c("red", "lightblue"))
```
Bis hierhin lassen sich die ersten Erkenntnisse der Explorativen Analyse zusammenfassen:
* Es ist tatsächlich so, dass die meisten Hashtag-Markierungen vom iPhone stammen. Wenn der Präsident Hashtags verwendet, sind das allenfalls Hashtags aus Retweets.
* Das iPhone ist für offizielle Ankündigungen im Stil von Einladungen verantwortlich. Das Erkennt man an Wortfragmenten wie *"7pm"* oder *"join"* (*"Join me in Houston, Texas tomorrow night at 7 pm!"*)
* Emotionalere Tweets stammen vom Präsidenten selbst. Worte wie "poorly" oder "stupid" sind nicht nur Worte, die untypisch für eine Campagne oder eine offizielle Stelle sind, sie sind auch eindeutig dem Android und demnach dem Präsidenten zuzuordnen, wenn man die Hypthese zu unterstützen versucht.
## Sentiment-Analyse
Für den erstem Teil der Sentimentanalyse werden die Ergebnisse aus der vorherigen Analyse mit dem "NRC" Sentiment-Lexikon verknüpft. Dieses Sentiment-Lexikon wird hier mit `get_sentiment_dictionary` aus dem `syuzhet` Package geladen, weil es seit der neusten Version von `tidytext` nicht mehr zur Verfügung steht.
### *NRC*-Lexikon
Im Sentiment-Lexikon *'NRC'* sind zehn Sentimente verfügbar:
```{r}
get_sentiment_dictionary("nrc") %>%
distinct(sentiment)
```
Wovon *'positive'* sowie *'negative'* nicht ausreichend trennscharf sind und daher aus der folgenden Betrachtung ausgeschlossen werden.
Es werden die Sentimente mit den zehn meist verwendeten Wörtern, geordnet nach den absoluten Werten der Quotenverhältnisse, ausgewertet und anschließend visualisiert:
```{r}
nrc <- get_sentiment_dictionary("nrc") %>%
select(word, sentiment)
tweets_sentiments <- tweets_ratios %>%
inner_join(nrc, by = "word") %>%
filter(!sentiment %in% c("positive", "negative")) %>%
mutate(sentiment = reorder(sentiment, -oddratio),
word = reorder(word, -oddratio)) %>%
group_by(sentiment) %>%
top_n(10, abs(oddratio)) %>%
ungroup()
tweets_sentiments[1:5,]
```
```{r fig.height=7, fig.width=8}
ggplot(tweets_sentiments, aes(word, oddratio, fill = oddratio < 0)) +
facet_wrap(~ sentiment, scales = "free", nrow = 2) +
geom_col() +
theme(axis.text.x = element_text(angle = 90, hjust = 1,vjust = 0.3)) +
theme(legend.position = 'bottom') +
labs(x = "", y = "Android / iPhone Odd-Ratio") +
scale_fill_manual(name = "", labels = c("Android", "iPhone"),
values = c("red", "lightblue"))
```
Diese Darstellung bestätigt, dass die Wörter mit negativem Sentiment in den Tweets von *@realDonaldTrump* deutlich häufiger dem Android zuzuordnen sind.
---
Der Tidying-Ansatz von den Analysen war der Referenz von [David Robinson](http://varianceexplained.org/r/trump-tweets/) entnommen. Das `tidytext` package bietet jedoch mit der Option `token = "tweets"` für die `unnest_tokens`-Funktion einen alternativen *Tidying*-Ansatz, der im Folgenden angewendet wird:
```{r}
# 'tweets_tidy_sa' steht für 'Tweets tidy für Sentiment Analyse'
tweets_tidy_sa <- tweets %>%
filter(!str_detect(text, '^"'),
!str_detect(text, '^RT')) %>%
unnest_tokens(output = word, input = text, token = "tweets")
tweets_tidy_sa
```
### *BING*-Lexikon
```{r}
tweets_tidy_sa %>%
filter(device == "Android") %>%
inner_join(get_sentiments("bing"), by = "word") %>%
count(sentiment) %>%
ggplot(aes(sentiment, n, fill = sentiment)) +
geom_col() +
theme(legend.position = 'bottom') +
labs(title = 'Tweets mit "Bing"-Sentimenten, gesamt',
x = "",
y = "Anzahl Wörter",
fill = "")
```
```{r}
tweets_tidy_sa %>%
inner_join(get_sentiments("bing"), by = "word") %>%
count(device, sentiment) %>%
ggplot(aes(device, n, fill = sentiment)) +
geom_col(position = position_dodge()) +
theme(legend.position = 'bottom') +
labs(title = 'Tweets mit "Bing"-Sentimenten, gesamt pro Device',
x = "",
y = "Anzahl Wörter",
fill = "")
```
Laut *"Bing"*-Sentimenten sind die Tweets vom Android Device und damit diejenigen vom Präsidenten selbst wesentlich negativer als die Tweets vom iPhone. Das bezieht sich auf den gesamten Zeitraum.
### Sentiment Time Series
Die Analyse mit *NRC*-Sentimenten zusätzlich mit Satistik untermalt werden: Weil es sich hier um Zählungen handelt, ist ein *Poisson-Test* anwendbar.
```{r}
tweets_tidy_sa %>%
inner_join(get_sentiments("afinn"), by = "word") %>%
mutate(created = floor_date(created, unit = "1 month")) %>%
group_by(created, device) %>%
summarize(score = sum(value)) %>%
ggplot(aes(created, score, fill = device)) +
geom_bar(stat = "identity",
show.legend = FALSE) +
geom_line(stat = "smooth",
linetype = 4,
method = "loess",
se = F,
color = "black",
alpha = 0.3,
size = 1,
show.legend = FALSE) +
facet_wrap(~ device) +
labs(title = "'AFINN'-Sentimente\n1-Monats-Lag-Windows",
x = "",
y = "sum(score) im Lag-Window")
```
Referenz: https://dzone.com/articles/text-analysis-of-trumps-tweets-confirms-he-writes
## Topic Modeling
### Erstellen der Document-Term-Matrix
Zunächst muss aus dem tidy Dataset eine *Document-Term-Matrix* erstellt werden:
```{r}
tweets_dtm <- tweets_tidy_sa %>%
filter(!word %in% stop_words$word,
str_detect(word, "[a-z]")) %>%
count(id, word) %>%
cast_dtm(id, word, n)
tweets_dtm
```
```{r}
as.matrix(tweets_dtm)[1:4,1:6]
```
Wie in den Grundlagen beschrieben, resultiert eine sehr dünn besetzte Matrix.
### Trainieren des LDA-Modells
Daraufhin kann die **Latent Dirichlet allocation** durchgeführt werden. Die Methode wird zunächst näher spezifiziert, was im Default "quick approximation", also *Schnelle Schätzung* resultiert.
```{r}
tweets_lda <- LDA(tweets_dtm, k = 2, control = list(seed = 1234))
tweets_lda
```
Die Auftrittswahrscheinlichkeiten $\beta$ pro Wort können dann wie bereits beschrieben mit der `tidy`-Funktion extrahiert werden:
```{r}
tweets_topics <- tidy(tweets_lda, matrix = "beta")
tweets_topics
```
Für die Interpretation werden die Ergebnisse weiterverarbeitet. Es werden die Wörter mit den $n$ = 7 höchsten Auftrittswahrscheinlichkeiten $\beta$ ausgewählt.
```{r}
tweets_topics_top_terms <- tweets_topics %>%
group_by(topic) %>%
top_n(7, beta) %>%
ungroup() %>%
arrange(topic, -beta)
tweets_topics_top_terms
```
```{r}
tweets_topics_top_terms %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = as.factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip()
```
Die zwei Themen, die durch *LDA* erfasst wurden, unterscheiden sich zwar, liefern aber keine nennenswerten Erkenntnisse. Das Verfahren wird mit der *"Gibbs"* Methode wiederholt:
```{r}
tweets_lda <- LDA(tweets_dtm, k = 2, method = "Gibbs", control = list(seed = 1234))
tweets_topics <- tidy(tweets_lda, matrix = "beta")
tweets_topics_top_terms <- tweets_topics %>%
group_by(topic) %>%
top_n(7, beta) %>%
ungroup() %>%
arrange(topic, -beta)
tweets_topics_top_terms %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = as.factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip()
```
Nun spiegelt das Ergebnis sinngemäß den Unterschied zwischen den Devices wider. Thema 1 könnte hier nach "Android", Thema 2 nach "iPhone" benannt werden.
Die Methode wird wiederum wiederholt. Dieses Mal mit $n$ = 6 Themen.
```{r}
tweets_lda <- LDA(tweets_dtm, k = 6, method = "Gibbs", control = list(seed = 1234))
tweets_topics <- tidy(tweets_lda, matrix = "beta")
tweets_topics_top_terms <- tweets_topics %>%
group_by(topic) %>%
top_n(7, beta) %>%
ungroup() %>%
arrange(topic, -beta)
tweets_topics_top_terms %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = as.factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free", nrow = 3) +
coord_flip() +
labs(title = "Latent Dirchlet Allocation, k = 6",
x = "")
```
Die Themen sind ausreichend unterschiedlich zueinander und zur Interpretation geeignet. Ihnen nun eine Bedeutung zuzuordnen ist ein subjektiver Prozess. An dieser Stelle wird auch schnell deutlich, dass für das Interpretieren das entsprechende Hintergrundwissen bzw. *Domain-Knowledge* benötigt wird.
Die Themen werden vom Autor dieser Ausarbeitung wie folgend festgelegt. Die Themen sind nicht als
Bei Thema sechs fällt jedoch der Bergiff bzw. *Term* "amp" auf.
```{r}
tweets %>%
filter(str_detect(text, "amp")) %>%
.$text %>%
head()
```
## Weiterführende Aufgaben
Es scheint so, dass "amp" zu einem HTML-Fragment gehört. Beim Tidying-Prozess wurde `&` zu "amp" geändert, was letztlich als ein "&" übersetzt werden kann. Die vorhergehende Analyse kann also weiter optimiert werden, indem das zugrunde liegende Tidy-DataFrame angepasst wird. Die korrekte Stelle dafür wäre ein negierter Filter auf die *Term-Matrix* (z.B. ).
# Zusammenfassung der Ergebnisse
Die zuvor schon von anderen Usern der Data Science Community (und vor allem durch Todd Vaziri) aufgestellte Hypothese, US Präsident Donald Trump sei nicht alleiniger Autor des Twitter Accounts *@realDonaldTrump* konnte bestätigt werden. Die Tweets des Präsidenten können im Zeitraum vom Februar 2013 bis März 2017 anhand des Devices in Cluster eingeteilt werden. Diese Erkenntnis ist nicht neu, jedoch für die Glaubwürdigkeit der nachfolgenden Ergebnisse wichtig, denn sie werden somit untermauert.
Bei der Sentimentanalyse fiel schnell auf, dass die einzelnen Methoden nicht direkt trennscharf sind. Die Interpretation, die selbstredend den wesentlichen Teil der Ergebnisse ausmacht, ist letztlich ein subjektiver Vorgang. Der Autor schließt sich aufgrund der Ergebnisse aus den Analysemethoden der Meinung vieler anderer aus der Data Science Community an:
Die wesentlich negativeren Tweets werden von einem Android Device geschrieben. Donald Jr. Trump nutzte [laut Bericht des Verge](https://www.theverge.com/2015/10/5/9453935/donald-trump-twitter-strategy) ein solches Device im Untersuchungszeitraum. Daher ist anzunehmen, dass Trump selbst derjenige ist, der die Polarität der Posts in die negative Richtung vorantreibt. Es wurde zusätzlich klar, dass für die Sentimentanalyse ein gewissen Domänenwissen erforderlich ist. Einige Begriffe können mitunter nur dann korrekt eingeordnet werden, wenn man um die Umstände der US Politik gewiss ist. Letztlich bleibt die Sentimentanalyse stark abhängig von manuellen Prozessen. Viele Daten müssen zunächst per Grafiken analysiert werden. Wenn gewisse Inhalte Aufmerksamkeit erregen, können diese dann durch wiederum andere Grafiken aus einem anderen Blickwinkel betrachtet werden (bspw. hinzunehmen oder auch reduzieren von Dimensionen). Einzelne Störfaktoren fallen gar erst mit fortschreitenden Analysen ins Gewicht. Ein Beispiel dafür ist der führende Term "*RT*" bei den Tweet von *@realDonaldTrump*. Selbst wenn diese von einem Android Device geschickt wurden, ist davon auszugehen, dass sie nicht repräsentativ für Trumps Schriftbild oder Sentiment sind, denn *RT* steht für *Retweet* und ist somit wahrscheinlich der Wortlaut anderer Twitter Nutzer. Bereinigt man die die das Dataset von diesen Tweets noch vor dem *Tokenizing*, liefern die Analysen gänzlich andere Ergebnisse.
Für das Topic Modeling war eines der Ziele, eine geeignete Methode zu finden, die Themen in der Praxis errechnen automatisiert kann. Mit LDA wurde ein Ansatz aus dem Bereich des Maschinellen Lernens herausgesucht und angewendet. Die Ergebnisse können als sinngebend betrachtet werden. Es können den einzelnen Clustern tatsächlich Themenüberschriften, bzw. Beschreibungen zugeordnet werden, sie sind jedoch relativ subjektiv formuliert und zugeordnet. Das dafür benötigte Domänenwissen ist höher als bei der Sentimentanalyse einzustufen. Für die Analyse wurde zunächst Topic Modeling mit $k=2$ und anschließen $k=6$ durchgeführt. Für weitere Analysen sind andere Einstellungen für den Tuning-Parameter $k$ denkbar.
**Ausblick:** Die Analysen können also noch weiterverfolgt werden. In Hinsicht auf die hohe Anzahl der Tweets und dem relativ hohen Zeitraum, ist es durchaus möglich, dass eine wesentlich höherer Parameter für $k$ noch sinnvolle Ergebnisse liefern kann.
# Appendix
Alternative Einstellung der Topic Modelling untersuchung.
```{r fig.height=14, fig.width=8}
tweets_dtm <- tweets_tidy_sa %>%
filter(!word %in% c(stop_words$word,"amp"),
str_detect(word, "[a-z]")) %>%
count(id, word) %>%
cast_dtm(id, word, n)
tweets_lda <- LDA(tweets_dtm, k = 20, method = "Gibbs", control = list(seed = 1234))
tweets_topics <- tidy(tweets_lda, matrix = "beta")
tweets_topics_top_terms <- tweets_topics %>%
group_by(topic) %>%
top_n(7, beta) %>%
ungroup() %>%
arrange(topic, -beta)
tweets_topics_top_terms %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = as.factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free", ncol = 2) +
coord_flip()
```