Análisis de frecuencias de palabras aplicado a tweets

Cargue de paquetes

Para llevar a cabo el caso de uso es necesario cargar los siguientes paquetes:

  • tidytext: Implementa principios de datos ordenados para hacer que muchas tareas de minería de texto sean -más fáciles
  • ggplot2: Una gramática de gráficas que expande las funciones base de R.
  • wordcloud2: Graficas datos textuales, graficas de nubes de palabras
  • tidyr: Procemsamiento
  • igraph: Necesario para grafico de redes
  • ggraph: Necesario para grafico de redes
  • udpipe: Toquenización y diccionarios en diferentes idiomas.
  • tm: Toquenización y diccionarios en diferentes idiomas.
  • stringr: Manejo de cadenas de texto y expresiones regulares
  • dplyr: Con funciones auxiliares para manipular y transformar datos. En particular, el operador %>% permite escribir funciones más legibles para seres humanos.
  • readxl: Cargar de archivos Excel.

A continuación, encontrarán el código de R para el cargue de los paquetes, es una buena práctica comentar al lado de los paquetes para recordar fácilmente porque los estamos cargando.

#Cargue de paquetes
     library(tidytext) #Análisis de datos textuales
     library(ggplot2) #Graficas
     library(wordcloud2) #Graficas datos textuales
     library(tidyr) #Procemsamiento
     library(igraph)#grafico de redes
     library(ggraph)#grafico de redes
     library("udpipe") #toquenización y diccionarios en español
     library(stringr) #Manejo de cadenas de texto expresiones regulares
     library(dplyr) #Procemsamiento
     library(readxl) #Cargar archivos excel
     library(readr) #Cargar archios csv

Cargue de datos

Aunque es posible hacer realizar la conexión de R a twitter directamente, para agilizar el desarrollo del curso obviaremos esta parte y cargaremos los datos de un archivo .csv, esta conexión será realizada más adelante a través de qlik, software en el cual el procedimiento se simplifica.

Si usted desea conocer el procedimiento para la conexión desde R puede consultar el siguiente tutorial: https://rpubs.com/Joaquin_AR/334526

El código que encuentra a continuación corresponde a la importación de una base de datos en formato .csv que contiene información de tweets, entre ella el texto correspondiente, el cual guardaremos en la variable texto

tweets <- read_delim("C:/Users/User/Downloads/TManar.csv", 
                             "|", escape_double = FALSE, trim_ws = TRUE)
     texto=tweets$UserTimeline_text

Limpieza de texto

