La minería de texto es un campo de la ciencia de datos que se centra en extraer información útil y conocimiento significativo a partir de grandes cantidades de texto no estructurado. Con el auge de la digitalización y la disponibilidad masiva de datos en forma de documentos, correos electrónicos, publicaciones en redes sociales, entre otros, la minería de texto se ha convertido en una herramienta poderosa para analizar y comprender el contenido textual.
Es importante resaltar que este módulo es introductorio, donde se realizan operaciones elementales de minería de texto usando R, las cuales pueden proporcionar herramientas básicas para este tipo de análisis. Pero si se necesita hacer algún análisis más complejo, será necesario adquirir otras herramientas mas complejas.
LAs librerías que necesitaremos son las siguientes:
tm
:
específico para minería de textos.wordcloud
:
para gráficar nubes de palabras.ggplot2
:
para gráficas interactivas.dplyr
:
para manipular y transformar datos.readr
:
facilitará leer tablas.Para entender las diferencias funciones que estaremos trabajando,
tomaremos como ejemplo de texto el libro ``Niebla
de Miguel de Unamuno. Lo pueden descargar de la pagina
de Moodle.
Una vez descargado el documento a nuestro directorio de trabajo,
procedemos a leerlo usando la función read_lines
de readr.
Nuestro interés está en el contenido de este libro y no en los avisos
legales y anotaciones, así que los omitiremos de la lectura. Empezaremos
a leer el documento desde la línea 28, que es donde inicia realmente el
libro, para ello nos saltaremos (skip
) 28 líneas previas,
pero igual se guarda la información legal por aparte para cuestiones de
referencias.
Niebla_info <- read_lines("Niebla.txt",n_max = 28-0)
Niebla_info
## [1] "The Project Gutenberg EBook of Niebla, by Miguel de Unamuno"
## [2] ""
## [3] "This eBook is for the use of anyone anywhere in the United States and most"
## [4] "other parts of the world at no cost and with almost no restrictions"
## [5] "whatsoever. You may copy it, give it away or re-use it under the terms of"
## [6] "the Project Gutenberg License included with this eBook or online at"
## [7] "www.gutenberg.org. If you are not located in the United States, you'll have"
## [8] "to check the laws of the country where you are located before using this ebook."
## [9] ""
## [10] ""
## [11] "Title: Niebla"
## [12] " (Nivola)"
## [13] ""
## [14] "Author: Miguel de Unamuno"
## [15] ""
## [16] "Release Date: August 31, 2015 [EBook #49836]"
## [17] ""
## [18] "Language: Spanish"
## [19] ""
## [20] "Character set encoding: UTF-8"
## [21] ""
## [22] "*** START OF THIS PROJECT GUTENBERG EBOOK NIEBLA ***"
## [23] ""
## [24] "Produced by Roberto Marabini, Ramon Pajares Box and the"
## [25] "Online Distributed Proofreading Team at http://www.pgdp.net"
## [26] "(This file was produced from images generously made"
## [27] "available by The Internet Archive/Canadian Libraries)"
## [28] ""
Niebla <- read_lines("Niebla.txt",skip = 28)
El objeto que cargamos es de tipo character
, con 7760
elementos.
str(Niebla)
## chr [1:7760] "NIEBLA" "" "" "I" "" "" ...
Cada uno de estos elementos corresponde a un renglón de libro y tiene ancho máximo 70 caracteres, pues este es el ancho usado en los textos electrónicos de Gutenberg. Esta es una cantidad muy pequeña de texto para encontrar asociaciones entre palabras, por lo que necesitamos crear elementos con una mayor cantidad de caracteres en cada uno. Por lo tanto, crearemos elementos del tamaño aproximado a un párrafo.
Creamos estructuras de posibles “párrafos”, los cuales tendrán un tamaño de diez renglones consecutivos. Vamos uniendo grupos de 10 renglones hasta llegar al máximo de grupos posibles en función del número de renglones de nuestro documento.
diez <- rep(1:ceiling(length(Niebla)/10), each = 10)
Combinamos diez
con
`Nieblay los asignamos al objeto
text``. Así tenemos una
columna con los renglones de texto y otra con un número que identifica a
qué grupo de diez renglones pertenece.
Además, convertimos a data.frame
para que las columnas
estén identificadas con un nombre, lo cual será útil en los siguientes
pasos.
text <- cbind(diez, Niebla) %>% data.frame()
Usamos la función aggregate
para concatenar los
renglones (FUN = paste
, con collapse = " "
para preservar el espacio entre palabras).
text <- aggregate(Niebla ~ diez,
data = text,
FUN = paste,
collapse = " ")
Aprovechamos para transformar text
en una matriz, pues
esto nos facilitará los pasos siguientes.
text <- text %>% select(Niebla) %>% as.matrix
dim(text)
## [1] 776 1
Necesitamos limpiar el texto de caracteres que son de poca utilidad en la minería de textos. Empezamos por aseguramos de que no queden caracteres especiales de la codificación, como saltos de línea y tabulaciones.
text <- gsub("[[:cntrl:]]", " ", text)
Convertimos todo a minúsculas.
text <- tolower(text)
Usamos las funciones removeWords
con
stopwords("spanish")
para eliminar palabras vacías, es
decir, aquellas con poco valor para el análisis, tales como algunas
preposiciones y muletillas.
text <- removeWords(text, words = stopwords("spanish"))
# palabras observadas que no son parte del análisis
text <- removeWords(text, words = c("usted", "pues", "tal", "tan", "así", "dijo", "cómo", "sino", "entonces", "aunque", "don", "doña","señor","y", "la", "el","en", "es", "si", "lo", "ya", "pero", "esa", "los","yo","mi", "un", "con", "las", "más","eso", "al", "una", "del", "qué", "así", "le", "su", "va", "porque", "todos", "hay", "les", "pues", "ese", "son", "está", "pues", "ahí", "sí","ver", "estás", "algo", "vas", "ir","voy", "creo","fue","solo", "ni","sólo","nada", "aqui", "que", "tú", "alguien", "tu", "que", "de", "se", "te", "o", "como", "este", "bien", "nos", "para","día","días","buenos","buenas","sé","fué","dos","ser","sabe", "por","esto","todo","están","hoy"))
Nos deshacemos de la puntuación, puesto que fin y fin. son identificadas como palabras diferentes, lo cual no deseamos.
text <- removePunctuation(text)
Ahora, removemos los números, pues en Niebla no hay fechas u otras cantidades que deseemos conservar.
text <- removeNumbers(text)
Por último eliminamos los espacios vacíos excesivos, muchos de ellos introducidos por las transformaciones anteriores.
text <- stripWhitespace(text)
Con nuestro documento preparado, procedemos a crear nuestro Corpus, es decir, nuestro acervo de documentos a analizar.
En nuestro caso, nuestro Corpus se compone de todos los párrafos del libro Niebla.
Corpus <- Corpus(VectorSource(text))
Corpus
## <<SimpleCorpus>>
## Metadata: corpus specific: 1, document level (indexed): 0
## Content: documents: 776
Nuestro Corpus está compuesto por 776 documentos. Los siguientes análisis se harán a partir de este Corpus.
Usaremos la función TermDocumentMatrix
en nuestro Corpus
y asignaremos el resultado al objeto nov_tdm.
nov_tdm <- TermDocumentMatrix(Corpus)
nov_tdm
## <<TermDocumentMatrix (terms: 7184, documents: 776)>>
## Non-/sparse entries: 20014/5554770
## Sparsity : 100%
## Maximal term length: 17
## Weighting : term frequency (tf)
Podemos observar que tenemos 7184 terms, esto quiere decir que tenemos 7184 palabras diferentes en nuestro Corpus. Lo cual es una cantidad considerable de vocabulario.
Para obtenerlas, primero transformaremos nuestro objeto nov_tdm en un objeto de clase matriz, que de nuevo tendrá un número de renglones igual al número de palabras distintas de nuestro Corpus y número de columnas igual a su número de documentos.
nov_mat <- as.matrix(nov_tdm)
dim(nov_mat)
## [1] 7184 776
Obtenemos las sumas de renglones (rowSums
) odenadas de
mayor a menor (sort
con decreasing = TRUE
)para
conocer la frecuencia de cada palabra y después transformamos los
resultados a objeto de clase data.frame
de dos columnas,
palabra
y frec
, que nos permitirá gráficar
fácilmente su contenido.
nov_mat <- nov_mat %>% rowSums() %>% sort(decreasing = TRUE)
nov_mat <- data.frame(palabra = names(nov_mat), frec = nov_mat)
wordcloud(
words = nov_mat$palabra,
freq = nov_mat$frec,
max.words = 80,
random.order = F,
colors=brewer.pal(name = "Dark2", n = 8)
)
Aunque una nube de palabras nos muestra de manera visual la frecuencia de las palabras en nuestro Corpus, no nos devuelve cantidades.
nov_mat[1:20, ]
## palabra frec
## augusto augusto 365
## eugenia eugenia 202
## mujer mujer 184
## hombre hombre 133
## casa casa 127
## ahora ahora 124
## mismo mismo 103
## vez vez 91
## ojos ojos 87
## vida vida 86
## luego luego 76
## cosas cosas 72
## madre madre 71
## pobre pobre 71
## decir decir 71
## después después 71
## dios dios 70
## hacer hacer 68
## decía decía 66
## parece parece 65
nov_mat[1:10, ] %>%
ggplot(aes(palabra, frec)) +
geom_bar(stat = "identity", color = "black", fill = "#87CEFA") +
geom_text(aes(hjust = 1.3, label = frec)) +
coord_flip() +
labs(title = "Diez palabras más frecuentes en Niebla", x = "Palabras", y = "Número de usos")
Para las asociaciones, podemos introducir un vector. He elegido “augusto”, “eugenia”, “hombre” y “mujer”. Es importante recordar que con esto no estamos pidiendo la asociación de estas cuatro palabras entre si, sino las asociaciones para cada una de las cuatro, que no necesariamente deben coincidir.
Esta también nos pide el límite inferior de correlación
(corlimit
) para mostrarnos. Valores cercanos a 1 indican
que las palabras aparecen casi siempre asociadas una con otra, valores
cercanos a 0 nos indican que nunca o casi nunca lo hacen.
findAssocs(nov_tdm, terms = c("augusto", "eugenia", "hombre", "mujer"), corlimit = .2)
## $augusto
## numeric(0)
##
## $eugenia
## lucharemos ojos nebulosa separó
## 0.23 0.22 0.22 0.20
##
## $hombre
## almacena creerlo cuervos devoren empezando
## 0.34 0.34 0.34 0.34 0.34
## enseñé entenderás» silencios ¡almacenan ¡cuánto
## 0.34 0.34 0.34 0.34 0.34
## muertos viste cuánto salida lametones
## 0.33 0.31 0.30 0.28 0.28
## «¡pobre animal huesos aguabenditera bendita
## 0.26 0.25 0.23 0.20 0.20
## cancela encontráronse mojó rezos santiguándose
## 0.20 0.20 0.20 0.20 0.20
## susurraba buscado decidida defecto jugarme
## 0.20 0.20 0.20 0.20 0.20
## capital feminismo engendrarlo mortal respire
## 0.20 0.20 0.20 0.20 0.20
## resucitarlo ¡resucitarlo ¿resucitarlo almacenan confundían
## 0.20 0.20 0.20 0.20 0.20
## enterrarán guardan trajes
## 0.20 0.20 0.20
##
## $mujer
## hermosura hermosa suponía hijos rechazarme
## 0.25 0.24 0.24 0.23 0.22
## abstracto legítima aceptó agraciada ardor
## 0.21 0.21 0.21 0.21 0.21
## cerraban exceso figuré partos perderla
## 0.21 0.21 0.21 0.21 0.21
## párpados venas fogueteiro bellas busto
## 0.21 0.21 0.21 0.21 0.21
## desvanecidos excelencias explosión fastidiarse gravísimas
## 0.21 0.21 0.21 0.21 0.21
## inspiradora inspirarle musa pirotécnicas ponderar
## 0.21 0.21 0.21 0.21 0.21
## prende producciones pólvora quemaduras quemó
## 0.21 0.21 0.21 0.21 0.21
## arrogante compadecían convertida desafío desfigurada
## 0.21 0.21 0.21 0.21 0.21
## desfiguramiento hermosa» lazarilla orgulloso ponderaban
## 0.21 0.21 0.21 0.21 0.21
## ponderándola sabedores talle admita admitirme
## 0.21 0.21 0.21 0.21 0.21
## cuerpos disfruta femeninos hermosos masculino
## 0.21 0.21 0.21 0.21 0.21
## mayúscula millones obligada rechazará reparte
## 0.21 0.21 0.21 0.21 0.21
Aunque “augusto” es la palabra más frecuente, no tiene relaciones tan fuertes como las demás palabras que elegimos. De hecho, no tiene ninguna superior a .2, por eso el resultado mostrado.