Newer
Older
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
---
title: "Textdaten bearbeiten mit **stringr**"
author: "B. Philipp Kleer"
date: "11. Oktober 2021"
output:
html_document:
toc: true
toc_float:
toc_collapsed: true
smooth_scroll: true
toc_depth: 3
widescreen: true
highlight: pygments
theme: readable
css: styles/style.css
df_print: paged
mathjax: default
self_contained: false
incremental: false #True dann jedes Bullet einzeln
collapse: true # means the text output will be merged into the R source code block
includes:
after_body: ./styles/footer.html
before_body: ./styles/header.html
---
```{r setup, include=FALSE}
library("knitr")
library("rmarkdown")
library("tidyverse")
gbbo <- readRDS("../datasets/gbbo.rds")
opts_chunk$set(#fig.path = 'pics/s6-', # path for calculated figures
fig.align = 'center', # alignment of figure (also possible right, left, default)
fig.show = 'hold', # how to show figures: hold -> direct at the end of code chunk; animate: all plots in an animation
fig.width = 6, # figure width
fig.height = 6, # figure height
echo = TRUE, # Code is printed
eval = FALSE, # Code is NOT evaluated
warning = FALSE, # warnings are NOT displayed
message = FALSE, # messages are NOT displayed
size = "tiny", # latex-size of code chunks
background = "#E7E7E7", # background color of code chunks
comment = "", # no hashtags before output
options(width = 80),
results = "markdown",
rows.print = 15
)
htmltools::tagList(
xaringanExtra::use_clipboard(
button_text = "<i class=\"fa fa-clipboard\"></i>",
success_text = "<i class=\"fa fa-check\" style=\"color: #90BE6D\"></i>",
error_text = "<i class=\"fa fa-times-circle\" style=\"color: #F94144\"></i>"
),
rmarkdown::html_dependency_font_awesome()
)
```
## Starten wir!
:::: {class="flex-container"}
::: {class="flex-text"}
In diesen Slides wollen wir uns mit Textdaten beschäftigen. Hieru lernen wir grundlegende Funktionen im Umgang mit Textdaten kennen. Eins vorweg: Textdaten sind immer schwieriger zu händeln als numerische Daten. Der Umgang in R ist zwar ganz passabel, aber optimal ist anders.
In R gibt es dazu ein gutes Paket innerhalb des **tidyverse** - **stringr**. Gemeinsam mit *regular expressions* können viele Transformationen durchgeführt werden.
Es bedarf aber einer erhöhten Lernbereitschaft (im Vergleich zu anderen Paketen und Funktionen).
:::
::: {class="flex-picture"}
{width="300px"}
:::
::::
## Wie bekommen wir Textdaten?
Als Textdaten fallen einen natürlich sofort Tweets ein, oder für die Offline-Leute z.B. Wahlprogramme. Im Kurs **Quantitative Text- und Inhaltsanalyse** wird die Erhebung dieser Daten über die sogenannte **API** (*Application Programming Interferenc*) beigebracht. Wir überspringen jetzt diesen Schritt, da wir uns erstmal nur mit den grundlegenden Funktionen der Bearbeitung von Textvariablen in R beschäftigen wollen.
Dafür habe ich die letzten Tweets der hervorragenden Sendung [The Great British Bake Off](https://thegreatbritishbakeoff.co.uk) über die Twitter-API geladen (Stand: 22. September 2021). Wir werden im nachfolgenden verschiedene Schritte durchgehen, wie wir Textdaten mit den Funktionen des Pakets **stringr** bearbeiten können.
## Package und Daten laden
Zuerst laden wir wieder **tidyverse** bzw. installieren es, wenn es noch nicht installiert ist:
``` {r install-tidy}
# install.packages("tidyverse")
library("tidyverse")
# alternativ:
# install.packages("stringr")
# library("stringr")
```
Anschließend laden wir den Datensatz ```gbbo``` ins *environment*.
``` {r uni-load}
gbbo <- readRDS("../datasets/gbbo.rds") # oder eigenen Pfad!
```
Schauen wir uns nun den Datensatz mal an:
``` {r data-inspect, eval = TRUE}
gbbo
```
Der Datensatz umfasst in Gänze 90 Variablen, für uns ist in diesem Fall aber nur eine einzige Variable erstmal relevant - nämlich **text**. In **text** befindet sich der Text des jeweiligen Tweets. Und wir möchten ja nun Text bearbeiten mit **stringr**.
Der einfachheithalber speichern wir diese eine Variable als einzelnen Vektor, schaffen eine neue ID-Variable und erstellen den Datensatz ```tweet```.
```{r vektor, eval=TRUE}
tweet <- gbbo %>%
select(text)
tweet$id <- seq(1, 862, 1)
head(tweet,
n = 100)
```
## Basisfunktionen in **stringr**
Die Basisfunktionen umfassen ...
- ```str_length()```
- ```str_sub()```
- ```str_dup()```
- ```str_pad()```
- ```str_trunc()```
- ```str_trim()```
- ```str_to_upper()``` / ```str_to_lower()``` / ```str_to_title()```
- ```str_order``` / ```str_sort()```
- ```str_detect()```
- ```str_subset()```
- ```str_count()```
- ```str_locate()```
- ```str_extract()```
- ```str_match()```
- ```str_replace()```
- ```str_split()```
## ```str_length()```
Die Funktion ist ähnlich zur Funktion ```nchar()``` aus ```baseR```. Sie gibt die Länge der jeweiligen Zeichenkette an.
```{r length, eval=TRUE}
str_length(tweet$text)
# oder für einen spezifischen
tweet %>%
filter(id == 275) %>%
select(text) %>%
str_length()
```
Hiermit sehen wir für jede Position die Länge des Tweets. Wir können auch einzelne Positionen aufrufen (also einzelne Tweets).
```{r length-single, eval=TRUE}
# hier der 23. tweet
str_length(tweet$text[23])
```
## ```str_sub()```
Wir können uns auch einzelne Zeichen aus einem String ausgeben lassen. So z.B. das zweite bis fünfte Zeichen aus dem Tweet an Stelle 23.
```{r sub, eval=TRUE}
# hier der 23. tweet
str_sub(tweet$text[23],
2,
5)
tweet %>%
filter(id == 23) %>%
select(text) %>%
str_sub(2, 5)
```
Natürlich kann man sich auch einzelne Buchstaben ausgeben lassen, dazu setzt man den Anfang (2. Argument) und das Ende der Extraktion (3. Argument) gleich. Will man von hinten zählen nimmt man einfach ein ```-``` zur Hilfe.
```{r sub2, eval=TRUE}
# hier der 23. tweet
str_sub(tweet$text[23],
25,
25)
str_sub(tweet$text[23],
-20,
-2)
```
**Warum ist die erste Ausgabe leer?**
Mit dieser Funktion kann man auch einzelne Zeichen innerhalb einer Funktion ändern:
```{r sub3, eval=TRUE}
tweet$text[23]
str_sub(tweet$text[23], 26, 26) <- "M"
tweet[23]
```
## ```str_dup()```
Anstatt Zeichen zu löschen oder zu ersetzen, können wir Zeichen auch wiederholen. Wiederholen wir einfach den Text von Tweet 23 viermal.
```{r dup, eval=TRUE}
str_dup(tweet$text[23], 4)
```
Man kann das ganze auch verketten und wieder direkt die *Pipes* nehmen, die wir am Vormittag kennengelernt haben.
```{r dup, eval=TRUE}
tweet %>%
filter(id == 23) %>%
select(text) %>%
str_sub(1, 8) %>%
str_dup(4)
```
**Was wird hier wiederholt?**
## ```str_pad()```
Oft ist es von Vorteil, wenn Zeichenketten dieselbe Länge aufweisen, z.B. wenn man sie gleichmäßig grafisch darstellen möchte. Dies können wir mit ```str_pad()``` machen: Die Zeichenkette soll 100 Zeichen lang werden.
``` {r pad, eval=TRUE}
text <- tweet$text[c(23, 731)]
text
```
Wir haben jetzt also einen einzelnen Vektor, der zwei Tweets beinhaltet. Jetzt wollen wir die Länge auf 100 festsetzen:
```{r pad2, eval=TRUE}
str_pad(text,
100,
side = "both")
```
**Was passiert hier nun beim zweiten Tweet?**
Da passiert nichts, da der Tweet länger als 100 Zeichen ist und die Funktion ```str_pad()``` kürzt nicht automatisch.
Will man es doch mit *Pipes* machen gibt es einen Umweg über ```mutate()```, denn die ```str_..()```-Funktionen haben überwiegend kein Datenargument.
```{r pad-pipe, eval=TRUE}
tweet <- tweet %>%
mutate(shortT = str_pad(text,
100,
side = "both"))
head(tweet$shortT,
n = 50)
```
Dies empfiehlt sich eigentlich sogar, da man so immer die originalen Daten behält.
## ```str_trunc()```
Um gleichzeitig die Tweets zu kürzen, benutzt man die Funktion ```str_trunc()```. Hiermit kann die maximale Zeichenkettenzahl gesetzt werden. So könnten wir zum Beispiel sagen, uns interessiert nur der Beginn der Tweets, also die ersten 50 Zeichen.
``` {r trunc, eval=TRUE}
tweet <- tweet %>%
mutate(truncT = str_trunc(text, 50),
truncT = str_pad(truncT,
60,
side = "both"))
head(tweet$truncT,
n = 50)
```
## ```str_trim()```
Um Leerzeichen zu entfernen, nutzt man ```str_trim()```. Wir entfernen jetzt wieder die Zeichen:
``` {r trim, eval=TRUE}
tweet <- tweet %>%
mutate(truncT2 = str_trim(truncT,
side = "both"))
head(tweet$truncT2,
n = 50)
```
## ```str_to_upper()``` / ```str_to_lower()``` / ```str_to_title()```
Groß- und Kleinschreibung spielt gerade bei der quantitativen Analyse von Texten keine Rolle. Daher ändert man in der Regel alle Zeichen auf Großbuchstaben (```str_to_upper()```) oder auf Kleinbuchstaben (```str_to_lower```). Teste gleich anschließend selbst aus, was die Funktion ```str_to_title()``` macht!
``` {r schreibweise, eval=TRUE}
tweet <- tweet %>%
mutate(truncT2 = str_to_lower(truncT2))
head(tweet$truncT2,
n = 50)
```
## ```str_order``` / ```str_sort()```
Mit diesen zwei Funktionen können die einzelnen Zeichenketten innerhalb eines Vektors geordnet werden. Die Standardeinstellung ist dabei Englisch. Mit ```str_order()``` erhält man die Reihungszahl des Tweets. Mit ```str_sort()``` werden die Tweets neu geordnet (was die ID-Variable zerstört!).
```{r order, eval=TRUE}
tweet <- tweet %>%
mutate(orderedText = str_order(truncT2,
locale = "en")) # für D z.B. de
```
## Regular Expression
Mit **regular expression** (*regex*) können Zeichenkette bestimmt werden, die beliebig eingesetzt werden können.
Die folgenden Funktionen werden oftmals in Kombination mit *regex* eingesetzt, um String-Variablen zu bearbeiten:
- ```str_detect()```
- ```str_subset()```
- ```str_count()```
- ```str_locate()```
- ```str_extract()```
- ```str_match()```
- ```str_replace()```
- ```str_split()```
## ```str_detect()```
Machen wir der einfachheithalber nochmal ein Subsample, dass nur 50 Tweets erhält.
```{r subsample, eval=TRUE}
tweet2 <- tweet %>%
filter(id < 51) %>%
select(id, text)
```
In diesem möchten wir nun die Tweets finden, die das Wort *thanks* beinhalten. Die Funktion gibt einen **Boolean**-Wert zurück, also ```TRUE``` oder ```FALSE```.
```{r detect, eval=TRUE}
tweet2 <- tweet2 %>%
mutate(includesThanks = str_detect(text, "thanks"))
table(tweet2$includesThanks)
```
Kein Tweet beinhaltet also *thanks*, aber was ist mit Tweet 7?
```{r detect2, eval=TRUE}
tweet2$text[7]
```
**Wo könnte das Problem liegen?**
Das Problem liegt im großen ```T```, da wir in der Anweisung nicht berücksichtigt haben, dass es uns egal ist, ob es genau die Reihenfolge ist oder ob Groß-/Kleinschreibung unberücksichtigt bleiben soll. Dazu benötigen wir die Hilfsfunktion ```regex()```, bei der wir im Argument ```ignore_case = TRUE``` angeben, dass es egal ist, wie die Buchstaben geschrieben sind, sondern nur die Reihenfolge vorhanden sein muss
```{r detect3, eval=TRUE}
tweet2 <- tweet2 %>%
mutate(includeThanks = str_detect(text,
regex("thanks",
ignore_case = TRUE)))
table(tweet2$includeThanks)
```