Polígonos para Aeropuerto JFK y Empire State

La zona del Aeropuerto JFK y la zona central de Manhattan (por ejemplo, el Empire State) parecen dos zonas muy interesantes de NYC por lo que parece una buena idea estudiarlas por separado.

Para aislar los puntos correspondientes a estas zonas de los demás puntos, debemos crear, para cada zona, un polígono que abarque dicha área y luego pueda ser comparado con nuestro dataset y poder quedarnos sólo con aquellos puntos dentro de estos polígonos.

Para ello, primero de todo hemos obtenidos los 2 polígonos necesarios a través de una herramienta de Tableau muy útil y que viene muy bien explicada en este blog https://tableauandbehold.com/2015/05/13/creating-custom-polygons-by-drawing-directly-on-a-google-map/.

Así, a través de "tableau-map-pack.html" hemos obtenido los 2 polígonos necesarios y se han guardado en los archivos Aeropuerto_JFK.csv y Empire_state.csv. Hecho esto, se realizó la comparación de estos polígonos con nuestro conjunto de datos

In [4]:
#Importación de las librerías necesarias
import pandas as pd
import matplotlib.path as mpltPath
In [5]:
#Llamada a nuestro dataset
filename = "data_limpios.csv"
data = pd.read_csv(filename, sep=";", decimal=',', encoding = "utf-8")
data.head()
Out[5]:
Unnamed: 0 ID vendorID pickup_datetime dropoff_datetime numero_pasajeros distancia pickup_longitude pickup_latitude ratecodeID ... propina peaje recargo_mejora cantidad_total pickup_hora color dropoff_hora duracion_segundos duracion propina?
0 0 11600 VeriFone Inc 2016-01-04 00:02:19 2016-01-04 00:23:11 1 16.37 -73.790016 40.646664 Aeropuerto JFK ... 17.5 5.54 0.3 75.84 00:02:19 Amarillo 00:23:11 1252.0 20.866667 Si
1 1 11700 VeriFone Inc 2016-01-04 00:02:22 2016-01-04 00:31:04 1 19.66 -73.782204 40.644642 Tarifa normal ... 10.0 0.00 0.3 64.30 00:02:22 Amarillo 00:31:04 1722.0 28.700000 Si
2 2 11789 VeriFone Inc 2016-01-04 00:05:37 2016-01-04 00:26:02 3 4.07 -73.964691 40.803478 Tarifa normal ... 0.0 0.00 0.3 18.30 00:05:37 Amarillo 00:26:02 1225.0 20.416667 No
3 3 11867 VeriFone Inc 2016-01-04 00:05:37 2016-01-04 00:15:46 5 2.15 -74.003868 40.751381 Tarifa normal ... 0.0 0.00 0.3 10.80 00:05:37 Amarillo 00:15:46 609.0 10.150000 No
4 4 26960 VeriFone Inc 2016-01-04 00:02:21 2016-01-04 00:17:48 1 3.96 -73.991997 40.754192 Tarifa normal ... 2.0 0.00 0.3 18.30 00:02:21 Amarillo 00:17:48 927.0 15.450000 Si

5 rows × 27 columns

In [6]:
#Creamos dos columnas en nuestro dataframe. 
#Sus filas serán rellenadas con "Aeropuerto_JFK" o "Empire_state" si son puntos pertenecientes a los polígonos
data['pickup_zona_interes'] = pd.Series("Desconocido", index=data.index)
data['dropoff_zona_interes'] = pd.Series("Desconocido", index=data.index)

#Lista que contiene los nombres de los archivos csv con los polígonos
zonas = ["Empire_state", "Aeropuerto_JFK"]

#Funcion que devuelve el conjunto de coordenadas que forman los polígonos
def generarZona(zona):
    datos_zona = pd.read_csv(zona + ".csv")
    poligono_zona = [[datos_zona["Latitude"][index], datos_zona["Longitude"][index]] for index, latitud in enumerate(datos_zona["Latitude"])]
    return mpltPath.Path(poligono_zona)

