/
quickstart_es.Rmd
416 lines (312 loc) · 19.1 KB
/
quickstart_es.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
---
title: "Guía de Inicio Rápido"
output:
html_document:
toc: true
---
```{r, echo = FALSE}
knitr::opts_chunk$set(collapse = FALSE,
comment = "##")
```
# Instalando el paquete
Desde que **quanteda** está disponible en [CRAN](https://CRAN.R-project.org/package=quanteda), lo puedes instalar usando tu instalador de paquetes en R GUI's o ejecutar las siguientes líneas:
```{r, eval = FALSE}
install.packages("quanteda")
```
Ver instrucciones (en inglés) en https://github.com/quanteda/quanteda para instalar la versión de GitHub.
## Paquetes adicionales recomendados:
Los siguientes paquetes funcionan bien con con **quanteda** o lo complementan y por eso recomendamos que también los instaleis:
* [**readtext**](https://github.com/quanteda/readtext): una manera sencilla de leer data de texto casi con cualquier formato con R,.
* [**spacyr**](https://github.com/kbenoit/spacyr): NLP usando la librería [spaCy](https://spacy.io), incluyendo etiquetado part-of-speech, entity recognition y dependency parsing.
* [**quanteda.corpora**](https://github.com/quanteda/quanteda.corpora): data textual adicional para uso con **quanteda**.
```{r, eval = FALSE}
devtools::install_github("quanteda/quanteda.corpora")
```
* [**LIWCalike**](https://github.com/kbenoit/quanteda.dictionaries): una implementación en R del abordaje de análisis de texto [Linguistic Inquiry and Word Count](http://liwc.wpengine.com).
```{r, eval = FALSE}
devtools::install_github("kbenoit/quanteda.dictionaries")
```
# Creando un corpus
Cargas el paquete para acceder a funciones y data en el paquete.
```{r, message = FALSE}
library(quanteda)
```
## Fuentes disponibles de corpus
**quanteda** tiene un simple y poderoso paquete adicional para cargar textos: [**readtext**](https://github.com/kbenoit/readtext). La función principal en este paquete, `readtext()`, toma un archivo o set de archivos de un disco o una dirección de URL y devuelve un tipo de data.frame que puede ser usado directamente con la función de construcción de corpus (`corpus()`) para crear un objeto corpus en **quanteda**.
`readtext()` funciona con:
* archivos de texto (`.txt`);
* archivos de valores separados por comas (`.csv`);
* data en formato XML;
* data del API de Facebook API, en formato JSON;
* data de la API de Twitter, en formato JSON; y
* data en formato JSON en general.
El comando constructor de corpus llamado `corpus()` funciona directamente sobre:
* un vector de objetos de tipo character, por ejemplo aquellos que ya has cargado al workspace usando otras herramientas;
* un objeto corpus `VCorpus` del paquete **tm**.
* un data.frame que contenga una columna de texto y cualquier otro documento de metadata.
### Construyendo un corpus de un vector de tipo character
El caso más simple sería crear un corpus de un vector de textos que ya estén en la memoria en R. De esta manera, el usuario avanzado de R obtiene completa flexibilidad con su elección de textos dado que hay virtualmente infinitas posibilidades de obtener un vector de textos en R.
Si ya se disponen de textos en este formato es posible llamar a la función de constructor de corpus directamente. Es posible demostrarlo en el objeto de tipo character integrado de los textos sobre políticas de inmigración extraídos de los manifiestos de partidos políticos compitiendo en la elección del Reino Unido en 2010 (llamado `data_char_ukimmig2010`).
```{r}
corp_uk <- corpus(data_char_ukimmig2010) # build a new corpus from the texts
summary(corp_uk)
```
Si quisiéramos, también podríamos incorporar también a este corpus algunas variables a nivel documento -- lo que quanteda llama *docvars*.
Esto lo hacemos utilizando la función de R llamada `names()` para obtener los nombres del vector de tipo character de `data_char_ukimmig2010` y asignárselos a una variable de documento (`docvar`).
```{r}
docvars(corp_uk, "Party") <- names(data_char_ukimmig2010)
docvars(corp_uk, "Year") <- 2010
summary(corp_uk)
```
### Cargando archivos usando el paquete readtext
```{r, eval=FALSE}
require(readtext)
# Twitter json
dat_json <- readtext("~/Dropbox/QUANTESS/social media/zombies/tweets.json")
corp_twitter <- corpus(dat_json)
summary(corp_twitter, 5)
# generic json - needs a textfield specifier
dat_sotu <- readtext("~/Dropbox/QUANTESS/Manuscripts/collocations/Corpora/sotu/sotu.json",
textfield = "text")
summary(corpus(dat_sotu), 5)
# text file
dat_txtone <- readtext("~/Dropbox/QUANTESS/corpora/project_gutenberg/pg2701.txt", cache = FALSE)
summary(corpus(dat_txtone), 5)
# multiple text files
dat_txtmultiple1 <- readtext("~/Dropbox/QUANTESS/corpora/inaugural/*.txt", cache = FALSE)
summary(corpus(dat_txtmultiple1), 5)
# multiple text files with docvars from filenames
dat_txtmultiple2 <- readtext("~/Dropbox/QUANTESS/corpora/inaugural/*.txt",
docvarsfrom = "filenames", sep = "-",
docvarnames = c("Year", "President"))
summary(corpus(dat_txtmultiple2), 5)
# XML data
dat_xml <- readtext("~/Dropbox/QUANTESS/quanteda_working_files/xmlData/plant_catalog.xml",
textfield = "COMMON")
summary(corpus(dat_xml), 5)
# csv file
write.csv(data.frame(inaug_speech = texts(data_corpus_inaugural),
docvars(data_corpus_inaugural)),
file = "/tmp/inaug_texts.csv", row.names = FALSE)
dat_csv <- readtext("/tmp/inaug_texts.csv", textfield = "inaug_speech")
summary(corpus(dat_csv), 5)
```
## Cómo funciona un corpus de quanteda
### Principios del Corpus
Un corpus está diseñado para ser una "librería" original de documentos que han sido convertidos a formato plano, texto codificado en UTF-8, y guardado junto con meta-data en a nivel de corpus y a nivel de documento. Tenemos un nombre especial para meta-data a nivel de documento: *docvars*. Estas son variables o características que describen atributos de cada documento.
Un corpus está diseñado para ser un contenedor de textos más o menos estático en lo que respecta a su procesamiento y análisis. Esto significa que los textos en el corpus no están disenado para ser cambiados internamente a través de (por ejemplo) limpieza o preprocesamiento, como stemming o removiendo la puntuación. Más que nada, los textos pueden ser extraídos del corpus como parte del procesamiento y asignados a objetos nuevos, pero la idea es que los corpus se conserven como una copia de referencia original para que otros análisis, por ejemplo aquellos en que stems y puntuación son necesarios, como analizar un índice, pueden ser realizados sobre el mismo corpus.
Para extraer texto de un corpus, es posible utilizar el extractor llamado `texts()`.
```{r}
texts(data_corpus_inaugural)[2]
```
Para obtener la data resumida de textos de un corpus, se puede llamar al método `summary()` definido para un corpus.
```{r}
data(data_corpus_irishbudget2010, package = "quanteda.textmodels")
summary(data_corpus_irishbudget2010)
```
Se puede guardar el output del comando summary como un data frame y graficar algunos estadísticos descriptivos con esta información:
```{r, fig.width = 8}
tokeninfo <- summary(data_corpus_inaugural)
if (require(ggplot2))
ggplot(data = tokeninfo, aes(x = Year, y = Tokens, group = 1)) +
geom_line() +
geom_point() +
scale_x_continuous(labels = c(seq(1789, 2017, 12)), breaks = seq(1789, 2017, 12)) +
theme_bw()
# El discurso inaugural más largo: William Henry Harrison
tokeninfo[which.max(tokeninfo$Tokens), ]
```
## Herramientas para manejar objetos de corpus
### Juntando dos objetos de corpus
El operador `+` provee un método simple para concatenar dos objetos corpus. Si contenían diferentes sets de variables a nivel documento las unirá de manera que no se pierda nada de información. La meta-data a nivel corpus también queda concatenada.
```{r}
corp1 <- corpus(data_corpus_inaugural[1:5])
corp2 <- corpus(data_corpus_inaugural[53:58])
corp3 <- corp1 + corp2
summary(corp3)
```
### Armando subsets dentro de objetos corpus
Hay un método de la función `corpus_subset()` definida por objetos corpus, donde un nuevo corpus puede ser extraído en base a condiciones lógicas aplicadas a docvars:
```{r}
summary(corpus_subset(data_corpus_inaugural, Year > 1990))
summary(corpus_subset(data_corpus_inaugural, President == "Adams"))
```
## Explorando textos de corpus
La función `kwic` (keywords-in-context) realiza una búsqueda de una palabra y permite visualizar los contextos en los que aparece:
```{r, tidy=TRUE}
kwic(data_corpus_inaugural, pattern = "terror")
```
```{r}
kwic(data_corpus_inaugural, pattern = "terror", valuetype = "regex")
```
```{r}
kwic(data_corpus_inaugural, pattern = "communist*")
```
En el summary de arriba, las variables `Year` (año) y `President` (presidente) son variables asociadas a cada documento. Es posible acceder a dichas variables con la función 'docvars()'
```{r}
# inspect the document-level variables
head(docvars(data_corpus_inaugural))
```
Más corpora están disponibles en el repositorio [quanteda.corpora](https://github.com/quanteda/quanteda.corpora).
# Extrayendo atributos de un corpus
Para realizar análisis estadísticos tales como document scaling, es necesario extraer una matriz asociando valores de ciertos atributos con cada documento. En quanteda, se utiliza la función 'dfm' para producir dicha matriz. 'dfm', por sus siglas en inglés *document-feature matrix* o matriz documento-atributo en castellano, siempre se refiere a los documentos como filas y a los atributos como columnas. Se determinó esta orientación de las dimensiones dado que es estándar en el campo de análisis de datos que las unidades de análisis se computen en las filas y los atributos o variables se computen en las columnas. Se denominan "atributos" en vez de términos porque los atributos son más generales que los términos: pueden ser definidos como términos crudos, términos stemmed, términos de partes de discurso, términos luego de la remoción de las stopwords o una clase de diccionario al que pertenece un término. Los atributos pueden ser enteramente generales, como ngrams o dependencias sintácticas y dejamos esto abierto.
## Convirtiendo textos en tokens
Para convertir un texto en tokens de manera simple, quanteda provee un poderoso comando denominado`tokens()`. Produce un objeto intermedio que consiste en una lista de tokens en forma de vectores de caracteres, donde cada elemento de la lista corresponde con un documento de input.
El comando `tokens()` es deliberadamente conservador, es decir, que no remueve nada del texto excepto que se le especifique explícitamente que lo haga.
```{r}
txt <- c(text1 = "This is $10 in 999 different ways,\n up and down; left and right!",
text2 = "@kenbenoit working: on #quanteda 2day\t4ever, http://textasdata.com?page=123.")
tokens(txt)
tokens(txt, remove_numbers = TRUE, remove_punct = TRUE)
tokens(txt, remove_numbers = FALSE, remove_punct = TRUE)
tokens(txt, remove_numbers = TRUE, remove_punct = FALSE)
tokens(txt, remove_numbers = FALSE, remove_punct = FALSE)
tokens(txt, remove_numbers = FALSE, remove_punct = FALSE, remove_separators = FALSE)
```
También existe la opción de convertir en token los caracteres:
```{r}
tokens("Great website: http://textasdata.com?page=123.", what = "character")
tokens("Great website: http://textasdata.com?page=123.", what = "character",
remove_separators = FALSE)
```
y las oraciones:
```{r}
# sentence level
tokens(c("Kurt Vongeut said; only assholes use semi-colons.",
"Today is Thursday in Canberra: It is yesterday in London.",
"En el caso de que no puedas ir con ellos, ¿quieres ir con nosotros?"),
what = "sentence")
```
## Construyendo una matriz de documentos y atributos
Convertir los textos en tokens es una opción intermedia y la mayoría de los usuarios querrán directamente construir la matriz de documentos y atributos. Para hacer esto existe la función de navaja suiza llamada `dfm()`, que realiza la tokenización y tabula los atributos extraídos dentro de una matriz de documentos por atributos. A diferencia del enfoque conservador de `tokens()`, la función `dfm()` aplica ciertas opciones por default, como `tolower()` -- una función separada para transformar textos a minúsculas -- y remueve puntuación. De todos modos, todas las opciones de `tokens()` se pueden pasar a `dfm()`.
```{r}
corp_inaug_post1990 <- corpus_subset(data_corpus_inaugural, Year > 1990)
# make a dfm
dfmat_inaug_post1990 <- tokens(corp_inaug_post1990) %>%
dfm()
dfmat_inaug_post1990[, 1:5]
```
Otras opciones para incluyen remover las stopwords y realizar stemming de los tokens.
```{r}
# make a dfm, removing stopwords and applying stemming
dfmat_inaug_post1990 <- dfm(dfmat_inaug_post1990,
remove = stopwords("english"),
stem = TRUE, remove_punct = TRUE)
dfmat_inaug_post1990[, 1:5]
```
La opción 'remove' provee una lista de tokens a ser ignorados. La mayoría de los usuarios proveerán una lista de 'stop words' predefinidas para varios idiomas, accediendo a través de la función `stopwords()`:
```{r}
head(stopwords("en"), 20)
head(stopwords("ru"), 10)
head(stopwords("ar", source = "misc"), 10)
```
### Visualizando la matriz de documentos y atributos
El dfm puede ser inspeccionado en el panel de 'Environment' en Rstudio o llamando la función `View` en R. Llamando la función `plot` en un dfm se presentará una nube de palabras usando el paquete [wordcloud package](https://cran.r-project.org/web/packages/wordcloud)
```{r warning=FALSE, fig.width = 8, fig.height = 8}
dfmat_uk <- dfm(data_char_ukimmig2010, remove = stopwords("english"), remove_punct = TRUE)
dfmat_uk
```
Para acceder a la lista de los atributos más frecuentes es posible utilizar `topfeatures()`:
```{r}
topfeatures(dfmat_uk, 20) # 20 most frequent words
```
Para un objeto `dfm` se puede graficar una nube de palabras usando `textplot_wordcloud()`. Esta función pasa argumentos a `wordcloud()` del paquete **wordcloud** y puede embellecer el gráfico usando los mismos argumentos:
```{r warning=FALSE, fig.width = 7, fig.height = 7}
set.seed(100)
library("quanteda.textplots")
textplot_wordcloud(dfmat_uk, min_freq = 6, random_order = FALSE,
rotation = .25,
colors = RColorBrewer::brewer.pal(8, "Dark2"))
```
### Agrupando documentos por variable
Frecuentemente estamos interesados en analizar cómo textos difieren según factores sustantivos que pueden estar codificados en las variables de documento en vez de simplemente por los límites de los archivos. En esos casos es posible agrupar los documentos que comparten el mismo valor por variable de documento cuando se crea un dfm:
```{r}
dfmat_ire <- tokens(data_corpus_irishbudget2010, remove_punct = TRUE) %>%
tokens_remove(stopwords("en")) %>%
tokens_group(groups = party) %>%
dfm()
```
We can sort this dfm, and inspect it:
```{r}
dfm_sort(dfmat_ire)[, 1:10]
```
### Agrupando palabras por diccionario o clase de equivalencia
Para algunas aplicaciones se tiene conocimiento previo del conjunto de palabras que son indicativas de rasgos que quisiéramos medir. Por ejemplo, una lista general de palabras positivas puede indicar sentimiento positivo en un reseña de una película tal tenemos un diccionario de términos políticos asociados a una tendencia ideológica en particular. En estos casos, a veces es útil tratar estos grupos de palabras como equivalentes para los propósitos del análisis y sumar las veces en que se utiliza agregándolas por clase.
Por ejemplo, observemos cómo palabras asociadas al terrorismo y palabras asociadas con la economía varían por presidente en el corpus de discursos inaugurales de presidentes de Estados Unidos. Del corpus original seleccionamos los presidentes desde Clinton:
```{r}
corp_inaug_post1991 <- corpus_subset(data_corpus_inaugural, Year > 1991)
```
Ahora definimos un diccionario de muestra:
```{r}
dict <- dictionary(list(terror = c("terrorism", "terrorists", "threat"),
economy = c("jobs", "business", "grow", "work")))
```
Se puede usar el diccionario cuando creamos el dfm:
```{r}
dfmat_inaug_post1991_dict <- dfm(corp_inaug_post1991, dictionary = dict)
dfmat_inaug_post1991_dict
```
El constructor de la función `dictionary()` también funciona con el formato de dos diccionarios externos comunes: los formatos LIWC y Provalis Research's Wordstat. Por ejemplo, es posible cargar el LIWC y aplicarlo al corpus de discursos inaugurales de presidentes:
```{r, eval = FALSE}
dictliwc <- dictionary(file = "~/Dropbox/QUANTESS/dictionaries/LIWC/LIWC2001_English.dic",
format = "LIWC")
dfmat_inaug_subset <- dfm(data_corpus_inaugural[52:58], dictionary = dictliwc)
dfmat_inaug_subset[, 1:10]
```
# Más ejemplos
## Similitudes entre textos
```{r fig.width = 6}
dfmat_inaug_post1980 <- dfm(corpus_subset(data_corpus_inaugural, Year > 1980),
remove = stopwords("english"), stem = TRUE, remove_punct = TRUE)
library("quanteda.textstats")
tstat_obama <- textstat_simil(dfmat_inaug_post1980,
dfmat_inaug_post1980[c("2009-Obama", "2013-Obama"), ],
margin = "documents", method = "cosine")
tstat_obama
# dotchart(as.list(tstat_obama)$"2009-Obama", xlab = "Cosine similarity")
```
Se puede utilizar estas distancias para graficar un dendrograma, armando clusters por presidente:
```{r, fig.width = 10, fig.height = 7, eval = TRUE}
data_corpus_sotu <- readRDS(url("https://quanteda.org/data/data_corpus_sotu.rds"))
dfmat_sotu <- dfm(corpus_subset(data_corpus_sotu, Date > as.Date("1980-01-01")),
stem = TRUE, remove_punct = TRUE,
remove = stopwords("english"))
dfmat_sotu <- dfm_trim(dfmat_sotu, min_termfreq = 5, min_docfreq = 3)
# hierarchical clustering - get distances on normalized dfm
tstat_dist <- textstat_dist(dfm_weight(dfmat_sotu, scheme = "prop"))
# hiarchical clustering the distance object
pres_cluster <- hclust(as.dist(tstat_dist))
# label with document names
pres_cluster$labels <- docnames(dfmat_sotu)
# plot as a dendrogram
plot(pres_cluster, xlab = "", sub = "",
main = "Euclidean Distance on Normalized Token Frequency")
```
También se puede observar similitudes de los términos:
```{r}
tstat_sim <- textstat_simil(dfmat_sotu, dfmat_sotu[, c("fair", "health", "terror")],
method = "cosine", margin = "features")
lapply(as.list(tstat_sim), head, 10)
```
## Escalamiento de posiciones de documentos
Aquí realizamos una demostración de escalamiento de documentos unsupervised comparado con el modelo "wordfish":
```{r}
library("quanteda.textmodels")
dfmat_ire <- dfm(data_corpus_irishbudget2010)
tmod_wf <- textmodel_wordfish(dfmat_ire, dir = c(2, 1))
# plot the Wordfish estimates by party
textplot_scale1d(tmod_wf, groups = docvars(dfmat_ire, "party"))
```
## Topic models
**quanteda** hace muy sencillo ajustar topic models también. Por ejemplo:
```{r}
dfmat_ire2 <- dfm(data_corpus_irishbudget2010,
remove_punct = TRUE, remove_numbers = TRUE, remove = stopwords("english"))
dfmat_ire2 <- dfm_trim(dfmat_ire2, min_termfreq = 4, max_docfreq = 10)
dfmat_ire2
set.seed(100)
if (require(topicmodels)) {
my_lda_fit20 <- LDA(convert(dfmat_ire2, to = "topicmodels"), k = 20)
get_terms(my_lda_fit20, 5)
}
```