Los últimos meses del año pasado fueron escenario de varias movilizaciones alrededor del mundo, en respuesta a acciones gubernamentales de diversas índoles. En Ecuador, las protestas de octubre fueron detonadas por las medidas económicas anunciadas por la presidencia, en el contexto del cumplimiento de una agenda acordada con el Fondo Monetario Internacional.

Entre otras cosas, las medidas consistían en terminar inmediatamente el subsidio de combustibles. Como suele suceder con esta clase de medidas, ciertos sectores -estudiantil, indígena, agricultor, transportista- hubiesen sido los más afectados. Tras un paro nacional que se extendió del 3 al 13 de octubre, el gobierno retiró la propuesta. Para mayor información sobre la naturaleza de las medidas, la protesta de dichos sectores y la respuesta gubernamental, se recomienda consultar las siguientes fuentes: Wambra, Pichincha Comunicaciones1, Crisis y CONAIE.

El propósito de este artículo es demostrar cómo se ejecutó un análisis sobre tweets generados entre el 1 y el 10 de octubre, considerando tres hashtags que fueron populares durante el paro nacional en Ecuador. La premisa es que la elección de un hashtag expone la intención del usuario, de reportar los hechos acontecidos o de desaprobar la protesta. Originalmente se ejecutó el análisis con ayuda del paquete twitteR, que permite obtener tweets a través de la API gratuita de Twitter; para esto, es necesario ejecutar previamente una proceso de autenticación2.

library(twitteR)

setup_twitter_oauth("...", "...", "...", "...") # claves personales de desarrollador

wambra = userTimeline("wambraEc", 100, excludeReplies = FALSE, includeRts = TRUE)

A manera de ejemplo se han obtenido cien publicaciones de la cuenta “wambraEc”, incluyendo replies y retweets. El resultado es una lista de tweets donde los respectivos metadatos están ocultos:

tail(wambra)
## [[1]]
## [1] "wambraEc: RT @CDHGYE: #ParoNacionalEc\n#MuertesEnElParo \n\n@CDHGYE agradece a @wambraEc por el trabajo periodístico sobre estas graves violaciones de D…"
## 
## [[2]]
## [1] "wambraEc: RT @machadopepita: Esas son las vidas que menos importan al capitalismo, se vuelven descartables y nos corresponde honrarlas, no olvidar y…"
## 
## [[3]]
## [1] "wambraEc: RT @kinoraxx: Un 8 de enero, dos adolescentes fueron asesinados a manos de la Policía ecuatoriana #HermanosRestrepo\n32 años después la impu…"
## 
## [[4]]
## [1] "wambraEc: #MuertesEnElParo\nAnte el silencio y la falta de respuesta de las autoridades, sus familias decidieron contar sus hi… https://t.co/b5zXi9cuR6"
## 
## [[5]]
## [1] "wambraEc: [ESPECIAL]\nSon 11 nombres, muy poco pronunciados en la historia del #ParoNacional #Ecuador\n\n¿Quiénes eran las perso… https://t.co/82jimxB9P0"
## 
## [[6]]
## [1] "wambraEc: [INTERNACIONAL]\n#Chiapas: “Necesitamos y merecemos vivir”, ese fue el mensaje de clausura en el segundo Encuentro I… https://t.co/JKgRBVaQ0R"

Para tabular los metadatos se puede utilizar la función existente twitteR::twListToDF() o una función personalizada:

library(dplyr)
library(purrr)

user2na = function(user) ifelse(length(user) == 0, NA, paste0("@", user))

coor2na = function(coor) ifelse(length(coor) == 0, NA, as.double(coor))

twts2tbl = function(twts_list, time_zone = "GMT") tibble(
   isrt = map_lgl(twts_list, ~.$isRetweet),
   rts  = map_dbl(twts_list, ~.$retweetCount),
   favs = map_dbl(twts_list, ~.$favoriteCount),
   time = map_dbl(twts_list, ~.$created) %>%
      as.POSIXct(tz = time_zone, origin = "1970-01-01"),
   lon  = map_dbl(twts_list, ~coor2na(.$longitude)),
   lat  = map_dbl(twts_list, ~coor2na(.$latitude)),
   user = map_chr(twts_list, ~user2na(.$screenName)),
   rpto = map_chr(twts_list, ~user2na(.$replyToSN)),
   text = map_chr(twts_list, ~.$text))

