Cargamos las librerías que necesitaremos
library(tidyverse)
library(rtweet)
E iniciamos la descarga de tweets que mencionen alguno de los términos “tren”, “metrobus”, “colectivo”, “subte”, en un radio de 20 millas (~32 km) en torno obelisco de la Ciudad de Buenos Aires.
tweets_transporte <- search_tweets(q = "tren OR metrobus OR colectivo OR subte",
geocode = "-34.603722,-58.381592,20mi",
include_rts = FALSE,
n = 100000,
retryonratelimit = TRUE)
El proceso toma cerca de una hora. Quien no pueda esperar, puede descargar unos resultados obtenidos previamente:
load(url("https://bitsandbricks.github.io/data/tweets_transporte.RData"))
Algunos de lo tweets, aquellos que fueron publicados desde un ceular u otro dispositivo con GPS, tienen coordenadas de posición precisas (latitud y longitud). El dataframe creado por rtweet guarda la pareja de coordenadas em el campo “coords_coords”, dentro de una lista. Es decir que en lugar de un valor simple, cada elemento de la columna contiene una lista de varios valores. De manera similar, también crea otras dos columnas, “geo_coords” y “bbox_coords” que contienen datos sobre la ubicación del tweet en forma de lista.
Esto trae dos problemas:
No podemos usar los verbos de manipulación de datos con esas columnas (filter, mutate, arrange, etc) porque están diseñados para trabajar con valores atómicos, como “hola” y no listas, como (“hola”, “chau”, “sin datos”).
No podemos guardar el dataframe en formato .csv, el favorito de los que comparten datos, porque no hay forma estandarizada de indicar que algunos campos contienen una lista de datos en lugar de un valor único. write.csv y write_csv intentan guardar el dataframe en un archivo .csv, pero fallan al encontrar la primera columna que contiene listas.
La solución es simple: extraemos los datos de latitud y longitud, guardamos cada una en su propia columna, y descartamos los campos problemáticos -los que contienen información geográfica en forma de listas.
Primero creamos una función que extrae los valores de lat y long del campo problemático:
coordenadas <- function(campo_coordenadas) {
extraer_coordenadas <- function(lista_coords) {
data_frame(lon = lista_coords[1],
lat = lista_coords[2])
}
map_df(campo_coordenadas, extraer_coordenadas)
}
Y con una cadena de funciones extraemos los datos de georeferenciamiento, los agregamos al dataframe en sus propias columnas, y por último descartamos los campos en formato lista:
tweets_transporte <- tweets_transporte %>%
cbind(coordenadas(tweets_transporte$coords_coords)) %>%
select(-geo_coords, -coords_coords, -bbox_coords)
Vamos a usar leaflet, un paquete excelente que permite usar desde R leaflet.js, una librería para visualización espacial interactiva que por ser abierta y con excelentes funciones se ha convertido en la más popular.
Para empezar, filtramos nuestros tweets para conservar sólo los que contienen coordenadas exactas de posición.
tweets_transporte_geo <- tweets_transporte %>%
filter(!is.na(lat), !is.na(lon))
El resultado evidencia que lso tweets georeferenciados son sólo una pequeña fracción del total que se produce:
nrow(tweets_transporte_geo)
## [1] 97
Aunque sean pocos, nos sirven para explorar patrones espaciales llevándolos a un mapa.
ggmap es un paquete de R que complementa a ggplot, agregando funciones que permiten adquirir y visualizar mapas en forma fácil.
Si no lo tenemos instalado, ya sabemos que hacer:
install.packages("ggmap")
Lo activamos:
library(ggmap)
Ahora, para obtener un mapa base del área donde se encuentran los puntos que queremos mostrar, necesitamos determinar la “bounding box”: el rango de latitud y longitud que forma un rectángulo conteniendo todas las posiciones. En resumidas cuentas, se trata de los valores de latitud máxima y mínima, y de longitud máxima y mínima.
Los proveedores de mapas online suelen solicitar los valores en este orden: izquierda, abajo, derecha, arriba. Es decir, posición mas al oeste, posición mas al sur, posición mas al este, posición mas al norte.
Cuando disponemos de un dataframe con columnas de lat y long, obtener la bounding box es bastante fácil:
bbox <- c(min(tweets_transporte_geo$lon),
min(tweets_transporte_geo$lat),
max(tweets_transporte_geo$lon),
max(tweets_transporte_geo$lat))
Con eso podemos descargar un mapa del área. Como opción por defecto, ggmap solicita los mapas a Google Maps, pero ésta ha dejado de ser la alternativa ideal: desde octubre de 2018, Google exige a los usarios registrarse y proveer una tarjeta de crédito para descargar información mediante porgramas propios. Por eso vamos a usar otra de las fuentes habilitadas por ggmap, el servicio de mapas de Stamen Design.
Lo descargamos así, entregando la bounding box del área que nos interesa:
mimapa <- get_stamenmap(bbox)
Y para verlo usamos ggmap():
ggmap(mimapa)
Stamen ofrece varios estilos de mapa: “terrain” (usado por defecto), “terrain-background”, “terrain-labels”, “terrain-lines”, “toner”, “toner-2010”, “toner-2011”, “toner-background”, “toner-hybrid”, “toner-labels”, “toner-lines”, “toner-lite”, “watercolor”.
Probemos algunos:
mimapa_terrain_lines <- get_stamenmap(bbox, maptype = "terrain-lines")
mimapa_toner_lite <- get_stamenmap(bbox, maptype = "toner-lite")
mimapa_watercolor <- get_stamenmap(bbox, maptype = "watercolor")
ggmap(mimapa_terrain_lines)
ggmap(mimapa_toner_lite)
ggmap(mimapa_watercolor)
Cuando descargamos un mapa que vamos a usar de base para visualizar datos, siempre es una buena idea elegir una opción en escala de grises, sin colores que compitan contra los datos que proyectaremos. Probamos entonces con “toner-lite” para el mapa que usaremos de aqui en adelante.
mapa_BA <- get_stamenmap(bbox, maptype = "toner-lite")
ggmap(mapa_BA)
Ahora agregamos capas de puntos mostrando la posición de los tweets. La sintaxis es la misma que aprendimos para ggplot; de hecho, ggmap es una llamada a ggplot que tras bambalinas se encarga de los ajustes necesarios para mostrar el mapa como findo. Habiendo revisado la data de Moreno, sabemos que las columnas de longitud y latitud de los puntos georeferenciados se llaman “lon” y “lat”. Al graficar los puntos, las usaremos como posición x e y respectivamente.
ggmap(mapa_BA) +
geom_point(data = tweets_transporte_geo, aes(x = lon, y = lat))
También podemos asignar a cada punto un color de acuerdo a la popularidad del usuario:
ggmap(mapa_BA) +
geom_point(data = tweets_transporte_geo,
aes(x = lon, y = lat, color = followers_count)) +
scale_color_distiller(palette = "Spectral")
Por último, usemos el tamaño de cada punto para indicar la repercusión de cada tweet:
ggmap(mapa_BA) +
geom_point(data = tweets_transporte_geo,
aes(x = lon, y = lat, color = followers_count, size = retweet_count),
alpha = .5) +
scale_color_distiller(palette = "Spectral")
Con la explosión de de popularidad de los mapas online, con Google Maps al frente, se ha vuelto habitual explorar información geográfica en entornos interactivos, que permiten al usuario desplazarse libremente por la superficie terrestre y cambiar el nivel de zoom con el que se muestran los datos. Un mapa con información tan precisa como la posición de los tweets, que incluso permite ver a parcela desde donde se han emitido, se beneficia en extremo de la posibilidad de variar la escala de visualización a voluntad.
Desde R es fácil proyectar nuestros datos sobre mapas interactivos, usando el paquete leaflet. Si aún no lo tenemos en nuestro sistema, lo obtenemos mediante:
install.packages("leaflet")
Una vez que está instalado, lo activamos
library(leaflet)
EL uso de leaflet es similar al de ggplot; uno toma un dataframe y lo muestra mediante capas que exponen distintos aspectos de la información. Para empezar, hacemos
leaflet(tweets_transporte_geo)
… y no obtuvimos mucho. Tal como pasa con ggplot(), si uno no define ninguna capa de visualización, el resultado es una especie de lienzo vacío.
Siguiente paso: agregar un mapa base. Para sumar capas a un mapa de leaflet usamos " %>% " en ugar del " + " que requiere ggplot(), pero el concepto es el mismo.
leaflet(tweets_transporte_geo) %>%
addTiles()
Ahora está un poco mejor, nos encontramos con un mapa, pero falta que aparezcan nustros datos. Es fácil: con addMarkers() leaflet se encarga de buscar las coordenadas de cada observación, y si aparecen con algún nombre esperable, las identifica y sitúa en el mapa un pin por cada una.
Nombres esperables serían “latitude” y “longitude” o también, como en nuestro caso, “lat” y “lon”. Si las coordenadas aparecieran bajo columnas con nombres menos interpretables, se le puede aclarar a leaflet cuáles son vía paramétros.
leaflet(tweets_transporte_geo) %>%
addTiles() %>%
addMarkers()
Ya tenemos un mapa útil! Para mejorarlo, agregamos la opción de “popup”, que permite extraer información adicional cliqueando sobre un pin. Por ejemplo, el contenido del campo con el texto de cada tweet (nótese el uso de “~”, que leaflet requiere para entender que nos referimos a un campo presente en el dataframe que le pasamos).
leaflet(tweets_transporte_geo) %>%
addTiles() %>%
addMarkers(popup = ~text)
Si en vez de pines usamos círculos vía addCircleMarkers(), podemos controlar el diámetro del círculo que representa a un tweet para mostrar su impacto, de acuerdo a la cantidad de retweets que recibió:
leaflet(tweets_transporte_geo) %>%
addTiles() %>%
addCircleMarkers(radius = ~retweet_count,
popup = ~text)
Para sumar una dimensión más a la visualización, podemos usar el color para indicar la cantidad de seguidores del autor de cada tweet. Para codificar por color, leaflet requiere definir una paleta de colores para aplicar a nuestros datos. Al crear una paleta debemos elegir la función correspondiente al tipo de datos que vamos a mostrar: colorFactor() para variables categóricas, colorNumeric() para variabes numéricas, o colorQuantile() también para variables numéricas, pero agrupadas en cuantiles. La función requere al menos dos parámetros. Uno es “palette”, para definir los tonos a usar. Aquí funcionan nuestros amigos viridis, magma, plasma e inferno, y también las paletas Brewer, como _“Spectral_ o Accent). El parametro restante es”domain“, que simplemente toma un vector con los datos que vamos a representar con la paleta.
Como la cantidad de seguidores es una variable numérica, podemos usar:
paleta <- colorNumeric(
palette = "magma",
domain = tweets_transporte_geo$followers_count)
Y luego la usamos en nuestro mapa:
leaflet(tweets_transporte_geo) %>%
addTiles() %>%
addCircleMarkers(radius = ~retweet_count,
popup = ~text,
color = ~paleta(followers_count))
Como siempre es muy util agregar una leyenda que explique la codificación de los datos. leaflet sólo permite mostrar leyendas basadas en color (no en el diamétro de los círculos), pero algo es algo. Agregamos la leyenda así:
leaflet(tweets_transporte_geo) %>%
addTiles() %>%
addCircleMarkers(radius = ~retweet_count,
popup = ~text,
color = ~paleta(followers_count)) %>%
addLegend(title = "seguidores", pal = paleta, values = ~followers_count)
Esto es sólo una introducción a la producción de mapas interactivos. Para un recorrido por muchas otras opciones disponibles con leaflet, visitar https://rstudio.github.io/leaflet/