Imports y funciones

imports

In [1]:
import pandas as pd
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import geopandas as gpd
import requests
import os

from bokeh.io import show, output_notebook, save, output_file
from bokeh.models import GeoJSONDataSource, HoverTool, LinearColorMapper, ColumnDataSource, ColorBar, BasicTicker
from bokeh.plotting import figure
from bokeh.palettes import Viridis256, all_palettes, viridis
from bokeh.tile_providers import get_provider, Vendors
from bokeh.embed import file_html

from random import randint
from cartopy import crs as ccrs
os.environ['BOKEH_PHANTOMJS_PATH'] = 'D:/Descargas/phantomjs-2.1.1-windows/bin/phantomjs.exe'
from shapely.geometry import Point

#import plotly.plotly as py
import plotly.offline as py 
import plotly.graph_objs as go
from plotly.io import to_html, write_image

import datashader as ds
import holoviews as hv
from colorcet import fire
from datashader.bundling import directly_connect_edges, hammer_bundle
from datashader.utils import export_image

from holoviews.operation.datashader import aggregate, shade, datashade, dynspread, rasterize
from holoviews.operation import decimate
from holoviews import opts
import geoviews as gv
import geoviews.tile_sources as gts
import plotly_express as px
import imageio
hv.notebook_extension('bokeh', 'matplotlib')

from dask.distributed import Client
client = Client()
decimate.max_samples=20000
dynspread.threshold=0.01
datashade.cmap=fire[40:]
sz = dict(width=150,height=150)


px.set_mapbox_access_token(open("credenciales/mapbox").read())
print(pd.__version__)
print(nx.__version__)
print(gpd.__version__)
print(ds.__version__)
print(hv.__version__)
print(gv.__version__)
%matplotlib inline
output_notebook()
py.init_notebook_mode()
0.24.2
2.3
0.5.0
0.7.0
1.12.1
1.6.2
Loading BokehJS ...

Globals