#Función para establecer si los puntos pertenecen al polígono. Si sí pertenecen, el valor de las filas creadas
#anteriormente, cambia
def actualizarZona(zona, path, accion):
    for index, latitude in enumerate(data[accion + "_latitude"]):
        if path.contains_point([data[accion + "_latitude"][index], data[accion + "_longitude"][index]]) == True:
            data.set_value(index, accion + '_zona_interes', zona)

#Llamamos a las 2 funciones anteriores para cambiar los valores de la columna "pickup_zona_interes"
for zona in zonas:
    path = generarZona(zona)
    actualizarZona(zona, path,"pickup")

#Llamamos a las 2 funciones anteriores para cambiar los valores de la columna "dropoff_zona_interes"
for zona in zonas:
    path = generarZona(zona)
    actualizarZona(zona, path,"dropoff")
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/ipykernel_launcher.py:20: FutureWarning: set_value is deprecated and will be removed in a future release. Please use .at[] or .iat[] accessors instead
In [7]:
#Comprobación de cómo los valores de las columnas "pickup_zona_interes" y "dropoff_zona_interes" han cambiado
data.head(10)
Out[7]:
Unnamed: 0 ID vendorID pickup_datetime dropoff_datetime numero_pasajeros distancia pickup_longitude pickup_latitude ratecodeID ... recargo_mejora cantidad_total pickup_hora color dropoff_hora duracion_segundos duracion propina? pickup_zona_interes dropoff_zona_interes
0 0 11600 VeriFone Inc 2016-01-04 00:02:19 2016-01-04 00:23:11 1 16.37 -73.790016 40.646664 Aeropuerto JFK ... 0.3 75.84 00:02:19 Amarillo 00:23:11 1252.0 20.866667 Si Aeropuerto_JFK Desconocido
1 1 11700 VeriFone Inc 2016-01-04 00:02:22 2016-01-04 00:31:04 1 19.66 -73.782204 40.644642 Tarifa normal ... 0.3 64.30 00:02:22 Amarillo 00:31:04 1722.0 28.700000 Si Aeropuerto_JFK Desconocido
2 2 11789 VeriFone Inc 2016-01-04 00:05:37 2016-01-04 00:26:02 3 4.07 -73.964691 40.803478 Tarifa normal ... 0.3 18.30 00:05:37 Amarillo 00:26:02 1225.0 20.416667 No Desconocido Desconocido
3 3 11867 VeriFone Inc 2016-01-04 00:05:37 2016-01-04 00:15:46 5 2.15 -74.003868 40.751381 Tarifa normal ... 0.3 10.80 00:05:37 Amarillo 00:15:46 609.0 10.150000 No Desconocido Desconocido
4 4 26960 VeriFone Inc 2016-01-04 00:02:21 2016-01-04 00:17:48 1 3.96 -73.991997 40.754192 Tarifa normal ... 0.3 18.30 00:02:21 Amarillo 00:17:48 927.0 15.450000 Si Desconocido Desconocido
5 5 27436 VeriFone Inc 2016-01-04 00:05:37 2016-01-04 00:13:31 1 2.31 -73.986153 40.746899 Tarifa normal ... 0.3 12.36 00:05:37 Amarillo 00:13:31 474.0 7.900000 Si Empire_state Desconocido
6 6 27892 VeriFone Inc 2016-01-04 00:05:39 2016-01-04 00:13:41 1 2.40 -74.000114 40.732841 Tarifa normal ... 0.3 11.30 00:05:39 Amarillo 00:13:41 482.0 8.033333 Si Desconocido Desconocido
7 7 28153 VeriFone Inc 2016-01-04 00:08:51 2016-01-04 00:27:31 1 15.02 -73.797928 40.644985 Tarifa normal ... 0.3 50.16 00:08:51 Amarillo 00:27:31 1120.0 18.666667 Si Aeropuerto_JFK Desconocido
8 8 28282 VeriFone Inc 2016-01-04 00:08:51 2016-01-04 00:18:56 6 2.07 -74.001572 40.735943 Tarifa normal ... 0.3 13.50 00:08:51 Amarillo 00:18:56 605.0 10.083333 Si Desconocido Desconocido
9 9 28555 VeriFone Inc 2016-01-04 00:08:55 2016-01-04 00:12:33 1 0.79 -73.989029 40.721527 Tarifa normal ... 0.3 6.30 00:08:55 Amarillo 00:12:33 218.0 3.633333 No Desconocido Desconocido

