logo

Introducción

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.

Librerías necesarias

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.

Ejercicio de aplicación

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)

Preparación del texto

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.

Creación de “párrafos”

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 objetotext``. 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

Limpieza de texto”

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)

Análisis del “Corpus”

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.

Nube de palabras y frecuencias de palabras

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
Gráfica de frecuencias y asociaciones entre palabras
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.

Análisis de datos de redes sociales

La tecnología dio lugar al crecimiento de las redes sociales, en las cuales el usuario puede crear sus contenidos, compartir mensajes de textos, fotos, videos y notas de voz. Por lo cual, se han convertido en un factor importante para la comunicación e interacción entre los usuarios. Las redes sociales han crecido muy rápido en la última década, logrando ser un importante medio para adquirir y difundir información en diferentes dominios, como negocios, entretenimiento, política, etc.

Este crecimiento de las redes sociales ha abierto una nueva oportunidad para el análisis de datos en varios aspectos. Por ejemplo, análisis de problemas actuales, análisis de tendencias, etc. El análisis de datos en redes sociales además tiene un comonente de interacción. Es decir, con los datos podemos dar seguimiento y clas interacciones que hay entre los usuarios.

A modo de ejemplo para la clase, se usara una base de datos de WhatsApp. Esta red social es la tercer aplicación más usada en el mundo, se estima que mensualmente 2 mil millones de ususarios hacen uso de esta aplicación. Statista,2024

¿Para qué sirven?

Si queremos estudiar las relaciones de varias personas entre sí, necesitamos de herramientas matemáticas y estadísticas que nos permitan procesar y analizar toda esa información. Por ello, se han desarrollado software de análisis de redes para procesar y analizar los datos. Nosotros trabajaremos con las librerías desarrolladas en R.

Análisis de datos de WhatsApp

Es bastante sencillo descargar un chat. Vas a la aplicación, abres el chat, opciones > más > exportar chat.

Luego de descargado en nuestro directorio de trabajo, procedemos a leerlo.

chat <- rwa_read("Chat.txt")

Separamos los datos por meses.

chat <- chat %>%  mutate(day = date(time)) %>% 
  mutate(# segmentación por mes
  mes = case_when(
      day >= dmy(01082022) & day <= dmy(31082022) ~ "1. Agosto 2022",
      day >= dmy(01092022) & day <= dmy(30092022) ~ "2. Septiembre 2022",
      day >= dmy(01102022) & day <= dmy(31102022) ~ "3. Octubre 2022",
      day >= dmy(01112022) & day <= dmy(30112022) ~ "4. Noviembre 2022",
      day >= dmy(01122022) & day <= dmy(31122022) ~ "5. Diciembre 2022",
      day >= dmy(01012023) & day <= dmy(31012023) ~ "6. Enero 2023",
      day >= dmy(01022023) & day <= dmy(28022023) ~ "7. Febrero 2023",
      day >= dmy(01032023) & day <= dmy(31032023) ~ "8. Marzo 2023",
      day >= dmy(01042023) & day <= dmy(30042023) ~ "9. Abril 2023",
      day >= dmy(01052023) & day <= dmy(31052023) ~ "10. Mayo 2023",
      T ~ "Fuera de rango")) %>% 
  mutate( mes = factor(mes) ) %>% 
  filter(!is.na(author))

Removemos palabras irrelevantes. Es decir, conjunciones, artículos, preposiciones, adverbios, pronombres, etc. (stopwords)

remover_palabras <- c(stopwords(),
"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", "por","esto","todo","están","hoy")

Análisis de resultados

  1. Mensajes enviados por día coloreados por mes.
colores <- rainbow(11)

chat %>%  group_by(mes) %>% count(day) %>%
  ggplot(aes(x = day, y = n, fill=mes)) +
  geom_bar(stat = "identity") +
  scale_fill_manual(values=colores) +
  ylab("Número de mensajes") + xlab("Fecha") +
  ggtitle("Mensajes por día", "Frecuencia de agosto 2022 a mayo 2023") +  theme_minimal() +  theme( legend.title = element_blank(), 
         legend.position = "bottom")

  1. Emojis más usados.
chat %>%
  unnest(emoji) %>%                                         
  count(emoji, sort = TRUE) %>%  
  top_n(n = 10, n) %>%           
  ggplot(aes(x = reorder(emoji, n), y = n)) + 
  geom_col(fill="blue", alpha=0.5) + 
  ylab("Cantidad") + xlab( "Emoji") +
  coord_flip() +
  ggtitle("Cantidad de veces que se usa un emoji")

  1. Palabras más usadas.
chat %>%
  unnest_tokens(input = text, output = word) %>%
  filter(!word %in% remover_palabras) %>% 
  count(word) %>% 
  top_n(15,n) %>% 
  arrange(desc(n)) %>% 
  ggplot(aes(x=reorder(word,n), y=n, fill=n, color=n)) +
 geom_col(show.legend = FALSE, width = .1) +
 geom_point(show.legend = FALSE, size = 3) +
 scale_fill_gradient(low="#2b83ba",high="#d7191c") +
 scale_color_gradient(low="#2b83ba",high="#d7191c") +
 ggtitle("Palabras más usadas en la conversación de manera general") +
 xlab("Palabras") +
 ylab("Número de veces que se usó la palabra") +
 coord_flip() +
 theme_minimal()

  1. Nube de palabras más usadas.
palabras <- chat %>%
  unnest_tokens(input = text, output = word) %>%
  filter(!word %in% remover_palabras) %>% 
  count(word) %>% 
  top_n(30,n) %>% 
  arrange(desc(n))

wordcloud2(palabras, size = 0.7, color ='random-light',backgroundColor = 'Black')