twts2tbl(tail(wambra), "America/Guayaquil")
## # A tibble: 6 x 9
##   isrt    rts  favs time                  lon   lat user   rpto   text          
##   <lgl> <dbl> <dbl> <dttm>              <dbl> <dbl> <chr>  <chr>  <chr>         
## 1 TRUE     19     0 2020-01-09 09:15:07    NA    NA @wamb~ <NA>   "RT @CDHGYE: ~
## 2 TRUE     25     0 2020-01-09 08:25:34    NA    NA @wamb~ <NA>   "RT @machadop~
## 3 TRUE     14     0 2020-01-09 08:25:03    NA    NA @wamb~ <NA>   "RT @kinoraxx~
## 4 FALSE    75    81 2020-01-08 22:17:37    NA    NA @wamb~ @wamb~ "#MuertesEnEl~
## 5 FALSE   626   578 2020-01-08 22:17:36    NA    NA @wamb~ <NA>   "[ESPECIAL]\n~
## 6 FALSE    10    17 2020-01-06 12:54:11    NA    NA @wamb~ <NA>   "[INTERNACION~

Entre esas publicaciones se encuentra este artículo sobre las once personas que lamentablemente fallecieron participando en las manifestaciones, o de manera paralela a las mismas.

Una vez tabulados los metadatos, existe una variedad de aspectos que pueden ser analizados. La siguiente tabla presenta el conteo de tweets que contienen cada hashtag, así como el porcentaje de los mismos que se han considerado únicos (esto es, no son replies ni su texto se encuentra repetido), el conteo de usuarios y la razón promedio de tweets por usuario.

hashtag tweets (1) únicos (2) porcentaje (2÷1) usuarios (3) razón (1÷3)
#ParoNacionalEcuador 13770 11471 0.8330 5297 2.600
#EcuadorPaisDePaz 19754 13356 0.6761 5412 3.650
#ElParoSigue 13083 10223 0.7814 3892 3.361

Un par de ideas se derivan de este conteo. La utilización de #EcuadorPaisDePaz (relacionado con una desaprobación de las protestas) presentó el porcentaje más bajo de tweets únicos y la razón más alta de tweets por usuario. Aunque los números no son particularmente atípicos, ponen en evidencia una forma de contribución cercana al spam. En contraposición, #ParoNacionalEcuador demuestra el porcentaje más alto de publicaciones diferentes, y la razón más baja de contribución por usuario; este hashtag fue utilizado comúnmente para reportar, con fotos y videos, eventos represivos y violentos ejecutados por las fuerzas gubernamentales.

Además, se halló que solo tres usuarios superaron en 85 tweets el uso de #ParoNacionalEcuador; entre ellos, la cuenta de Pichincha Comunicaciones. Por el contrario, fueron catorce las cuentas que publicaron #EcuadorPaisDePaz más de 85 veces, entre ellas: los ministerios de transporte y obras públicas, de desarrollo urbano y vivienda, y de salud pública; las secretarias técnica, del deporte, y de comunicación de la presidencia; el servicio de rentas internas y cuentas zonales del ministerio de inclusión económica y social. Esta observación confirma la sospecha de que se trató de propaganda, por parte de instituciones públicas, para desmerecer las demandas de los sectores movilizados.

Los datos resumidos en la tabla anterior fueron obtenidos a través de tres ejecuciones de la función twitteR::searchTwitter(), variando cada vez el hashtag de interés. Notar el uso de un filtro que excluye retweets, pues de otra manera los tweets devueltos habrían excedido el límite impuesto de veinte mil.

"#ParoNacionalEcuador -filter:retweets" %>%
   searchTwitter(2e4, since = "2019-10-01", until = "2019-10-10")

Este análisis se efectuó originalmente en octubre; sin embargo, recuperar los datos aplicando este método resulta imposible. La razón es que la API gratuita permite obtener tweets con una semana3 de antigüedad como máximo, a diferencia del sitio web de Twitter que permite navegar por tweets de cualquier época. Afortunadamente, existe una alternativa diseñada por el programador Jefferson Henrique; básicamente es un programa java que simula la navegación en el sitio web de Twitter y recupera datos conforme son presentados. Los siguientes comandos representan la obtención de tweets que contengan #ParoNacionalEcuador a través de la command line (requiere git y java).

