Inspirándonos en una visualización de Chris Whong, hemos querido simularla.
Para ello, se ha obtenido el dataset de esa visualización, disponible en http://chriswhong.github.io/nyctaxi/. De ese dataset se han seleccionado todos los datos de un día para un único taxi. Como el archivo descargado era tan grande y sólo necesitábamos un pequeño conjunto de este, aquí mostramos únicamente el CSV creado a partir de la obtención de los datos necesarios. Este CSV se llama DiaTaxi.csv.
Ahora veremos que con este dataset se ha realizado:
#Importación de las librerias necesarias
import pandas as pd
from datetime import datetime
import math
import datetime
#Llamada al dataset con la información sobre los datos de un día para un único taxi
data = pd.read_csv("DiaTaxi.csv")
data
Del mismo modo que en Jupiter Notebook "Limpieza de datos", calculamos la duración total de cada trayecto
pickup_hora = [pickup_time.split(" ")[1] for pickup_time in data["pickup_datetime"]]
dropoff_hora = [dropoff_time.split(" ")[1] for dropoff_time in data["dropoff_datetime"]]
data['pickup_hora'] = pd.Series(pickup_hora, index=data.index)
data['dropoff_hora'] = pd.Series(dropoff_hora, index=data.index)
duracion_segundos=[]
duracion_minutos=[]
date_format = "%Y-%m-%d %H:%M:%S"
for index, fecha in enumerate(data["pickup_datetime"]):
a = datetime.strptime(data["pickup_datetime"][index], date_format)
b = datetime.strptime(data["dropoff_datetime"][index], date_format)
c = b - a
segundos = c.total_seconds()
duracion_segundos.append(segundos)
minutos = c.total_seconds()/60
duracion_minutos.append(minutos)
data['duracion_segundos'] = pd.Series(duracion_segundos, index=data.index)
data['duracion_minutos'] = pd.Series(duracion_minutos, index=data.index)
data.head()
Como podemos ver en la tabla anterior, las rutas del taxi durante ese día se presentan desordenas y es muy importante que estas se presenten en orden temporal para crear la animación correctamente en la visualización en Carto, por tanto, las vamos a ordenar según la hora de pickup.
data = data.sort_values(by=['pickup_hora'])
data
Ahora guardamos el archivo modificado pues lo vamos a tener que usar fuera de Jupypter Notebook.
data.to_csv("DiaTaxi.csv")
El siguiente script no ha sido corrido en Jupyter Notebook pues forma parte de una librería encontrada para convertir las rutas de Google Maps en un archivo geojson y para ello, eran necesarios correr algunos otros archivos y requerimientos de la librería.
La librería encontrada y usada fue: https://github.com/kexin-zhang/gmaps2geojson
El script principal usado ha sido el siguiente, en el cual se informa explicitamente mediante comentarios qué fue lo que se cambió del código para adaptarlo a nuestro caso
import requests
import polyline
import json
import os
import pandas as pd #Necesitamos la librería pandas, por lo que la importamos
#Aquí utilizo mi key de Google Maps en ese momento, si lo quieres correr por tu cuenta, seguramente deberás usar otra.
os.environ["GMAPS_KEY"] = "AIzaSyCYaEtCgI-orYSze2msqULpaK-g0gl5jdQ"
class Writer:
def __init__(self):
self.features = []
def query(self, src, dest, custom_label = None):
request_url = 'https://maps.googleapis.com/maps/api/directions/json?origin="{0}"&destination="{1}"&key={2}'
.format(src, dest, os.environ["GMAPS_KEY"])
try:
r = requests.get(request_url)
results = r.json()
route = results['routes'][0]['overview_polyline']['points']
coords = polyline.decode(route)
except Exception as e:
print("Error querying for directions for {0} tp {1} -- {2}".format(src, dest, str(e)))
return
#reverse order to comply with geojson spec
coords_list = [[lon, lat] for lat, lon in coords]
default_name = "{0} to {1}".format(src, dest)
self.features.append({
"type": "Feature",
"properties": {
"name": custom_label or default_name
},
"geometry": {
"type": "MultiLineString",
"coordinates": [coords_list]
}
})
return [[lat, lon] for lat, lon in coords]
def save(self, filename):
geojson = {"type": "FeatureCollection", "features": self.features}
with open(filename, "w") as out:
json.dump(geojson, out)
#Código adaptado a nuestro caso a partir de aquí.
#Se hace una llamada en bucle de los pickups y dropoffs de todos los puntos que tenemos
#y se guardan en un archivo geojson
filename = "DiaTaxi.csv"
data = pd.read_csv(filename)
writer = Writer()
for index, latitude in enumerate(data["pickup_longitude"]):
s = ",";
seq_pickup = (str(data["pickup_latitude"][index]), str(data["pickup_longitude"][index]))
pickup = s.join(seq_pickup)
seq_dropoff = (str(data["dropoff_latitude"][index]), str(data["dropoff_longitude"][index]))
dropoff = s.join(seq_dropoff)
writer.query(pickup, dropoff)
writer.save("DiaTaxi.geojson")
Este script, llamado "DiaTaxiToGeojson.py", dentro de la carpeta "DiaTaxi-get-geojson", como hemos dicho, simplemente se ha corrido en Visual Studio y nos ha dado un archivo GEOJSON utilizado para:
Para conseguir lo segundo, necesitamos trabajar con los datos del archivo geojson conseguido pero en formato csv por lo que transformamos el archivo geojson en csv mediante esta página: http://www.convertcsv.com/geojson-to-csv.htm (no se si habrá algún modo de hacerlo a través de Python).
#Llamada al dataset
filename = "DiaTaxiGeojson.csv"
data = pd.read_csv(filename)
data.head()
Podemos ver que, en la columna "coordinates" tenemos el conjunto de coordenadas de cada ruta seguida. Para que estas sean útiles, tendremos que poner cada conjunto de coordenadas en una fila diferente, diferenciando entre latitud y longitud.
#Comprobación de modo en que está expresado uno de los conjuntos de coordenadas
data["coordinates"][0]
Incluimos cada una de las coordendas en una lista (todavía no diferenciamos entre latitud y longitud)
lista_coordenadas = []
for index, latitude in enumerate(data["coordinates"]):
result = data["coordinates"][index].split(",")
lista_coordenadas.append(result)
#Comprobación
lista_coordenadas[0][4]
Como en nuestro caso todas las longitudes son negativas y todas las latitudes, positivas, utilizamos esto para diferenciar entre ambas y agregar cada conjunto en una lista separada.
longitudes = []
latitudes = []
for list in lista_coordenadas:
for number in list:
number = float(number)
if number < 0:
longitudes.append(number)
else:
latitudes.append(number)
len(latitudes)
Por último, vamos a necesitar una columna que hable del momento en el que el taxi están en cada uno de los puntos de las rutas.
Idealmente, tenemos:
Con estos datos, podríamos adaptar cada conjunto de coordenadas a un momento más o menos preciso en el que el taxi pasó por ese punto pero esto es algo tedioso de conseguir puesto que:
No parece que hacer esta diferenciación temporal merezca mucho la pena para una animación tan cortita por lo que se ha hecho algo más simple: Teniendo en cuenta el número de conjuntos de coordenadas que hay (2584), se ha expandido cada una de estas a lo largo de una franja horaria de 16 horas por lo que presentamos cada conjunto de coordenadas cada 23 segundos.
Hemos establecido 16 horas debido a que el taxi es conducido por 2 taxistas, asignandoles a cada uno una jornada laboral de 8 horas.
#Redondeo a la alza
math.ceil(16*60*60/len(latitudes))
Establecemos el inicio del dia, con el primer conjunto de coordenadas, a las 8 de la mañana y a partir de ahí se le asigna una hora equidistante a cada conjunto de coordenadas.
lista = []
a = datetime.datetime(2013,2,28,8,0,0)
segundos = 23
for i in range(0,len(latitudes),1):
a = a + datetime.timedelta(0,segundos)
lista.append(a)
#Comprobación
lista[0:10]
#Creación del dataframe necesario
df = pd.DataFrame({'latitudes':latitudes, 'longitudes':longitudes, 'fecha': lista})
df.head()
Una vez terminado el proceso, guardamos el dataframe en un CSV
df.to_csv("puntos_ruta_dia_taxi.csv")