En la entrega anterior, seguimos el rastro del usuario a través de las calles, y descubrimos donde vive.
Como cierre, vamos a visualizar los vuelos con los que el usuario conectó las ciudades por donde estuvo.
Para reproducir los resultados, recomiendo correr antes el código de la parte I, que deja preparados los sets de datos que vamos a utilizar aquí.
Identificando vuelos
Durante los ejercicios anteriores, notamos que los datos contienen ubicaciones registradas a ambos lados del Atlántico. A no ser que nuestro viajero haya optado por largos viajes en barco, podemos estar seguros de que ha tomado varios aviones. ¿Cómo podemos detectar los destinos unidos mediante vuelos?
Tras experimentar con varios métodos, me encontré con que la forma más simple es buscar registros de ubicación consecutivos separados por más de 250 Km. Tomando ventaja de la prohibición de usar las antenas del celular durante el vuelo, asumimos que si alguien recorrió un largo trecho desde su última conexión a GPS ha estado a bordo de una aeronave. Hay que aclarar que este criterio podría producir falsos positivos en caso de que el usuario haya viajado en auto por larguísimos trechos con su celular apagado (y falsos negativos en el caso de tramos en avión muy breves).
A continuación necesitamos traducir a metros la distancias entre geo-coordenadas. Podríamos escribir una función que aplique la fórmula del haversine, o haciendo gala de una saludable pereza, usar la que viene incluida en el paquete geosphere
:
library(tidyverse)
library(geosphere)
# Cargamos el dataset con el historial de ubicaciones, obtenido en # https://bitsandbricks.github.io/post/visualizando-con-r-el-historial-de-ubicaciones-de-google-parte-i/
locationdf <- read.csv('/home/havb/data/Google/Location History/locationdf.csv',
stringsAsFactors = F)
class(locationdf$date) <- 'POSIXct'
# La funcion distGeo toma dos listas con sets de coordenadas,
# y devuelve la distancia en m
distance <- distGeo(locationdf[-nrow(locationdf), c("lat", "lon")],
locationdf[-1, c("lat", "lon")])
# Unimos los datos de distancia con el dataset de ubicaciones
# Descartamos la primera fila del dataset de ubicaciones, ya que al no registrarse
# una posición previa, no permite calcular distancia recorrida
locationdf <- cbind(locationdf[-1,], distance)
# Agregamos un indice al dataframe, que nos va a servir luego
# para encontrar el origen de los viajes
locationdf <- cbind(index = 1:nrow(locationdf), locationdf)
# Extraemos los destinos de los vuelos (arrivos a más de 250 KM del último registro)
flights <- locationdf %>%
select(index, lat, lon, date, year, CITY_NAME, CNTRY_NAME, distance) %>%
filter(distance > 250000)
# Agregamos el lugar de salida de los vuelos
findOrigin <- function(index) {
return(data.frame(prev_lat = locationdf[locationdf$index == index-1, "lat"],
prev_lon = locationdf[locationdf$index == index-1, "lon"],
prev_city = locationdf[locationdf$index == index-1, "CITY_NAME"])
)
}
# Finalmente, nuestra lista de vuelos
flights <- cbind(flights, map_df(flights$index, findOrigin))
La lista de vuelos encontrados luce así (versión abreviada):
head(flights[c("CITY_NAME", "prev_city", "distance", "year")])
## CITY_NAME prev_city distance year
## 1 Salta Buenos Aires 934818.1 2011
## 2 Buenos Aires San Miguel De Tucuman 850142.9 2011
## 3 Resistencia Buenos Aires 416533.2 2011
## 4 Buenos Aires Resistencia 416909.9 2011
## 5 Madrid Buenos Aires 8786212.0 2011
## 6 Amsterdam Madrid 1632970.9 2012
Transformar la lista en una visualización es simple; sólo trazamos sobre un mapamundi las lineas que conectan orígenes y destinos:
library(mapdata)
maps::map("world", col="#f2f2f2", fill=TRUE, bg="white", lwd=0.15)#, xlim=xlim, ylim=ylim)
points(bind_rows(flights[, c("lon", "lat")],
flights[, c("prev_lon", "lat")]),
col = "red")
lines(bind_rows(flights[, c("lon", "lat")],
flights[, c("prev_lon", "lat")]),
col = "orange", lwd=2)
title( main = ":/")
Simple si, pero esas líneas rectas quedan bastante raras. Dado que nuestro planeta es un esferoide, si proyectamos sobre un plano la distancia más corta entre dos puntos, la línea resultante no es una recta. Por ejemplo, así es como Delta Airlines muestra los vuelos que brinda en sociedad con Aerolíneas Argentinas:
Para reproducir esas curvas sobre el globo terráqueo, que llevan el simpático nombre de ortodrómicas, volvemos a recurrir al paquete geosphere
:
curvas <- gcIntermediate(flights[c("lon", "lat")],
flights[c("prev_lon", "prev_lat")])
maps::map("world", col="#f2f2f2", fill=TRUE, bg="white", lwd=0.15)#, xlim=xlim, ylim=ylim)
points(bind_rows(flights[, c("lon", "lat")],
flights[, c("prev_lon", "lat")]),
col = "red")
for(i in 1:length(curvas)){
lines(curvas[[i]], col = "orange", lwd=2)
}
title( main = ":)")
Inflando el globo
Por último, hagamos una proyección esférica. De paso podemos mostrar más información, como el año en que se efectuó cada vuelo, o la cantidad de veces que se pasó por un destino. Para ello, necesitamos (como siempre!) preparar nuestros datos.
# Generamos las coordenadas de las curvas/trayectos
curvas <- gcIntermediate(flights[,c('prev_lon', 'prev_lat')],
flights[,c('lon', 'lat')],
100, addStartEnd=TRUE, sp=TRUE)
# Convertimos las curvas (que son un "spatial object") en una lista de dataframes que vamos a poder plotear
curvas <- map_df(curvas@lines, fortify)
# Agregamos la data de los vuelos
flights$index <- 1:nrow(flights)
curvas <- merge(curvas, flights, by.x = "id", by.y = "index", all.x = T)
# Acomodamos los datos de las ciudades para agregarlas a la visuaizacion
# Unimos las ciudades, sean origen o destino, en una sola lista
ciudades <- bind_rows(flights[c("lat", "lon", "CITY_NAME")] %>%
setNames(c("lat", "lon", "ciudad")),
flights[c("prev_lat", "prev_lon", "prev_city")] %>%
setNames(c("lat", "lon", "ciudad")))
ciudades <- ciudades %>%
# Las coordenadas de las ciudades difieren enre registros, debido a difrentes
# posiciones exactas del usuario. Lo arreglamos...
aggregate(data = ., cbind(lon, lat) ~ ciudad, FUN = function(x) mean(range(x))) %>%
# Agregamos frecuencia de visitas
left_join(count(ciudades, ciudad))
Ahora si, generemos la visualización. Indicamos el año del vuelo mediante su color, y la cantidad de veces que el usuario pasó por una ciudad mediante el tamaño del punto que la representa.
## Un globo terráqueo
library(hrbrthemes)
library(ggrepel)
ggplot() +
borders("world", colour="gray80", fill="gray95") +
# Graficamos los trayectos, diferenciando por año
geom_path(data = curvas,
aes(long, lat.x ,
group = group,
color = as.factor(year)),
size = 1.6,
alpha = .85) +
scale_color_brewer(palette = "YlGn") +
# Agregamos las ciudades
geom_point(data=ciudades, aes(x = lon, y = lat, size = n),
shape = 16, alpha = .5) +
# y etiquetas con los nombres
geom_label_repel(data = ciudades,
aes(lon, lat, label = ciudad, angle = 10,
group = NULL),
size = 3) +
# Centramos la proyección en el Océano Atlántico
coord_map("ortho", orientation = c(10, -40, 0)) +
labs(y = "", x = "",
title="Historial de ubicaciones de Google",
subtitle="vuelos detectados",
color = "Año",
size = "Frecuencia") +
theme_ipsum()
No luce nada mal.
Para terminar, una simple conclusión: Google sabe demasiado sobre sus usuarios. Sería bueno pensar en como podemos remediarlo. No me refiero a “volver atrás” y dejar de usar tecnologías como el GPS y los registros de ubicación, sino a encontrar la forma de tomar control de nuestros datos, y tener el derecho a saber que se hace con ellos, limitando los usos que no nos convenzan.