In [2]:
dir_datos = 'datos'
dir_datos_d = 'D:/Datos/Ecobici'
dias = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo']
meses = ["Enero", "Febrero", "Marzo", "Abril", "Mayo","Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]
labels_edad = ['Hasta 20', 'Entre 21 y 30', 'Entre 31 y 40', 'Entre 41 y 50', 'Entre 51 y 60', 'Más de 60']

Funciones

In [3]:
def getPolyCoords(row, geom, coord_type):
    """Returns the coordinates ('x' or 'y') of edges of a Polygon exterior"""

    # Parse the exterior of the coordinate
    exterior = row[geom].exterior

    if coord_type == 'x':
        # Get the x coordinates of the exterior
        return list( exterior.coords.xy[0] )
    elif coord_type == 'y':
        # Get the y coordinates of the exterior
        return list( exterior.coords.xy[1] )

Leyendo datos

In [5]:
# Viajes entre estaciones
df = pd.read_pickle(f'{dir_datos_d}/viajes_2019.pkl')\
    .rename(columns={'Ciclo_Estacion_Arribo': 'est_arribo',
                     'Ciclo_Estacion_Retiro': 'est_retiro',
                    'Edad_Usuario': 'edad',
                    'Genero_Usuario': 'sexo'})\
    .assign(est_arribo=lambda x: x['est_arribo'].pipe(pd.to_numeric, errors='coerce'))\
    .dropna(subset=['est_arribo'])\
    .astype({'est_arribo': int})\

df['date_retiro'] = df['time_retiro'].dt.date.pipe(pd.to_datetime)
df['date_arribo'] = df['time_arribo'].dt.date.pipe(pd.to_datetime)
# Mapa Colonias
cdmx = gpd.read_file(f'{dir_datos}/coloniascdmx.geojson')\
    .astype({'id': int})\
    .rename(columns={'id': 'id_colonia'})\
    .pipe(gpd.GeoDataFrame)
est_geo = gpd.read_file(f'{dir_datos}/estaciones_ecobici.geojson')\
    .rename(columns={'id': 'id_est'})
cdmx.crs = est_geo.crs
estaciones = gpd.sjoin(est_geo, cdmx, op='within', how='left')
dicc_nom_est = estaciones.set_index('id_est')['name'].to_dict()
In [5]:
#df_curso = df[['Bici', 'sexo', 'edad', 'grupo_edad', 'est_retiro', 'est_arribo', 'time_retiro', 'time_arribo', 'time', 'mes', 'dia', 'hora', 'momento']].sample(n=100000)
#df_curso.to_csv('C:/Users/lnppcide04/Dropbox (LNPP)/Otros/Talleres UCD/Escuela de metodos 2019/sesion_5/datos/ecobici.csv', index=False)

Gráficas 😁📉📊

Total de viajes

In [5]:
print('El número total de viajes es', df.shape[0])
El número total de viajes es 6396089
  • Por mes
In [10]:
df.groupby('mes')['Bici'].count()
Out[10]:
mes
Abril         690044
Agosto        748679
Diciembre         28
Enero         697343
Febrero       684091
Julio         703292
Junio         695694
Marzo         752236
Mayo          750910
Noviembre          2
Octubre            3
Septiembre    673766
Name: Bici, dtype: int64
In [ ]:
viajes_mes = px.bar(mes_sexo_dia,  x="mes", y="Bici", color="sexo", orientation="v",
           labels={'sexo': 'Sexo', 'Bici': 'Viajes'},
           category_orders={'mes': meses[0:9]},
           title='Viajes por Mes, según sexo')
viajes_mes.update(data=[{'textfont': {'size': 1, 'color': '#636efa'},}])

with open('concurso/introduccion/viajes_por_mes.html', 'w') as htmlfile:
    htmlfile.write(to_html(viajes_mes))
viajes_mes

Viajes por sexo y edad

Datos

In [6]:
df_sex_edad = df.groupby(['sexo', 'grupo_edad', 'edad'], as_index=False)[['Bici']].count()
df_piramide = df_sex_edad.query('edad<=80')\
    .groupby(['sexo', 'edad']).sum()\
    .unstack(level='sexo')['Bici']\
    .assign(F=lambda x: -1*x['F'])\
    .reset_index()
In [22]:
df_sex_edad.groupby('edad').Bici.sum()#/df_sex_edad.Bici.sum()
Out[22]:
edad
16       3362.0
17       7424.0
18      18585.0
19      34401.0
20      44152.0
21      70764.0
22     103009.0
23     135687.0
24     202124.0
25     275656.0
26     323395.0
27     333779.0
28     350997.0
29     361154.0
30     341996.0
31     318636.0
32     296600.0
33     257568.0
34     240897.0
35     233005.0
36     213041.0
37     180263.0
38     180304.0
39     159851.0
40     139510.0
41     123865.0
42     108580.0
43      99185.0
44      96718.0
45      92056.0
         ...   
61      23290.0
62      21317.0
63      19123.0
64      18158.0
65      18520.0
66      15283.0
67       9694.0
68       9548.0
69       6366.0
70       4774.0
71       4224.0
72       4523.0
73       2926.0
74       3295.0
75        901.0
76        954.0
77       1387.0
78        996.0
79        218.0
80         70.0
81         13.0
82         83.0
83         33.0
84         36.0
92          3.0
96          8.0
97         15.0
108         1.0
118         3.0
119        11.0
Name: Bici, Length: 75, dtype: float64
In [43]:
prop_muj = df_piramide.abs().assign(prop=lambda x: 100*x['F']/(x['F']+x['M'])).astype({'edad': int})\

viajes_mujeres = px.bar(prop_muj,  x="edad", y="prop", orientation="v",
           labels={'edad': 'Edad', 'prop': 'Porcentaje'},
           title='Proporción de viajes realizados por mujeres, 2019')
viajes_mujeres.update(data=[{'textfont': {'size': 1, 'color': '#636efa'},}])

with open('concurso/introduccion/proporcion_mujeres.html', 'w') as htmlfile:
    htmlfile.write(to_html(viajes_mujeres))
viajes_mujeres

Barras horizontal

In [21]:
plot_sexo_edad_barra = px.bar(df_sex_edad, x="Bici", y="sexo", orientation="h", color="grupo_edad", labels={'sexo': 'Sexo', 'Bici': 'Viajes', 'grupo_edad': 'Grupo etario'},
      category_orders={'grupo_edad': labels_edad}, text='edad',
      title='Viajes por sexo y grupo etario',)

with open('concurso/introduccion/viajes_por_sexo_edad_barras.html', 'w') as htmlfile:
    htmlfile.write(to_html(plot_sexo_edad_barra))
    
plot_sexo_edad_barra

Piramide

In [26]:
minrange, maxrange, steprange = -200000, 400000, 50000
layout = go.Layout(title='Viajes por sexo y edad',
                    yaxis=go.layout.YAxis(title='Edad'),
                   xaxis=go.layout.XAxis(
                       range=[minrange, maxrange],
                       tickvals=list(range(minrange, maxrange, steprange)),
                       ticktext=list(map(abs, list(range(minrange, maxrange, steprange)))),
                       title='Viajes'),
                   barmode='overlay',
                   bargap=0.1)

data = [go.Bar(y=df_piramide['edad'],
               x=df_piramide['M'],
               orientation='h',
               name='Hombres',
               hovertemplate='viajes: %{x}, edad: %{y}'
               ),
        go.Bar(y=df_piramide['edad'],
               x=df_piramide['F'],
               orientation='h',
               name='Mujeres',
               text=-1 * df_piramide['F'].astype('int'),
               hovertemplate='viajes: %{text}, edad: %{y}',
               marker=dict(color='seagreen')
               )]

plot_sexo_edad_piramide = dict(data=data, layout=layout)

with open('concurso/introduccion/viajes_por_sexo_edad_piramide.html', 'w') as htmlfile:
    htmlfile.write(to_html(plot_sexo_edad_piramide))

py.iplot(plot_sexo_edad_piramide)

Estadísticas por colonia (Viajes, proporción de mujeres, edad promedio, tiempo promedio, O-D más populares)

Datos

In [5]:
OD_colonias = df[['est_retiro', 'est_arribo', 'Bici', 'sexo', 'genero', 'edad', 'time']]\
    .merge(estaciones[['id_colonia', 'nombre', 'id_est']].rename(columns={'nombre': 'colonia_retiro', 'id_colonia': 'id_colonia_retiro'}), left_on='est_retiro', right_on='id_est')\
    .rename(columns={'id_est': 'id_est_retiro'})\
    .merge(estaciones[['id_colonia', 'nombre', 'id_est']].rename(columns={'nombre': 'colonia_arribo', 'id_colonia': 'id_colonia_arribo'}), left_on='est_arribo', right_on='id_est')\
    .rename(columns={'id_est': 'id_est_arribo'})\

Por colonia de retiro

In [25]:
# Por colonia de retiro
col_retiro = OD_colonias\
        .groupby(['id_colonia_retiro', 'colonia_retiro'])\
        .agg({'Bici': 'count', 'genero': 'mean', 'edad': 'mean', 'time': 'mean',
             'colonia_arribo': lambda x: ','.join(x.value_counts().iloc[0:3].index)})\
        .sort_values('Bici', ascending=False)\
        .reset_index('colonia_retiro')\
        .join(cdmx.set_index('id_colonia'), how='inner')\
        .pipe(gpd.GeoDataFrame)
geosrc_arribo = GeoJSONDataSource(geojson=col_retiro.to_json())
#col_retiro.to_file('datos/colonias_ecobici/colonias_ecobici.shp')
cmap = LinearColorMapper(palette=Viridis256[::-1])
colorbar_retiro = ColorBar(color_mapper=cmap, location=(0, 0), ticker=BasicTicker())
In [26]:
hover_retiro = HoverTool(tooltips=[("viajes", "@Bici"),
                                 ("Colonia", "@colonia_retiro"),
                                 ('Edad promedio', '@edad'),
                                 ('Proporcíón mujeres', '@genero'),
                                 ('Tiempo promedio', '@time'),
                                 ('Destino más popular', '@colonia_arribo')])

tools_retiro = ['pan', 'wheel_zoom', 'reset', 'save', hover_retiro]
titulo = 'Viajes de Ecobici por colonia de retiro'
p_retiro = figure(title=titulo, tools=tools_retiro, 
           plot_width=600, plot_height=600, match_aspect=True)

p_retiro.patches('xs', 'ys', fill_alpha=0.7, fill_color={'field': 'Bici', 'transform': cmap},
         line_color='black', line_width=0.5, source=geosrc_arribo)
p_retiro.add_layout(colorbar_retiro, 'right')

output_file(f"concurso/introduccion/{titulo}.html", title=titulo)
show(p_retiro)
In [27]:
col_retiro.sort_values('Bici', ascending=False).head(10)
Out[27]:
colonia_retiro Bici genero edad time colonia_arribo control entidad oid clasificac geometry1 nombre cp nom_mun municipio otros_cp geometry
2269 ROMA NORTE 744860 0.283315 35.240931 13.508880 ROMA NORTE,JUAREZ,HIPODROMO 2285.0 9 2258 1 1=X=ou ROMA NORTE 6700.0 Cuauhtémoc 15 None POLYGON ((-99.17656348049771 19.42032859925793...
785 CUAUHTEMOC 531974 0.247119 35.703925 14.569975 CUAUHTEMOC,JUAREZ,ROMA NORTE 785.0 9 779 1 1=X=ou CUAUHTEMOC 6500.0 Cuauhtémoc 15 None POLYGON ((-99.16943016374846 19.43616698327909...
626 POLANCO 525245 0.259262 34.618858 16.041466 POLANCO,JUAREZ,GRANADA 626.0 9 620 1 1=X=os POLANCO 11550.0 Miguel Hidalgo 16 11560 Y 11570 POLYGON ((-99.21397076433631 19.43882430532312...
786 JUAREZ 515760 0.229087 35.080359 15.149995 JUAREZ,CUAUHTEMOC,ROMA NORTE 786.0 9 780 1 1=X=ou JUAREZ 6600.0 Cuauhtémoc 15 None POLYGON ((-99.17556412572701 19.42322687511553...
788 HIPODROMO 379711 0.308564 35.139822 12.763298 HIPODROMO,ROMA NORTE,CONDESA 788.0 9 782 1 1=X=ou HIPODROMO 6100.0 Cuauhtémoc 15 None POLYGON ((-99.16895837633354 19.41639769182621...
234 CENTRO 351574 0.204267 36.726408 17.043363 CENTRO,JUAREZ,CUAUHTEMOC 234.0 9 234 1 1=X=N CENTRO 6000.0 Cuauhtémoc 15 6010,6020,6040,6050,6070,60800 POLYGON ((-99.13876003901495 19.4439645478467,...
2290 ROMA SUR 256217 0.289290 35.567706 13.076567 ROMA NORTE,ROMA SUR,HIPODROMO 2306.0 9 2279 1 1=X=ou ROMA SUR 6760.0 Cuauhtémoc 15 None POLYGON ((-99.1669729212408 19.4104761258883, ...
787 CONDESA 220916 0.308280 35.627610 12.933696 ROMA NORTE,HIPODROMO,CONDESA 787.0 9 781 1 1=X=ou CONDESA 6140.0 Cuauhtémoc 15 None POLYGON ((-99.17662426678095 19.42027385085621...
231 BUENAVISTA 213960 0.166120 34.371499 16.551873 CUAUHTEMOC,JUAREZ,TABACALERA 231.0 9 231 1 1=X=os BUENAVISTA 6350.0 Cuauhtémoc 15 None POLYGON ((-99.15181101940688 19.45402616737206...
646 ESCANDON 200490 0.278378 34.504379 13.721493 ESCANDON,HIPODROMO,ROMA NORTE 646.0 9 640 1 1=X=ouI ESCANDON 11800.0 Miguel Hidalgo 16 None POLYGON ((-99.18429169928805 19.40773912500221...

Por Colonia de arribo

In [18]:
# Por colonia de arribo
col_arribo = OD_colonias\
        .groupby(['id_colonia_arribo', 'colonia_arribo'])\
        .agg({'Bici': 'count', 'genero': 'mean', 'edad': 'mean', 'time':'mean',
              'colonia_retiro': lambda x: ','.join(x.value_counts().iloc[0:3].index)})\
        .sort_values('Bici', ascending=False)\
        .reset_index('colonia_arribo')\
        .join(cdmx.set_index('id_colonia'), how='inner')\
        .pipe(gpd.GeoDataFrame)
geosrc_arribo = GeoJSONDataSource(geojson=col_arribo.to_json())
cmap = LinearColorMapper(palette=Viridis256[::-1])
colorbar_arribo = ColorBar(color_mapper=cmap, location=(0, 0), ticker=BasicTicker())
In [41]:
hover_arribo = HoverTool(tooltips=[("viajes", "@Bici"),
                                 ("Colonia", "@colonia_arribo"),
                                 ('Edad promedio', '@edad'),
                                 ('Proporcíón mujeres', '@genero'),
                                 ('Tiempo promedio', '@time'),
                                 ('Origen más popular', '@colonia_retiro')])

tools_arribo = ['pan', 'wheel_zoom', 'reset', 'save', hover_arribo]
titulo = 'Viajes de Ecobici por colonia de arribo'
p_arribo = figure(title=titulo, tools=tools_arribo, 
           plot_width=600, plot_height=600, match_aspect=True)

p_arribo.patches('xs', 'ys', fill_alpha=0.7, fill_color={'field': 'Bici', 'transform': cmap},
         line_color='black', line_width=0.5, source=geosrc_arribo)
p_arribo.add_layout(colorbar_arribo, 'right')


output_file(f"concurso/introduccion/{titulo}.html", title=titulo)
show(p_arribo)
In [23]:
col_arribo.sort_values('Bici', ascending=False).head(10)
Out[23]:
colonia_arribo Bici genero edad time colonia_retiro control entidad oid clasificac geometry1 nombre cp nom_mun municipio otros_cp geometry
2269 ROMA NORTE 783422 0.287043 35.118563 13.126944 ROMA NORTE,HIPODROMO,JUAREZ 2285.0 9 2258 1 1=X=ou ROMA NORTE 6700.0 Cuauhtémoc 15 None POLYGON ((-99.17656348049771 19.42032859925793...
785 CUAUHTEMOC 530933 0.244311 35.636291 13.681451 CUAUHTEMOC,JUAREZ,BUENAVISTA 785.0 9 779 1 1=X=ou CUAUHTEMOC 6500.0 Cuauhtémoc 15 None POLYGON ((-99.16943016374846 19.43616698327909...
786 JUAREZ 523234 0.233314 34.903051 14.804293 JUAREZ,CUAUHTEMOC,ROMA NORTE 786.0 9 780 1 1=X=ou JUAREZ 6600.0 Cuauhtémoc 15 None POLYGON ((-99.17556412572701 19.42322687511553...
626 POLANCO 459029 0.253267 34.878210 14.612089 POLANCO,GRANADA,CUAUHTEMOC 626.0 9 620 1 1=X=os POLANCO 11550.0 Miguel Hidalgo 16 11560 Y 11570 POLYGON ((-99.21397076433631 19.43882430532312...
234 CENTRO 398457 0.213067 36.674708 18.363503 CENTRO,JUAREZ,CUAUHTEMOC 234.0 9 234 1 1=X=N CENTRO 6000.0 Cuauhtémoc 15 6010,6020,6040,6050,6070,60800 POLYGON ((-99.13876003901495 19.4439645478467,...
788 HIPODROMO 387436 0.313298 35.120448 12.602643 HIPODROMO,ROMA NORTE,CONDESA 788.0 9 782 1 1=X=ou HIPODROMO 6100.0 Cuauhtémoc 15 None POLYGON ((-99.16895837633354 19.41639769182621...
2290 ROMA SUR 263785 0.283469 35.593362 13.513315 ROMA NORTE,ROMA SUR,HIPODROMO 2306.0 9 2279 1 1=X=ou ROMA SUR 6760.0 Cuauhtémoc 15 None POLYGON ((-99.1669729212408 19.4104761258883, ...
231 BUENAVISTA 257605 0.187656 34.179445 19.456109 CUAUHTEMOC,JUAREZ,CENTRO 231.0 9 231 1 1=X=os BUENAVISTA 6350.0 Cuauhtémoc 15 None POLYGON ((-99.15181101940688 19.45402616737206...
787 CONDESA 226278 0.307056 35.473537 12.500823 ROMA NORTE,HIPODROMO,CONDESA 787.0 9 781 1 1=X=ou CONDESA 6140.0 Cuauhtémoc 15 None POLYGON ((-99.17662426678095 19.42027385085621...
646 ESCANDON 189661 0.276683 34.575469 14.159175 ESCANDON,HIPODROMO,ROMA NORTE 646.0 9 640 1 1=X=ouI ESCANDON 11800.0 Miguel Hidalgo 16 None POLYGON ((-99.18429169928805 19.40773912500221...

Viajes entre colonias

In [28]:
entre_colonias = OD_colonias.groupby(['colonia_retiro', 'colonia_arribo'])[['Bici']].count()\
    .unstack(level='colonia_retiro')['Bici']
In [34]:
pct = [x/100 for x in range(0, 100, 3)] + [1]
decil = list(np.nanquantile(entre_colonias.values, q=pct)/np.nanmax(entre_colonias.values))
decil[0] = 0
paleta = viridis(len(pct))[::-1]
xtitle = 'Colonia de destino'
ytitle = 'Colonia de origen'
color = [y for x in [[[decil[i], c], [decil[i+1], c]] for i, c in zip(range(len(decil)-1), paleta)] for y in x]
trace = go.Heatmap(z=entre_colonias.values.tolist(),
                   x=entre_colonias.index.tolist(),
                   y=entre_colonias.keys().tolist(),
                  colorscale=color,
                  )
                  #hovertemplate='Origen: %{y} <br> destino: %{x} <br> Viajes %{z}')

data=[trace]
layout = go.Layout(
    title='Viajes entre colonias',
    height=1200,
    yaxis =go.layout.YAxis(
        title=ytitle,
        automargin=True,
    ),
    xaxis =go.layout.XAxis(
        title=xtitle,
            automargin=True,
    )  

)
fig = go.Figure(data=data, layout=layout)

#with open('concurso/introduccion/heatmap_viajes_entre_colonias.html', 'w') as htmlfile:
    #htmlfile.write(to_html(fig))

py.iplot(fig, filename='heatmap')
In [48]:
entre_colonias.unstack().to_frame(name='viajes').sort_values('viajes', ascending=False).query('colonia_retiro!=colonia_arribo')
Out[48]:
viajes
colonia_retiro colonia_arribo
CUAUHTEMOC JUAREZ 76859.0
JUAREZ CUAUHTEMOC 73423.0
HIPODROMO ROMA NORTE 71315.0
ROMA NORTE JUAREZ 68410.0
JUAREZ ROMA NORTE 67048.0
ROMA NORTE HIPODROMO 67035.0
ROMA SUR ROMA NORTE 63637.0
ROMA NORTE ROMA SUR 62018.0
JUAREZ CENTRO 56336.0
BUENAVISTA CUAUHTEMOC 50972.0
CUAUHTEMOC ROMA NORTE 49178.0
BUENAVISTA 48267.0
CONDESA ROMA NORTE 47964.0
ROMA NORTE CONDESA 47369.0
JUAREZ BUENAVISTA 46223.0
HIPODROMO CONDESA 42952.0
ROMA NORTE CUAUHTEMOC 42049.0
CONDESA HIPODROMO 41985.0
CUAUHTEMOC CENTRO 41545.0
SAN RAFAEL CUAUHTEMOC 40298.0
CENTRO JUAREZ 37949.0
CUAUHTEMOC 37059.0
CUAUHTEMOC SAN RAFAEL 36719.0
POLANCO JUAREZ 33505.0
GRANADA 32780.0
BUENAVISTA JUAREZ 31403.0
TABACALERA CUAUHTEMOC 31053.0
GRANADA POLANCO 29249.0
ROMA NORTE CENTRO 29058.0
POLANCO ROMA NORTE 27607.0
... ... ...
NOCHE BUENA BOSQUE DE CHAPULTEPEC II NaN
CUAUHTEMOC PENSIL NaN
IRRIGACION NaN
RINCON DEL BOSQUE NaN
NONOALCO ANAHUAC DOS LAGOS NaN
CUAUHTEMOC PENSIL NaN
PIEDAD NARVARTE ANAHUAC DOS LAGOS NaN
PORTALES NORTE ANAHUAC DOS LAGOS NaN
ANAHUAC MARIANO ESCOBEDO NaN
RINCON DEL BOSQUE NaN
RESIDENCIAL EMPERADORES ANAHUAC DOS LAGOS NaN
ANAHUAC LOS MANZANOS NaN
ANAHUAC MARIANO ESCOBEDO NaN
BOSQUE DE CHAPULTEPEC II NaN
CUAUHTEMOC PENSIL NaN
IRRIGACION NaN
LOMAS DE CHAPULTEPEC NaN
RINCON DEL BOSQUE NaN
RINCON DEL BOSQUE XOCO NaN
STA CRUZ ATOYAC ANAHUAC DOS LAGOS NaN
ANAHUAC MARIANO ESCOBEDO NaN
CUAUHTEMOC PENSIL NaN
TLACOQUEMECATL DEL VALLE ANAHUAC DOS LAGOS NaN
CUAUHTEMOC PENSIL NaN
XOCO ANAHUAC NaN
ANAHUAC DOS LAGOS NaN
ANAHUAC LOS MANZANOS NaN
ANAHUAC MARIANO ESCOBEDO NaN
BOSQUE DE CHAPULTEPEC II NaN
CUAUHTEMOC PENSIL NaN

2550 rows × 1 columns

In [45]:
1392456.0/6395454.0
Out[45]:
0.2177259034307807

Viajes por Día-Hora

Datos

In [49]:
dia_hora = df.groupby(['dia', 'hora'])[['Bici']].count()\
    .unstack(level='hora')['Bici']\
    .loc[reversed(dias)]\
    .drop([3, 4], axis=1)\
    .rename(columns=lambda x: f'{x:.0f} a {x+1:.0f}')\
    .loc[:, '5 a 6': '23 a 24']

Heatmap Día hora

In [53]:
trace = go.Heatmap(z=dia_hora.values.tolist(),
                   x=dia_hora.keys().tolist(),
                   y=dia_hora.index.tolist(),
                  hovertemplate='%{y} de %{x}: %{z} viajes',
                  colorscale='Viridis',
                  reversescale=True)

data=[trace]
layout = go.Layout(
    title='Viajes por día y hora',
    yaxis =go.layout.YAxis(
        automargin=True,
    ),
    xaxis =go.layout.XAxis(
        title='Hora',
            automargin=True,
    )  

)
fig = go.Figure(data=data, layout=layout)

with open('concurso/introduccion/heatmap_viajes_por_dia_hora.html', 'w') as htmlfile:
    htmlfile.write(to_html(fig))

py.iplot(fig)

Línea Sexo y hora

In [37]:
sexo_hora = df.groupby(['sexo', 'hora'])[['Bici']].count()\
    .pipe(lambda y: 100*y/y.groupby(level='sexo').sum())\
    .reset_index()
In [38]:
px.line(sexo_hora, x="hora", y="Bici", color="sexo", line_group="sexo", line_shape="spline",
        title='Porcentaje de viajes, por hora del día y sexo', labels={'Bici': 'Porcentaje del total de viajes'})

Barras sexo, día, edad

In [39]:
sexo_edad_dia = df.groupby(['sexo', 'grupo_edad', 'dia'], as_index=False)[['Bici']].count()\
    .assign(texto=lambda x: x.apply(lambda x: f'Sexo: {x["sexo"]} <br> Grupo edad: {x["grupo_edad"]} <br> Día: {x["dia"]} <br> Viajes: {x["Bici"]}', axis=1))
sexo_edad_dia.head()
Out[39]:
sexo grupo_edad dia Bici texto
0 F Hasta 20 Domingo 2209 Sexo: F <br> Grupo edad: Hasta 20 <br> Día: Do...
1 F Hasta 20 Jueves 4211 Sexo: F <br> Grupo edad: Hasta 20 <br> Día: Ju...
2 F Hasta 20 Lunes 4261 Sexo: F <br> Grupo edad: Hasta 20 <br> Día: Lu...
3 F Hasta 20 Martes 4475 Sexo: F <br> Grupo edad: Hasta 20 <br> Día: Ma...
4 F Hasta 20 Miércoles 4384 Sexo: F <br> Grupo edad: Hasta 20 <br> Día: Mi...
In [40]:
b = px.bar(sexo_edad_dia,  x="Bici", y="dia", color="sexo", orientation="h",
       labels={'sexo': 'Sexo', 'Bici': 'Viajes', 'grupo_edad': 'Grupo etario'},
      category_orders={"dia": dias}, text='grupo_edad', title='Viajes por día de la semana, según sexo y grupo de edad')
b.update(data=[{'textfont': {'size': 1, 'color': '#636efa'},}])

Barras por mes, color hombre-mujer

In [106]:
mes_sexo_dia = df.groupby(['mes', 'sexo'], as_index=False)[['Bici']].count()\
    .query('mes!=["Diciembre", "Noviembre", "Octubre"]')
mes_sexo_dia.head()
Out[106]:
mes sexo Bici
0 Abril F 175397
1 Abril M 514647
2 Agosto F 188360
3 Agosto M 560319
6 Enero F 174498
In [110]:
viajes_mes = px.bar(mes_sexo_dia,  x="mes", y="Bici", color="sexo", orientation="v",
           labels={'sexo': 'Sexo', 'Bici': 'Viajes'},
           category_orders={'mes': meses[0:9]},
           title='Viajes por Mes, según sexo')
viajes_mes.update(data=[{'textfont': {'size': 1, 'color': '#636efa'},}])

with open('concurso/introduccion/viajes_por_mes.html', 'w') as htmlfile:
    htmlfile.write(to_html(viajes_mes))
viajes_mes

Duración promedio del viaje

Datos

In [74]:
time_prom = df.groupby(['dia', 'sexo', 'hora'], as_index=False)[['time']].mean()\
    .query('hora>=5')\
    .astype({'hora': int})
time_prom.head()
Out[74]:
dia sexo hora time
4 Domingo F 5 13.661878
5 Domingo F 6 14.325130
6 Domingo F 7 15.245600
7 Domingo F 8 19.907612
8 Domingo F 9 20.696617

Duración promedio por sexo, día de la semana y hora

In [75]:
duracion = px.bar(time_prom,  x="dia", y="time", color="sexo", orientation="v", barmode='group', animation_frame='hora',
       category_orders={'dia': dias}, labels={'time': 'Tiempo promedio'},
       title='Tiempo promedio de viaje, por sexo, día de la semana y hora')

with open('concurso/introduccion/duracion_viajes_por_dia_hora.html', 'w') as htmlfile:
    htmlfile.write(to_html(duracion))

duracion

Crea GIF

In [81]:
folder_gif = 'concurso/introduccion/duracion'
images = [imageio.imread(f'{folder_gif}/{h}.png') for h in range(5, 24)]
imageio.mimsave(f'{folder_gif}/duracion_viajes_x_dia_hora.gif', images,  duration=0.5)

Viajes por estación

Datos

In [72]:
viajes_est_ret = df.groupby(['est_retiro', 'hora'])[['Bici']].count()\
        .join(estaciones.rename(columns={'id_est': 'est_retiro'})
              .set_index('est_retiro')[['nombre', 'location_lat', 'location_lon', 'nom_mun']], how='inner')\
        .reset_index()\
        .query('hora>=5')\
        .astype({'hora': int})
        
viajes_est_ret.head()
viajes_est_arr = df.groupby(['est_arribo', 'hora'])[['Bici']].count()\
        .join(estaciones.rename(columns={'id_est': 'est_arribo'})
              .set_index('est_arribo')[['nombre', 'location_lat', 'location_lon', 'nom_mun']], how='inner')\
        .reset_index()\
        .query('hora>=5')\
        .astype({'hora': int})
viajes_est_arr.head()
Out[72]:
est_arribo hora Bici nombre location_lat location_lon nom_mun
5 1 5 199 CUAUHTEMOC 19.433571 -99.167809 Cuauhtémoc
6 1 6 843 CUAUHTEMOC 19.433571 -99.167809 Cuauhtémoc
7 1 7 2962 CUAUHTEMOC 19.433571 -99.167809 Cuauhtémoc
8 1 8 6451 CUAUHTEMOC 19.433571 -99.167809 Cuauhtémoc
9 1 9 3912 CUAUHTEMOC 19.433571 -99.167809 Cuauhtémoc
In [86]:
 
Out[86]:
viajes
est_arribo
266 AV. JESÚS GARCIA-CARLOS J. MENESES 71388
27 REFORMA-HAVRE 58411
1 RIO SENA-RIO BALSAS 58384
271 AV. CENTRAL-J. MENESES 47767
182 PROGRESO-ASTRONOMOS 46762
64 SONORA-AMSTERDAM 45505
267 AV. JESÜS GARCÏA-CARLOS J. MENESES 45477
43 JUAREZ-REVILLAGIGEDO 44542
36 PUEBLA-VERACRUZ 43655
18 REFORMA-RIO RHIN 41822
28 TOLEDO-TOKIO 37821
14 REFORMA-RIO DE PLATA 37707
38 COZUMEL-PUEBLA 36532
47 GLORIETA CIBELES-OAXACA 36487
174 JOAQUÍN GARCIA-IGNACIO MANUEL ALTAMIRANO 36416
136 ÁLVARO OBREGÓN-TONALÁ 35306
41 REFORMA-AV. DE LA REPUBLICA 35156
15 REFORMA-RIO MISSISSIPPI 35131
217 EULER-AV. HORACIO 34638
24 REFORMA-VARSOVIA 34410
141 ZACATECAS-LUIS CABRERA 34169
134 ÁLVARO OBREGÓN-ORIZABA 32792
7 RIO ELBA-RIO LERMA 32068
74 NUEVO LEON-OZULAMA 31988
146 COAHUILA-MANZANILLO 31066
16 REFORMA-RÍO TIBER 31013
23 REFORMA-PRAGA 29900
270 DE LA REPUBLICA-PONCIANO ARRIAGA 29830
295 ANAXÁGORAS-EJE 4 SUR XOLA 29810
32 LONDRES-SEVILLA 28873
... ...
378 MIGUEL LAURENT-AMORES 3514
438 ADOLFO PRIETO-JOSÉ MARÍA OLLOQUI 3467
101 1ER. CALLEJÓN DE MESONES-MESONES 3451
311 CALLE 5-AVENIDA REVOLUCIÓN 3399
330 CALLE 15-AVENIDA PATRIOTISMO 3371
343 ENRIQUE RÉBSAMEN-DIVISIÓN DEL NORTE 3331
344 HERIBERTO FRÍAS-ANGEL URRAZA EJE 6 SUR 3182
383 LUIS CARACCI-2DA. LUIS CARACCI 3180
362 AUGUSTO RODIN-ALBERTO BALDERAS 3150
364 BOTICHELLI-CDA. DE GIORGONE 3110
418 GALICIA-ACTIPAN 3019
350 JOSE CLEMENTE OROZCO-CORREGGIO 3003
429 PIRINEOS-AV. POPOCATEPETL (EJE 8) 2979
407 PROL. XOCHICALCO-GENERAL EMILIANO ZAPATA 2933
456 ALPES-MONTES ESCANDINAVOS 2907
353 CAROLINA-HOLBEIN 2886
417 GOYA-AUGUSTO RODÍN 2795
367 BOSTON -AUGUSTO RODÍN 2761
376 PITÁGORAS-AV. UNIVERSIDAD 2690
422 BUFALO-JOSÉ MARÍA RICO (EJE 8) 2545
382 SAN LORENZO-AV. INSURGENTES 2539
E445 RIFF-AVENIDA RIO CHURUBUSCO 2473
406 PARROQUIA AV. MEXICO-COYOACAN 2398
E457 AV. PRADO NORTE-EXPLANADA 2326
E469 BAHÍA DE LA CONCEPCIÓN-BAHÍA DE TODOS LOS SAN 2128
388 VALENCIA-MALAGA 1691
403 UXMAL-MUNICIPIO LIBRE 1607
441 PUENTE XOCO-REAL DE MAYORAZGO 1399
410 PROLONGACIÓN TAJÍN-PROLONGACIÓN REPÚBLICAS 1068
411 PARROQUIA-AVENIDA MÉXICO-COYOACAN 889

479 rows × 1 columns

Por estación de retiro

In [100]:
plot_est_ret = px.scatter_mapbox(viajes_est_ret, lat="location_lat", lon="location_lon", color="nom_mun", size="Bici", animation_frame='hora',
                  color_continuous_scale=px.colors.cyclical.IceFire, hover_name='est_retiro', size_max=50, zoom=12, height=850,
                  labels={'Bici': 'Viajes', 'nom_mun': 'Alcaldía'}, title='Viajes por estación de retiro, por hora del día')
with open('concurso/introduccion/viajes_por_estacion_retiro_x_hora_2019.html', 'w') as htmlfile:
    htmlfile.write(to_html(plot_est_ret))
plot_est_ret

Crea GIF

In [103]:
folder_gif = 'concurso/introduccion/por_estacion_retiro/'
images = [imageio.imread(f'{folder_gif}/{h}.png') for h in range(5, 24)]
imageio.mimsave(f'{folder_gif}/viajes_x_estacion_retiro.gif', images,  duration=0.5)

Por estación de arribo

In [101]:
# https://rawcdn.githack.com/jjsantos01/datosecobici/46e312fc9c01b81e2e4b9001d36f01cbe7532453/viajes_por_estacion_arribo_x_hora.html
plot_est_arr = px.scatter_mapbox(viajes_est_arr, lat="location_lat", lon="location_lon", color="nom_mun", size="Bici", animation_frame='hora',
                  color_continuous_scale=px.colors.cyclical.IceFire, hover_name='est_arribo', size_max=50, zoom=12, height=850,
                  labels={'Bici': 'Viajes', 'nom_mun': 'Alcaldía'}, title='Viajes por estación de arribo, por hora del día')
with open('concurso/introduccion/viajes_por_estacion_arribo_x_hora_2019.html', 'w') as htmlfile:
    htmlfile.write(to_html(plot_est_arr))
plot_est_arr

Crea GIF

In [102]:
folder_gif = 'concurso/introduccion/por_estacion_arribo/'
images = [imageio.imread(f'{folder_gif}/{h}.png') for h in range(5, 24)]
imageio.mimsave(f'{folder_gif}/viajes_x_estacion_arribo.gif', images,  duration=0.5)

Rutas más frecuentes

In [95]:
rutas_frecuentes = df.assign(Retiro=lambda x:x['est_retiro'].map(dicc_nom_est),
         Arribo=lambda x:x['est_arribo'].map(dicc_nom_est))\
    .groupby(['Retiro', 'Arribo'], as_index=False)[['Bici']].count().sort_values('Bici', ascending=False)\
    .rename(columns={'Bici': 'Viajes'})
    
rutas_frecuentes.to_excel('concurso/dashboard/intro/ritas_frecuentes.xlsx', index=False)
Out[95]:
Retiro Arribo Viajes
31603 18 REFORMA-RIO RHIN 1 RIO SENA-RIO BALSAS 3407
44945 211 NEWTON-HORACIO 217 EULER-AV. HORACIO 3126
29764 174 JOAQUÍN GARCIA-IGNACIO MANUEL ALTAMIRANO 183 GABINO BARRERA-GUILLERMO PRIETO 3116
29692 174 JOAQUÍN GARCIA-IGNACIO MANUEL ALTAMIRANO 111 GUILLERMO PRIETO-JOAQUÍN VELÁZQUEZ DE LEÓN 2959
83 1 RIO SENA-RIO BALSAS 18 REFORMA-RIO RHIN 2865
4258 111 GUILLERMO PRIETO-JOAQUÍN VELÁZQUEZ DE LEÓN 174 JOAQUÍN GARCIA-IGNACIO MANUEL ALTAMIRANO 2751
33367 183 GABINO BARRERA-GUILLERMO PRIETO 174 JOAQUÍN GARCIA-IGNACIO MANUEL ALTAMIRANO 2609
22859 158 HUATABAMPO-EJE 1PTE. CUAUHTÉMOC 141 ZACATECAS-LUIS CABRERA 2359
43495 208 HESIODO-LAMARTINE 242 MIGUEL DE CERVANTES SAAVEDRA-PROL MOLIERE 2321
25710 165 BAJÍO-EJE 2 PTE. MONTERREY 163 TOLUCA-BAJÍO 2302
In [ ]:
# Estaciones de retiro más frecuentes
viajes_est_ret.assign(est_retiro=lambda x: x['est_retiro'].map(dicc_nom_est))\
    .groupby('est_retiro')['Bici'].sum().sort_values(ascending=False)\
    .to_frame('viajes')
In [ ]:
# Estaciones de arribo más frecuentes
viajes_est_arr.assign(est_arribo=lambda x: x['est_arribo'].map(dicc_nom_est))\
    .groupby('est_arribo')['Bici'].sum().sort_values(ascending=False)\
    .to_frame('viajes')

Conexiones entre estaciones

Crea nodos y edges

In [36]:
# https://anaconda.org/jbednar/edge_bundling/notebook
edges = df.query('est_retiro<=480 & est_arribo<=480')[['est_retiro', 'est_arribo']]\
    .rename(columns={'est_retiro': 'source', 'est_arribo': 'target'})\
    .pipe(hv.Curve)
nodos = estaciones.set_index('id_est')[['location_lat', 'location_lon']]\
    .rename(columns={'location_lat': 'y', 'location_lon': 'x'})\
    .pipe(hv.Points)

Grafica todas las conexiones

In [62]:
connected_edges = directly_connect_edges(nodos.data, edges.data)
direct  = hv.Curve(connected_edges)
Out[62]:
In [174]:
red_viajes = datashade(direct)
red_viajes.opts(title=f'Viajes 2019', width=800, height=800, xaxis=None, yaxis=None, tools=['hover']) 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-174-ba4600b84d3d> in <module>()
----> 1 red_viajes = datashade(direct)
      2 red_viajes.opts(title=f'Viajes 2019', width=800, height=800, xaxis=None, yaxis=None,
      3                zlim=(1, 1000), tools=['hover']) 

NameError: name 'direct' is not defined

Crea red de viajes entre estaciones por horas

In [68]:
horas = [0] + list(range(5, 24))
l_conn_edges = []
for h in horas:
    edges = df.query('est_retiro<=480 & est_arribo<=480 & hora==@h')[['est_retiro', 'est_arribo']]\
        .rename(columns={'est_retiro': 'source', 'est_arribo': 'target'})\
        .pipe(hv.Curve)
    nodos = estaciones.set_index('id_est')[['location_lat', 'location_lon']]\
        .rename(columns={'location_lat': 'y', 'location_lon': 'x'})\
        .pipe(hv.Points)
    l_conn_edges += [directly_connect_edges(nodos.data, edges.data).assign(hora=h)]
connected_edges = pd.concat(l_conn_edges)
connected_edges.to_csv(f'{dir_datos_d}/connected_edges_2019.csv', index=False)

Lee datos de red y grafica

In [5]:
connected_edges = pd.read_csv(f'{dir_datos_d}/connected_edges_2019.csv')
connected_edges.head()
Out[5]:
x y hora
0 -99.175880 19.411797 0
1 -99.175778 19.419725 0
2 NaN NaN 0
3 -99.162729 19.417715 0
4 -99.158160 19.437200 0

Red por hora

In [6]:
%%opts RGB [width=600 height=600]
das = hv.Dataset(connected_edges, kdims=['x'], vdims=['y'])\
    .to(hv.Curve, groupby='hora', dynamic=True)
rasterized = rasterize(das).opts(clipping_colors={'NaN': (0, 0, 0, 0)})
horas = list(range(5, 24))+[0]
holo_dict = dict()
for h in horas:
    ras = rasterized.get(h).opts(
        opts.Image(title=f'Viajes entre estaciones de Ecobici a las {h}', width=600, height=500, logz=False, xaxis=None, yaxis=None,
                   tools=['hover'], colorbar=True, clim=(1, 1500), cmap='Viridis'))
    holo_dict[h] = ras
hmap = hv.HoloMap(holo_dict, kdims='hora')
# rasterized.opts(opts.Image(width=600, height=500, logz=False, xaxis=None, yaxis=None, tools=['hover'], colorbar=True, clim=(1, 1500), cmap='Viridis', bgcolor=None), ) # Dynamic
In [7]:
 gv.Dataset(connected_edges, kdims=['x', 'y'], vdims=['hora'])\
    .to(gv.TriMesh, groupby='hora', dynamic=True)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-7-ea78f54b04b4> in <module>()
----> 1 gv.Dataset(connected_edges, kdims=['x', 'y'], vdims=['hora'])   .to(gv.TriMesh, groupby='hora', dynamic=True)

c:\programdata\anaconda3\lib\site-packages\geoviews\element\__init__.py in __call__(self, *args, **kwargs)
     37                 args = (Dataset,)
     38                 kwargs['kdims'] = []
---> 39         converted = super(GeoConversion, self).__call__(*args, **kwargs)
     40         if is_gpd:
     41             if kdims is None: kdims = group_type.kdims

c:\programdata\anaconda3\lib\site-packages\holoviews\core\data\__init__.py in __call__(self, new_type, kdims, vdims, groupby, sort, **kwargs)
    124                 (max_d is not None and len(dims) > max_d)):
    125                 raise ValueError("%s %s must be between length %s and %s." %
--> 126                                  (type_name, dim_type, min_d, max_d))
    127 
    128         if groupby is None:

ValueError: TriMesh kdims must be between length 3 and 3.
In [223]:
%%opts Image [colorbar=True clipping_colors={'NaN': (0, 0, 0, 0)} clim=(1, 1500)] (cmap='viridis')
hv.Image(datashade(das).get(8))
Out[223]:
In [293]:
gv.Image(gv.operation.convert_to_geotype(rasterize(das).get(8), crs=ccrs.PlateCarree())) * gts.Wikipedia
Out[293]:
In [283]:
gv.Image(rasterize(das).get(0)).opts(colorbar=True, clim=(1, 100)) * gts.Wikipedia
Out[283]:

Mapa

In [ ]:
col_ecobici = gv.Shape.from_shapefile('datos/colonias_ecobici/colonias_ecobici.shp', crs=ccrs.PlateCarree())
In [138]:
# %%opts Points (color="red") [tools=["hover"] width=900 height=650] 
d = {h: gts.Wikipedia * gv.Image(holo_dict[h]).opts(cmap='Viridis',  clim=(1, 1500), colorbar=True)
     * col_ecobici.opts(bgcolor='black', color=None)
     * gv.Points(est_geo) for h in horas[0:1]}
hv.HoloMap(d)
Out[138]:

Crea imágenes por hora y GIF

In [74]:
for h, ras in holo_dict.items():
    hv.save(ras, f'graficas/red_x_hora/2019/{h}.png')
In [105]:
images = [imageio.imread(f'graficas/red_x_hora/2019/{h}.png') for h in range(5, 24)]
imageio.mimsave('graficas/red_x_hora/2019/red_x_hora_2019.gif', images,  duration=0.5)

Número de viajes por día CARTO

In [45]:
n_viajes_dia = df.groupby('date_retiro')[['Bici']].count().loc['2019-01-01':]
n_viajes_dia.to_csv('datos/viajes_x_dia_2019.csv')
In [6]:
pre_carto = df.loc[(df['Fecha_Retiro']=='02/04/2019') & (df['Fecha_Arribo']=='02/04/2019') & (df['hora']>=5) & (df['hora']<=23)]\
    .assign(viaje=lambda x: range(len(x)))
carto = pre_carto[['Bici', 'est_retiro', 'time_retiro', 'viaje']].rename(columns=lambda x: x.replace('_retiro', ''))\
    .append(
        pre_carto[['Bici', 'est_arribo', 'time_arribo', 'viaje']].rename(columns=lambda x: x.replace('_arribo', ''))
    )\
    .merge(estaciones[['id_est', 'geometry']], left_on='est', right_on='id_est')\
    .pipe(gpd.GeoDataFrame, geometry='geometry')
#carto.to_file('concurso/cartodb_20190402.geojson', driver='GeoJSON')
In [7]:
viaje = carto.query('viaje==0')
crea_rutas(viaje)
In [34]:
def crea_rutas(viaje):
    rutas = []
    try:
        t0, t1 = viaje.time.to_list()
        p0, p1 = viaje.geometry.to_list()
        if p0==p1:
            return rutas
        m = (p0.y-p1.y)/(p0.x-p1.x)
        b = (p0.x*p1.y - p1.x*p0.y)/(p0.x-p1.x)
        d = (t1-t0).seconds/60
        mx = (p1.x-p0.x)/d
        rutas = [[viaje.index[0], t0+pd.to_timedelta(i, unit='m'),
                  Point(p0.x+i*mx, b + m*(p0.x+i*mx)), i+1 if i != int(d) else 100]
                 for i in range(0, int(d+1))]
    except Exception as e:
        print(e)
    return rutas
In [35]:
resultado = dict()
In [42]:
for v, g in carto.sort_values(['viaje', 'time']).set_index('viaje').loc[30000:].groupby('viaje'):
    if v not in resultado:
        resultado[v] = crea_rutas(g)
In [43]:
ruta = gpd.GeoDataFrame(pd.Series((list(resultado.values()))).sum(), columns=['viaje', 'time', 'geometry', 'step'], geometry='geometry')
In [44]:
ruta_filename = 'concurso/cartodb/ruta_cartodb.geojson'
if os.path.exists(ruta_filename):
    os.remove(ruta_filename)
ruta.to_file(ruta_filename, driver='GeoJSON')

App viaje entre colonias

datos

In [119]:
viajes_colonias = pd.read_pickle('D:/datos/ecobici/viajes_2019.pkl') \
    .rename(columns={'Ciclo_Estacion_Arribo': 'arribo', 'Ciclo_Estacion_Retiro': 'retiro'}) \
    .groupby(['arribo', 'retiro']) \
    .agg({'Bici': 'count', 'time': 'mean', 'genero': 'mean', 'Edad_Usuario': 'mean'}) \
    .reset_index() \
    .assign(arribo=lambda x: x['arribo'].pipe(pd.to_numeric, errors='coerce')) \
    .dropna(subset=['arribo']) \
    .astype({'arribo': int})

viajes_colonias.to_csv('D:/datos/ecobici/viajes_colonias_2019_app.csv', index=False)