10 rows × 29 columns

Ahora debemos crear un nuevo CSV que recoja los datos de nuestras zonas de interés. Debido al tipo de visualización que queremos hacer en Tableau (un mapa que muestre rutas lineales), nuestro CSV debe tener unos determinados datos y expuestos de cierta manera para que la visualización sea efectiva.

Para saber cuales son los datos necesarios para esta visualización, nos hemos basado en este artículo de Tableau: https://onlinehelp.tableau.com/current/pro/desktop/en-us/maps_howto_origin_destination.htm

En nuestro caso, queremos poder visualizar 4 posibles casos, no sólo uno:

  • Todo el conjunto de los pickups que salen del Empire State
  • Todo el conjunto de los pickups que salen del Aeropuerto JFK
  • Todo el conjunto de los dropoffs que suceden en la zona de Empire State
  • Todo el conjunto de los dropoffs que suceden en el Aeropuerto JFK

Captura%20de%20pantalla%202018-12-01%20a%20las%2018.56.18.png Captura%20de%20pantalla%202018-12-01%20a%20las%2019.08.25.png

Así, según el artículo anterior, sabemos que por cada trayecto a tratar, se deben crear dos filas, una con las coordenadas del pickup/dropoff de la zona de interés, otra con el dropoff/pickup de ese trayecto y ambas con la ID de ruta del trayecto seguido. Como no solo queremos hacer una visualización, sino 4 posibles visualizaciones, debemos crear otras dos columnas más:

  • Una que diferencie la zona de interés a visualizar (Aeropuerto JFK o Empire State)
  • Otra que hable sobre la acción que sucede en la zona de interés (pickups o dropoffs)

Por último, añadimos una columna extra que hable sobre la distancia entre los pickups y dropoffs para después en la visualización, poder dar una diferenciación de color a la ruta según es más larga o más corta.

Todo esto nos lleva a crear el siguiente diccionario y funciones.

In [8]:
#Diccionario con los nombres de las columnas del CSV
rutas_en_zonas_de_interes = {
    'ID de ruta': [],
    'Acción': [],
    'Zona': [],
    'Latitud': [],
    'Longitud': [], 
    'Distancia': []
}

#Función primaria
def rellenarListasDelDiccionario():
    for index, _ in enumerate (data["pickup_zona_interes"]):
        #Pasamos a llamar las columnas creadas anteriormente de un modo más facil
        zonas_interes = {
            "pickup": data["pickup_zona_interes"][index],
            "dropoff": data["dropoff_zona_interes"][index]
        }
        
        #Si se da el caso de que data["pickup_zona_interes"][index] o data["dropoff_zona_interes"][index]
        #tienen el valor "Desconocido" se pasará de esos valores y se continua a la siguiente fila
        if noTieneZonaDeInteres(zonas_interes):
            continue
            
        #Para los casos en los que los valores eran "Aeropuerto_JKF" o "Empire_state" se aplica la 
        #siguiente función, tanto para pickups como para dropoffs
        crearNuevaFilaPara(index, "pickup", zonas_interes)
        crearNuevaFilaPara(index, "dropoff", zonas_interes)

