Una de las cosas más fascinantes de esta clase de herramientas es usarlas para organizar información que existe de manera “pública” (ya veremos por qué las comillas). Usando internet y las redes sociales dejamos un auténtico reguero de información que de tan abundante y compleja que es, resulta por momentos inabordable.

Al empezar a estudiar R y lo que es big data, lo primero que pensé es que sería interesante (y por qué no, divertido) poder usar esta herramienta para relevar información de Twitter, la red social de opinión por excelencia. Así que me construí un par de funciones y experimentos para poder explorar la twittósfera.

Manos a la obra

Voy a detenerme un minuto en las librerías. Además de la clásica tidyverse, voy a usar twitteR para el relevamiento de información: este paquete tiene una construcción de funciones que me pareció cómoda para armar el setup necesario. Para usar Twitter desde una API, el requerimiento es hacerse una cuenta de developer de la red social (el proceso entero lleva menos de un día) y configurar una serie de keys y tokens (que son privados) para que twitteR los tome como parámetros y realizar las búsquedas y requerimientos.

Tidytext sirve para desgranar vectores en palabras y tm ayuda en el filtrado. Por último, wordcloud2, webshot y htmlwidgets las vamos a usar para el gráfico.

library(tidyverse)
## -- Attaching packages ------------------------------------------------- tidyverse 1.2.1 --
## v ggplot2 3.0.0     v purrr   0.2.5
## v tibble  1.4.2     v dplyr   0.7.6
## v tidyr   0.8.1     v stringr 1.3.1
## v readr   1.1.1     v forcats 0.3.0
## -- Conflicts ---------------------------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(twitteR)
## 
## Attaching package: 'twitteR'
## The following objects are masked from 'package:dplyr':
## 
##     id, location
library(tidytext)
library(tm)
## Loading required package: NLP
## 
## Attaching package: 'NLP'
## The following object is masked from 'package:ggplot2':
## 
##     annotate
library(proustr)
library(wordcloud2)
library(webshot)
library(htmlwidgets)