git clone https://github.com/Jefferson-Henrique/GetOldTweets-java
cd GetOldTweets-java
java -jar got.jar querysearch="#ParoNacionalEcuador" since=2019-10-01 until=2019-10-15
ren "output_got.csv" "output_ParoNacionalEcuador.csv"

Los resultados son escritos en un archivo de valores separados por comas, con dos particularidades: utiliza punto y coma como separador, e incurre en un uso inadecuado de las comillas. La siguiente función de lectura fue escrita teniendo en mente esas dos particularidades.

readHashTag = function(hashtag){
   
   lines = readLines(paste0("output_", hashtag, ".csv")) %>%
      gsub(";\"", ";'", .) %>% gsub("\";", "';", .)
      
   class = c(date = "POSIXct", mentions = "NULL", hashtags = "NULL", id = "character")
   
   read.csv2(text = lines, colClasses = class, quote = "'", na.strings = "") %>%
      mutate(user = sub("^https://twitter.com/(\\w+)/status/\\d+$", "@\\1", permalink),
             text = sub("https://twitter.com/.+$", "", text),
             text = sub("pic.twitter.com/.+$", "", text),
             ht = paste0("#", hashtag)) %>%
      select(ht, id, time = date, text, user, retweets, favorites) %>%
      as_tibble()
}

readHashTag("ParoNacionalEcuador")
## # A tibble: 244 x 7
##    ht       id      time                text           user   retweets favorites
##    <chr>    <chr>   <dttm>              <chr>          <chr>     <int>     <int>
##  1 #ParoNa~ 118382~ 2019-10-14 14:15:00 "La #prensa f~ @FUND~      183       109
##  2 #ParoNa~ 118357~ 2019-10-13 21:43:00 "#ATENCIÓN ES~ @kole~       84       133
##  3 #ParoNa~ 118355~ 2019-10-13 20:11:00 "Aquellos que~ @Kati~       47       111
##  4 #ParoNa~ 118351~ 2019-10-13 17:30:00 "#LoQueNoVera~ @Isma~       66        66
##  5 #ParoNa~ 118350~ 2019-10-13 17:25:00 "#Urgente #AT~ @ecua~      207       176
##  6 #ParoNa~ 118350~ 2019-10-13 17:19:00 "#ULTIMOMOMEN~ @Chal~       73        49
##  7 #ParoNa~ 118350~ 2019-10-13 17:15:00 "#Ahora | Cac~ @xime~      106        96
##  8 #ParoNa~ 118350~ 2019-10-13 17:14:00 "Pese al Esta~ @reli~      228       238
##  9 #ParoNa~ 118350~ 2019-10-13 16:57:00 "Anunciamos a~ @radi~      312       242
## 10 #ParoNa~ 118349~ 2019-10-13 16:17:00 "Asi luce la ~ @reli~       65        62
## # ... with 234 more rows

En este caso se han recuperado solamente 244 de 11471 tweets con hashtag #ParoNacionalEcuador que se obtuvieron originalmente; se trata de una cantidad mínima pero, por lo que sabemos, es la única alternativa a contratar una API comercial de Twitter. En el siguiente artículo se explorará qué aspectos del análisis son posibles con esta cantidad reducida de datos.


  1. A la fecha de publicación, el sitio web se encuentra suspendido debido a denuncias de la presidencia. De cualquier forma Pichincha Comunicaciones, al igual que las otras fuentes sugeridas, presenta en su Twitter la cobertura que realizó durante el paro.↩︎

  2. La autenticación consiste en suministrar ciertas claves personales de consumidor y de acceso, y para obtener esas claves uno debe primero solicitar una cuenta de desarrollador en Twitter.↩︎

  3. Existe otra API gratuita y oficial que recupera tweets de cualquier época, pero procedentes de una única cuenta; esta API es la utilizada en el ejemplo de “wambraEc”. La búsqueda por hashtag o texto sí está restringida a una semana.↩︎