#Función secundaria
def noTieneZonaDeInteres(zonas_interes):
    return zonas_interes["pickup"] == "Desconocido" and zonas_interes["dropoff"] == "Desconocido"

#Función secundaria
def crearNuevaFilaPara(index, accion, zonas_interes):
    rutas_en_zonas_de_interes['ID de ruta'].append(index)                    #Añade Index como ID de ruta
    rutas_en_zonas_de_interes['Zona'].append(obtenerZona(zonas_interes))     #Añade "Aeropuerto_JFK" o "Empire_state"
    rutas_en_zonas_de_interes['Acción'].append(obtenerAccion(zonas_interes)) #Añade "dropoff" o "pickup"
    rutas_en_zonas_de_interes['Latitud'].append(data[accion + "_latitude"][index])   #Añade la latitud del pickup o del dropoff según sea la acción
    rutas_en_zonas_de_interes['Longitud'].append(data[accion + "_longitude"][index]) #Añade la longitud del pickup o del dropoff según sea la acción
    rutas_en_zonas_de_interes['Distancia'].append(data["distancia"][index])

#Función terciaria
def obtenerZona(zonas_interes):
    return zonas_interes["dropoff"] if zonas_interes["pickup"] == "Desconocido" else zonas_interes["pickup"]

#Función terciaria
def obtenerAccion(zonas_interes):
    return "dropoff" if zonas_interes["pickup"] == "Desconocido" else "pickup"
        
#Llamada de la función primaria 
rellenarListasDelDiccionario()

#Creamos el dataframe a partir del diccionario creado y rellenado con los datos que queremos
zonas_interes = pd.DataFrame(rutas_en_zonas_de_interes)
In [9]:
#Comprobación del principio del dataframe creado
zonas_interes.head(10)
Out[9]:
ID de ruta Acción Zona Latitud Longitud Distancia
0 0 pickup Aeropuerto_JFK 40.646664 -73.790016 16.37
1 0 pickup Aeropuerto_JFK 40.750332 -73.974159 16.37
2 1 pickup Aeropuerto_JFK 40.644642 -73.782204 19.66
3 1 pickup Aeropuerto_JFK 40.697048 -73.962944 19.66
4 5 pickup Empire_state 40.746899 -73.986153 2.31
5 5 pickup Empire_state 40.722996 -74.005096 2.31
6 7 pickup Aeropuerto_JFK 40.644985 -73.797928 15.02
7 7 pickup Aeropuerto_JFK 40.726673 -73.943695 15.02
8 28 pickup Aeropuerto_JFK 40.644718 -73.782379 17.92
9 28 pickup Aeropuerto_JFK 40.721207 -73.980896 17.92
In [10]:
#Comprobación del final del dataframe creado
zonas_interes.tail(10)
Out[10]:
ID de ruta Acción Zona Latitud Longitud Distancia
45606 349677 dropoff Empire_state 40.703274 -73.994041 7.34
45607 349677 dropoff Empire_state 40.751961 -73.985703 7.34
45608 349777 dropoff Aeropuerto_JFK 40.715641 -73.960045 14.94
45609 349777 dropoff Aeropuerto_JFK 40.643497 -73.790138 14.94
45610 350434 dropoff Aeropuerto_JFK 40.732624 -73.861694 9.20
45611 350434 dropoff Aeropuerto_JFK 40.643547 -73.790215 9.20
45612 350664 dropoff Aeropuerto_JFK 40.746216 -73.890152 14.15
45613 350664 dropoff Aeropuerto_JFK 40.641705 -73.786942 14.15
45614 352350 dropoff Empire_state 40.723717 -73.950775 7.69
45615 352350 dropoff Empire_state 40.748047 -73.988823 7.69

Una vez terminado el proceso, guardamos este nuevo dataframe en un nuevo csv.

In [12]:
zonas_interes.to_csv("zonas_interes.csv", sep=";", decimal=',', encoding='utf-8')