Ahora sigue cargar los datos que da Twitter para setear en las búsquedas de twitteR (¡parece reiterativo, lo sé!), así que este paso que va a quedar oculto por razones de seguridad ;). Cabe destacar que el mismo twitteR (paquete) es muy claro en las instrucciones a seguir para setear esos parámetros. En el medio, aprovecho a crear dos vectores (fillers y remove_reg) que vamos a usar después: son listados de palabras a filtrar al momento de hacer la limpieza de la base de datos: pronombres, vocales sueltas, artículos y esa clase de elementos. El primero es uno que construí yo a través de la propia experiencia y el otro está sugerido en el brillante Tidy Text Mining (https://www.tidytextmining.com/).

Manos a la obra

Armé dos funciones distintas. tw_search entra en el buscador de Twitter y según el input que se le ponga, construye el hashtag y busqueda una cantidad determinada de resultados. Por otro lado, tw_search_timelines hace lo mismo extrae la cantidad que se le pide de tweets de una determinada cuenta. Lo que hace la función es sistematizar el paso de convertirlo de .json (la forma en la que Twitter te devuelve los datos) a un dataframe manipulable por R y dejar todo ya preparado para trabajar.

# Función 1: tw_search 0.3 ----------------------------------------------------

tw_search <- function(x, cant){
  terminos <- deparse(substitute(x)) #extraer el nombre de la X
  terminos_h <- paste0("#", terminos) #se pega el hashtag
  json <- searchTwitter(terminos_h, n=cant) #búsqueda del hashtag y la cantidad
  base <- twListToDF(json) #se convierte el json a una lista
  assign(x=terminos, value = base, pos=1) #asigna el nombre de X y lo mando al entorno
}

# Función 2: tw_search_timelines 0.1 --------------------------------------
tw_search_timelines <- function(x, cant) {#es el mismo principio que la anterior
  terminos <- deparse(substitute(x))
  json <- userTimeline(terminos, n=cant) #la función apropiada
  base <- twListToDF(json)
  assign(x=terminos, value = base, pos=1)
}

¿Se acuerdan que en el principio decía que había unas comillas en cuán “pública” es la información? La cuestión es que Twitter tiene varias restricciones: desde cuán atrás podés remontarte hasta la cantidad de búsquedas que le podés pedir en un lapso determinado. Así y todo, para sacar una foto del momento resulta muy apropiada.

Al momento de escritura del post, el Sistema de Información Cultural de la Argentina daba a conocer su Encuesta de Consumo Cultural: “Mujeres en la cultura. Notas para el análisis del acceso y la participación cultural en el consumo y el mercado de trabajo”. Aprovechando que la cuenta oficial estaba realizando una cobertura con el #ConsumosCulturales, vamos a usarlo para mostrar cómo funcionan las búsquedas.

tw_search(ConsumosCulturales, 1500)
## Warning in doRppAPICall("search/tweets", n, params = params,
## retryOnRateLimit = retryOnRateLimit, : 1500 tweets were requested but the
## API can only return 28
str(ConsumosCulturales)
## 'data.frame':    28 obs. of  16 variables:
##  $ text         : chr  "RT @CulturaNacionAR: \"Si no me pienso como medio nuevo me quedo sin laburo. Hay que entender el podcast, las h"| __truncated__ "RT @CulturaNacionAR: Escuchá el testimonio de @CarolinaDuek1 sobre el debate de #ConsumosCulturales en el @ccde"| __truncated__ "RT @CulturaNacionAR: El actor y director de cine @martinpiro habló sobre los #ConsumosCulturales en Argentina p"| __truncated__ "Escuchá el testimonio de @CarolinaDuek1 sobre el debate de #ConsumosCulturales en el @ccdelaciencia <U+0001F447"| __truncated__ ...
##  $ favorited    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ favoriteCount: num  0 0 0 2 0 9 0 0 0 0 ...
##  $ replyToSN    : chr  NA NA NA "CulturaNacionAR" ...
##  $ created      : POSIXct, format: "2018-12-15 13:32:36" "2018-12-14 23:10:50" ...
##  $ truncated    : logi  FALSE FALSE FALSE FALSE FALSE TRUE ...
##  $ replyToSID   : chr  NA NA NA "1073643865633161216" ...
##  $ id           : chr  "1073933848910725120" "1073716975422107648" "1073716680537358341" "1073643871324790785" ...
##  $ replyToUID   : chr  NA NA NA "216106179" ...
##  $ statusSource : chr  "<a href=\"http://twitter.com/download/iphone\" rel=\"nofollow\">Twitter for iPhone</a>" "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>" "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>" "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>" ...
##  $ screenName   : chr  "elosofan" "alejadelia7" "alejadelia7" "CulturaNacionAR" ...
##  $ retweetCount : num  13 1 2 1 2 2 7 5 7 3 ...
##  $ isRetweet    : logi  TRUE TRUE TRUE FALSE TRUE FALSE ...
##  $ retweeted    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ longitude    : logi  NA NA NA NA NA NA ...
##  $ latitude     : logi  NA NA NA NA NA NA ...

Acá podemos echar un vistazo a lo que tenemos, ya sea el contenido del texto o unas cuantas variables más: quién publicó qué, si son retweets, si fueron marcados como favoritos, etc. Desde acá se puede hacer todo tipo de filtros, por ejemplo:

ConsumosCulturales %>% 
  filter(screenName=="CulturaNacionAR") %>% 
  head()
##                                                                                                                                           text
## 1     Escuchá el testimonio de @CarolinaDuek1 sobre el debate de #ConsumosCulturales en el @ccdelaciencia <U+0001F447> https://t.co/ChAD1UeLVY
## 2   El actor y director de cine @martinpiro habló sobre los #ConsumosCulturales en Argentina previo a la charla en el… https://t.co/8rhFk662mu
## 3 "Ahora hay nichos de miles de personas en las redes. Los #ConsumosCulturales, las decisiones de compra, de atención… https://t.co/Ex9kNxay3E
## 4 “El público de 12 a 17 está casi perdido, pero a partir de los 17 ya empiezan a buscar lugares de identificación. L… https://t.co/8OccGd3tyc
## 5 “El tema del encuentro cuerpo a cuerpo y reírte con el otro, no hay con que darle. Son entradas baratas, te encontr… https://t.co/1HD0stu1Ml
## 6 “El tema del encuentro cuerpo a cuerpo y reírte con el otro, no hay con que darle. Son entradas baratas, te encontr… https://t.co/x3DnWj4k38
##   favorited favoriteCount       replyToSN             created truncated
## 1     FALSE             2 CulturaNacionAR 2018-12-14 18:20:20     FALSE
## 2     FALSE             9            <NA> 2018-12-14 16:07:17      TRUE
## 3     FALSE             2 CulturaNacionAR 2018-12-10 20:46:04      TRUE
## 4     FALSE             4 CulturaNacionAR 2018-12-10 20:10:23      TRUE
## 5     FALSE             0 CulturaNacionAR 2018-12-10 20:06:00      TRUE
## 6     FALSE             0 CulturaNacionAR 2018-12-10 20:04:42      TRUE
##            replyToSID                  id replyToUID
## 1 1073643865633161216 1073643871324790785  216106179
## 2                <NA> 1073610384374984704       <NA>
## 3 1072222013329211392 1072230991174033408  216106179
## 4 1072220911883948032 1072222013329211392  216106179
## 5 1072220583042125824 1072220911883948032  216106179
## 6 1072220148654882816 1072220583042125824  216106179
##                                                         statusSource
## 1 <a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>
## 2 <a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>
## 3 <a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>
## 4 <a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>
## 5 <a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>
## 6 <a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>
##        screenName retweetCount isRetweet retweeted longitude latitude
## 1 CulturaNacionAR            1     FALSE     FALSE        NA       NA
## 2 CulturaNacionAR            2     FALSE     FALSE        NA       NA
## 3 CulturaNacionAR            1     FALSE     FALSE        NA       NA
## 4 CulturaNacionAR            3     FALSE     FALSE        NA       NA
## 5 CulturaNacionAR            1     FALSE     FALSE        NA       NA
## 6 CulturaNacionAR            1     FALSE     FALSE        NA       NA

Sabiendo cuál es el nombre de la cuenta de la Secretaría de Cultura, podemos buscar cuáles salieron de su cuenta… o podemos ver qué encontramos directamente en su timeline:

tw_search_timelines(CulturaNacionAR, 1000)
str(CulturaNacionAR)
## 'data.frame':    256 obs. of  16 variables:
##  $ text         : chr  "Hasta el 12 de enero de 2019 tenés tiempo de visitar la exposición \"Contraste simultáneo\", un punto de encuen"| __truncated__ "<U+0001F57A> Todos los viernes de #diciembre, a las 21 h, te esperamos en la @Manzana_Luces para bailar tango y"| __truncated__ "¡Alfonsín por Alfonsín! Hasta el 31/12, acercate al museo de @CasaRosada a recorrer una muestra íntima que pres"| __truncated__ "<U+0001F335> Durante el mes de #diciembre acercate al @museoterry a conocer \"Transmutación\", la exhibición de"| __truncated__ ...
##  $ favorited    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ favoriteCount: num  2 3 15 1 3 3 2 8 2 3 ...
##  $ replyToSN    : chr  NA NA NA NA ...
##  $ created      : POSIXct, format: "2018-12-19 20:10:09" "2018-12-19 19:15:08" ...
##  $ truncated    : logi  TRUE TRUE TRUE TRUE TRUE TRUE ...
##  $ replyToSID   : chr  NA NA NA NA ...
##  $ id           : chr  "1075483444287819776" "1075469601851490304" "1075454499811196929" "1075440653448941569" ...
##  $ replyToUID   : chr  NA NA NA NA ...
##  $ statusSource : chr  "<a href=\"https://www.hootsuite.com\" rel=\"nofollow\">Hootsuite Inc.</a>" "<a href=\"https://www.hootsuite.com\" rel=\"nofollow\">Hootsuite Inc.</a>" "<a href=\"https://www.hootsuite.com\" rel=\"nofollow\">Hootsuite Inc.</a>" "<a href=\"https://www.hootsuite.com\" rel=\"nofollow\">Hootsuite Inc.</a>" ...
##  $ screenName   : chr  "CulturaNacionAR" "CulturaNacionAR" "CulturaNacionAR" "CulturaNacionAR" ...
##  $ retweetCount : num  0 2 7 2 4 2 3 3 1 1 ...
##  $ isRetweet    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ retweeted    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ longitude    : logi  NA NA NA NA NA NA ...
##  $ latitude     : logi  NA NA NA NA NA NA ...

Ahora dicen que…

Vamos a agarrar la base del #ConsumosCulturales y preparémonos para hacer un wordcloud, una nube de palabras que nos permita ver qué fue lo que más se dijo en los tweets al respecto. El paquete wordcloud2 nos exige tener una columna llamada word y otra freq, donde tome las palabras a utilizar y cuántas veces aparecen. Hay formas también de setearle variables a colores, pero por hoy quedémonos con esta versión.

Lo que tenemos que hacer es deconstruir esa variable “text” en todas las palabras que contienen y de ahí filtrar las palabras de relleno. Acá entran en juego esos vectores qeu creamos al inicio, así como también las stopwords del paquete tm y un pequeño truco para que tome sólo lo que puede empezar con una letra, un # o una @. De ahí lo que sigue es cambiarle los nombres a las variables que necesitamos y quedarnos sólo con ellas.

cons_cult_wordcloud <- ConsumosCulturales %>%
  mutate(text=str_remove_all(text, remove_reg)) %>%
  unnest_tokens(Palabra, text) %>%
  count(Palabra, sort=TRUE) %>%
  filter(!Palabra%in%stopwords('es')) %>%
  filter(!Palabra%in%fillers) %>%
  filter(str_detect(Palabra, "^[a-zA-z]|^#|^@")) %>%
  ungroup() %>%
  arrange(desc(n)) %>% 
  mutate(word=Palabra,
         freq=n) %>% 
  select(word, freq)

head(cons_cult_wordcloud)
## # A tibble: 6 x 2
##   word                freq
##   <chr>              <int>
## 1 culturanacionar       19
## 2 consumosculturales     8
## 3 cuerpo                 8
## 4 personas               6
## 5 encuesta               5
## 6 atención               4

Podemos ver que “culturanacionar” (el Twitter oficial de la Secretaría) y “consumosculturales” (el propio hashtag en cuestión) aparecen al tope de la lista. Sólo para reflejar mejor el resto de los resultados, lo voy a eliminar al preparar el gráfico.

Llegado ese momento, soy plenamente conciente de que usé una manera poco ortodoxa de preparar el código (creo que es el equivalente a tener una pésima caligrafía, lo cual en mi caso también es cierto) pero fue la forma en la que mejor logré que se visualicen los resultados. Voy a crear el wordcloud como un objeto y a través de webshot, “sacarle una foto” al resultado. El delay que se observa en el código es porque la función muchas veces se toma su tiempo hasta desplegar todos los elementos que contiene el gráfico, por lo que elijo decirle que se tome 15 segundos hasta que tome la captura. ¿Cómo queda todo esto?

hw <- wordcloud2(cons_cult_wordcloud[3:505,], size = 0.5, gridSize = 8)
saveWidget(hw, "1.html", selfcontained = F)
webshot::webshot("1.html", "1.png", vwidth = 1024, vheight = 860, delay = 15)

El resultado es muy atractivo y bastante claro: de un vistazo rápido podemos ver las palabras más utilizadas en Twitter para hablar de un tema determinado. En próximos posteos vamos a poner en práctica la scrapera, como la llamo cariñosamente, para ir viendo otra clase de análisis.