La limpieza del texto es un proceso fundamental en la minería de texto, consiste en estandarizar el texto y eliminar de él todo lo que no aporte información útil para la temática que queremos tratar. Este proceso no es único y depende de las necesidades del investigador, a continuación, encontrará algunos de los procesos de limpieza más comunes:

  1. Convertir todo el texto a minúsculas
    # El orden de la limpieza no es arbitrario
         # Se convierte todo el texto a minúsculas
         nuevo_texto <- tolower(texto)
  1. Las URL de páginas web que traen los tweets pueden considerarse Patrones no informativos, por lo que en este caso los eliminaremos, para esto haremos uso de expresiones regulares (si usted desea aprender más de expresiones regulares puede consultar este link: https://www.diegocalvo.es/expresiones-regulares-en-r/)
    # Eliminación de páginas web (palabras que empiezan por "http." seguidas 
         # de cualquier cosa que no sea un espacio "\\S*")
         nuevo_texto <- str_replace_all(nuevo_texto,"http\\S*", "")
  1. Es posible que al descargar los tweets estos tengan emoticons, los cuales son irrelevantes para el análisis que haremos, por tanto, los podemos excluir conservando solo el texto con encoding “UTF-8”
    # Elimiinacion de emoticones
         nuevo_texto <- iconv(nuevo_texto,"UTF-8", sub="")
  1. Haciendo uso de nuevo de expresiones reculares eliminaremos signos de puntuación y números, la expresiones regulares son [[:punct:]] y [[:digit:]] respectivamente
    # Eliminación de signos de puntuación
         nuevo_texto <- str_replace_all(nuevo_texto,"[[:punct:]]", " ")
     
         # Eliminación de números
         nuevo_texto <- str_replace_all(nuevo_texto,"[[:digit:]]", " ")
  1. Eliminación de espacios en blanco múltiples
    # Eliminación de espacios en blanco múltiples
         nuevo_texto <- str_replace_all(nuevo_texto,"[\\s]+", " ")
  1. Eliminación de saltos de línea (enter).
    # Eliminación de saltos de línea
         nuevo_texto <- str_replace_all(nuevo_texto,"\n", " ")
  1. En algunas ocasiones espacios adicionales no pueden causar problemas en el proceso de análisis, por tanto, es conveniente eliminar todo espacio que no contribuya al análisis.
    # Eliminación espacios al inicio de las cadenas
         nuevo_texto <- trimws(x = nuevo_texto)

Palabras vacías o Stop words

A continuación, crearemos un diccionario con lo que en el análisis textual llamamos Stop words o palabras vacías, este diccionario contiene artículos, preposiciones, pronombres, etc., en general, palabras que no aportan información relevante sobre el texto.

Hay diferentes diccionarios de palabras, en este caso usaremos el diccionario para el idioma español del paquete tm.

Guardaremos este diccionario en la variable custom_stop_words, lo usaremos más adelante para excluir esta palabras del análisis.

  custom_stop_words <- data_frame(word = c(tm::stopwords("spanish")),
                                                   lexicon = "spanish")
# A tibble: 6 x 2
       word  lexicon
       <chr> <chr>  
     1 de    spanish
     2 la    spanish
     3 que   spanish
     4 el    spanish
     5 en    spanish
     6 y     spanish

Tokenización

La tokenización es un proceso que consiste en dividir el texto en las unidades que deseamos analizar, para esta primera parte analizaremos las palabras individuales, la tokenización la haremos con la función unnest_tokens

text_df <- data.frame(line = 1:length(nuevo_texto), text = nuevo_texto)
     
     text_df_pre <- text_df %>%
         unnest_tokens(word, text, token = "words")
  line        word
     1    1 acompáñenos
     2    1           a
     3    1      partir
     4    1          de
     5    1         hoy
     6    1           a

Eliminación de Stopwords

En las siguientes líneas de código se eliminan las palabras vacías haciendo uso del diccionario que guardamos.

text_df_pre <- text_df_pre %>% 
         anti_join(custom_stop_words)
  line        word
     1    1 acompáñenos
     2    1      partir
     3    1         hoy
     4    1     conocer
     5    1  tendencias
     6    1      manera

Análisis de frecuencias

A la hora de entender las características del texto es interesenta estudiar la frecuencia con la que se emplean las palabras,

text_df_pre <- text_df_pre  %>%
         count(word, sort = TRUE)
      word   n
     1     qlik 911
     2    datos 507
     3 qlikview 285
     4     cómo 276
     5 descubre 205
     6       bi 160

Graficos de frecuencias

text_df_pre %>%
         filter(n > 100) %>%
         mutate(word = reorder(word, n)) %>%
         ggplot(aes(word, n)) +
         geom_col() +
         xlab(NULL) +
         coord_flip()

Nube de palabras

wordcloud2(text_df_pre %>% filter(n>50))

Relaciones entre palabras: n-gramas y correlaciones

Hasta este momento hemos considerado las palabras como unidades individuales. Sin embargo, es interesante ver la relación entre las palabras, estudiando que palabras siguen a otras.

Tokenización por n-gram

En el ejercicio anterior usamos la función unnest_tokens para tokenizar por palabras, lo cual es útil para los análisis de frecuencias de palabras individuales. Pero también podemos usar la función para tokenizar en secuencias consecutivas de palabras, llamadas n-grams. Al ver la frecuencia con la que la palabra X va seguida de la palabra Y, podemos construir un modelo de las relaciones entre ellas.

pares=tibble(txt = nuevo_texto)
     austen_bigrams <- pares %>%
         unnest_tokens(bigram,txt, token = "ngrams",n=2)
     
     austen_bigrams
# A tibble: 34,021 x 1
        bigram        
        <chr>         
      1 acompáñenos a 
      2 a partir      
      3 partir de     
      4 de hoy        
      5 hoy a         
      6 a conocer     
      7 conocer las   
      8 las tendencias
      9 tendencias en 
     10 en la         
     # ... with 34,011 more rows

Examinando el resultado por medio de un conteo vemos que, como era de esperar, muchos de los pares de palabras más comunes son poco interesantes y podrían considerarse “palabras vacías”.

austen_bigrams %>%
       count(bigram, sort = TRUE)
# A tibble: 16,321 x 2
        bigram         n
        <chr>      <int>
      1 de datos     197
      2 de la        192
      3 de qlik      185
      4 en el        159
      5 de los       148
      6 los datos    148
      7 qlik sense   135
      8 en la        133
      9 con qlik      88
     10 de las        86
     # ... with 16,311 more rows

Separaremos los pares de palabras para que cada uno quede en una columna: “Word1” y “Word2”, una vez realizada esta división podremos eliminar los casos en los que encontremos stopwords.

#Separamos los pares de palabras
     bigrams_separated <- austen_bigrams %>%
         separate(bigram, c("word1", "word2"), sep = " ") #función de tidyr
     
     #quitamos los stop words
     bigrams_filtered <- bigrams_separated %>%
         filter(!word1 %in% custom_stop_words$word) %>%
         filter(!word2 %in% custom_stop_words$word)
       
     #Contamos
     bigram_counts <- bigrams_filtered %>%
         count(word1, word2, sort = TRUE)
     
     bigram_counts
# A tibble: 5,463 x 3
        word1    word2            n
        <chr>    <chr>        <int>
      1 qlik     sense          135
      2 big      data            72
      3 descubre cómo            63
      4 <NA>     <NA>            53
      5 qlik     ayuda           44
      6 cómo     qlik            43
      7 tiempo   real            37
      8 manar    technologies    28
      9 sense    cloud           25
     10 descubra cómo            21
     # ... with 5,453 more rows

Visualización de bigrams

Histograma de pares de palabras

#Unimos los pares de palabras
     bigrams_united <- bigrams_filtered %>%
         unite(bigram, word1, word2, sep = " ")
     
     #Realizamos un conteo de los pares de palabras unidas
     conteo=bigrams_united %>%
         count(bigram, sort = TRUE)
     
     #Graficamos
     conteo %>%
         filter(n >= 15) %>%
         mutate(bigram = reorder(bigram, n)) %>%
         ggplot(aes(bigram, n)) +
         geom_col() +
         xlab(NULL) +
         coord_flip()

Visualizar una red de bigrams con ggraph

Podemos organizar las palabras en una red o “gráfico”. Aquí nos referiremos a un “gráfico” no en el sentido de una visualización, sino como una combinación de nodos conectados. Se puede construir un gráfico a partir de un objeto ordenado ya que tiene tres variables:

  • de: el nodo del que proviene
  • hasta: el nodo al que se dirige
  • peso: un valor numérico asociado

El paquete igraph tiene muchas funciones poderosas para manipular y analizar redes. Una forma de crear un objeto igraph a partir de datos ordenados es la función graph_from_data_frame(), que toma un marco de datos con columnas para los atributos “desde”, “hasta” y “pesos” (en este caso n):

#creamos el objeto redefilico
     bigram_graph <- bigram_counts %>%
         filter(n >=15) %>%
         graph_from_data_frame()
       
     bigram_graph
IGRAPH e989596 DN-- 29 20 -- 
     + attr: name (v/c), n (e/n)
     + edges from e989596 (vertex names):
      [1] qlik        ->sense        big         ->data        
      [3] descubre    ->cómo         NA          ->NA          
      [5] qlik        ->ayuda        cómo        ->qlik        
      [7] tiempo      ->real         manar       ->technologies
      [9] sense       ->cloud        descubra    ->cómo        
     [11] mike        ->tarallo      mejores     ->decisiones  
     [13] tomar       ->decisiones   business    ->intelligence
     [15] utiliza     ->qlik         leadwithdata->bi          
     + ... omitted several edges
#Realizamos el grafico de redes
     set.seed(2017)
     ggraph(bigram_graph, layout = "fr") +
         geom_edge_link() +
         geom_node_point() +
         geom_node_text(aes(label = name), vjust = 1, hjust = 1)