Mediante el uso de datos de uso del subte en CABA, se explican distintas funcionalidades de formato de tablas con pandas. Se busca replicar the grammar of tables (gt) en python.
En un post anterior (Uso del subte en la Ciudad Autónoma de Buenos Aires) se muestra cómo utilizar el paquete {gt}📦1 para realizar tablas basadas en the Grammar of Graphics. Se recomienda revisar el post anterior antes de leer este.
En este caso, se mostrarán algunas funcionalidades de {pandas}📦2 que permiten generar un formato de tabla muy similar al obtenido en el post sobre {gt} pero en este caso utilizando python.
⚙️ Se utiliza un environment específico para este proyecto, con python 3.9:
reticulate::conda_create(envname='tabla-subtes', python_version="3.9")
Se instalan los paquetes python 📦
reticulate::conda_install(envname = 'tabla-subtes',
packages='geopandas', channel='conda-forge')
reticulate::conda_install(envname = 'tabla-subtes',
packages='plotnine', channel='conda-forge')
reticulate::conda_install(envname = 'tabla-subtes',
packages='seaborn', channel='conda-forge')
reticulate::conda_install(envname = 'tabla-subtes',
packages='IPython', channel='conda-forge')
Con el environment creado y activado, se define que se va a utilizar ese environment:
reticulate::use_condaenv(condaenv = 'tabla-subtes', required = TRUE)
Para utilizar python en rmarkdown, es necesario definir que se va a utilizar un chunk de código python. Para más información sobre python en rmarkdown, ver: El uso de múltiples lenguajes en Rmarkdown.
Se importan las librerías python a utilizar:
import numpy as np
import pandas as pd
import geopandas as gpd
from io import BytesIO
import base64
from IPython.core.display import HTML
from plotnine import *
import seaborn as sns
import matplotlib.pyplot as plt
from mizani.formatters import date_format
from mizani.breaks import date_breaks
from scipy.stats import circmean
import pprint
import warnings
"ignore") warnings.filterwarnings(
Se importan los datos de viajes en subte de la Ciudad Autónoma de Buenos Aires, en Noviembre 2021.
= 'https://cdn.buenosaires.gob.ar/datosabiertos/datasets'
base_url = 'sbase/subte-viajes-molinetes'
dataset
def read_data(url, dataset, file):
= f'{url}/{dataset}/{file}'
path print(path)
= (pd.read_csv(path, delimiter=';')
df_
'~FECHA.isna()', engine='python')
.query('DESDE':'hora'},axis=1)
.rename({'HASTA'], axis=1)
.drop([= lambda c: c.lower())
.rename(columns
.assign(= lambda x: [i.replace('Linea','') for i in x['linea']],
linea = lambda x: pd.to_datetime(x['fecha'],format='%d/%m/%Y')
fecha
)= lambda x: np.select(
.assign(color 'linea']=='A',
[x['linea']=='B',
x['linea']=='C',
x['linea']=='D',
x['linea']=='E',
x['linea']=='H'],
x['#18cccc','#eb0909','#233aa8','#02db2e','#c618cc','#ffdd00'],
[='black')
default
)
['linea',
['color',
'fecha',
'hora',
'molinete',
'estacion',
'pax_pagos',
'pax_pases_pagos',
'pax_franq',
'pax_total']
]
)
return(df_)
= read_data(url=base_url, dataset=dataset, file='molinetes_112021.csv')
df = read_data(url=base_url,dataset=dataset, file='molinetes_102021.csv') df_oct
#df.to_csv('data/df_nov.csv', index=False)
#df_oct.to_csv('data/df_oct.csv', index=False)
= (pd.read_csv('data/df_nov.csv')
df
.assign(= lambda x: pd.to_datetime(x['fecha']),
fecha = lambda x: pd.to_datetime(x['hora'], format='%H:%M:%S').dt.hour
hora
)
)= (pd.read_csv('data/df_oct.csv')
df_oct
.assign(= lambda x: pd.to_datetime(x['fecha']),
fecha = lambda x: pd.to_datetime(x['hora'], format='%H:%M:%S').dt.hour
hora
) )
Estaciones de subte:
= pd.read_csv('data/estaciones.csv')
df_estaciones
= {
renombrar_estaciones 'Flores': 'San Jose De Flores',
'Saenz Peña ': 'Saenz Peña',
'Callao.b': 'Callao',
'Retiro E': 'Retiro',
'Independencia.h': 'Independencia',
'Pueyrredon.d': 'Pueyrredon',
'General Belgrano':'Belgrano',
'Rosas': 'Juan Manuel De Rosas',
'Patricios': 'Parque Patricios',
'Mariano Moreno': 'Moreno'
}
= (df
df_pasajeros_estaciones 'linea','color','estacion'], as_index=False)
.groupby([= ('pax_total','sum'))
.agg(pax_total = lambda x: x['estacion'].str.title())
.assign(estacion
.replace(renombrar_estaciones)=['linea','estacion'])
.merge(df_estaciones, on )
Se define el estilo de la tabla y los strings que se utilizarán como títulos y subtítulos. Al definirlos como objetos podrán ser reutilizados fácilmente en cualquier tabla que se genere luego.
= [
custom_style 'selector':"caption",
{'props':[("text-align", "left"),
"font-size", "135%"),
("font-weight", "bold")]
(
},'selector':'th',
{"props": 'text-align : center; background-color: white; color: black'
},"selector": "",
{"props": [("border", "1px solid lightgrey")]
}
]
='Formato de los datos (primeras 5 filas)'
titulo = 'Cantidad de pasajeros por molinete y por estación de todas las estaciones de la red de subte, Noviembre 2021' subtitulo
Un primer ejemplo para mostrar con pandas son los primeros registros del dataframe original. En este caso, se incluyen algunas primeras opciones del pandas styler object:
= (df
styled_df 'color',axis=1)
.drop(=lambda x: x['fecha'].dt.date)
.assign(fecha5)
.head(
# A partir de acá deja de ser un dataframe y pasa a ser un styler:
.style
# Formato de dos decimales en los valores numéricos
format(precision=2)
.
# Se añade una capa de título y subtítulo
f"""
.set_caption( <h1><span style="color: darkblue">{titulo}</span><br></h1>
<span style="color: black">{subtitulo}</span><br><br>
""")
# Se añade una capa de estilo
.set_table_styles(custom_style)
# Se oculta el índice
='index')
.hide(axis
)
styled_df
linea | fecha | hora | molinete | estacion | pax_pagos | pax_pases_pagos | pax_franq | pax_total |
---|---|---|---|---|---|---|---|---|
C | 2021-11-01 | 5 | LineaC_Indepen_Turn03 | Independencia | 0 | 0 | 2 | 2 |
A | 2021-11-01 | 5 | LineaA_Pasco_Turn03 | Pasco | 1 | 0 | 0 | 1 |
B | 2021-11-01 | 5 | LineaB_Malabia_N_Turn05 | Malabia | 0 | 0 | 1 | 1 |
B | 2021-11-01 | 5 | LineaB_Gallardo_S_Turn02 | Angel Gallardo | 1 | 0 | 0 | 1 |
A | 2021-11-01 | 5 | LineaA_Congreso_S_Turn03 | Congreso | 0 | 0 | 1 | 1 |
Se construye un dataframe a nivel Línea de subte. Para ello, se agrupa por línea obteniendo la estación más utilizada (moda) y la cantidad de usuarios totales. Se asigna el recorrido de cada línea como una nueva columna. Inicialmente se añaden columnas de la línea duplicadas, estas definen lo que luego serán gráficos.
Se incluirá una columna de % de variación de pasajeros por línea en relación al mes previo (Octubre 2021):
= (df_oct
df_pasajeros_mesprevio 'linea', as_index=False)
.groupby(= ('pax_total','sum'))
.agg(pax_total_oct )
= (df
datos_tabla 'linea','color'], as_index=False)
.groupby([
.agg(= ('estacion', pd.Series.mode),
Estacion_mas_usada = ('pax_total','sum'))
pax_total ='linea', how='left')
.merge(df_pasajeros_mesprevio, on
.assign(= lambda x: [str(round(i/1000000,2))+'M' for i in x['pax_total']],
Usuarios = lambda x: (x['pax_total']/x['pax_total_oct']-1),
Variacion_oct = lambda x: np.select([
Recorrido 'linea']=='A',
x['linea']=='B',
x['linea']=='C',
x['linea']=='D',
x['linea']=='E',
x['linea']=='H'],
x[
'Plaza de Mayo - San Pedrito',
['J.M. Rosas - L.N. Alem',
'Constitución - Retiro',
'Congreso de Tucumán - Catedral',
'Retiro - Plaza de los Virreyes',
'Hospitales - Facultad de Derecho'],
='Otro'),
default= lambda x: x['linea'],
Pasajeros_por_dia = lambda x: x['linea'],
Mapa = lambda x: x['linea'],
Horas = lambda x: x['linea']
Porcentaje
)'linea':'Linea'},axis=1)
.rename({'Linea','Recorrido','Estacion_mas_usada','Mapa','Usuarios',
[['Variacion_oct', 'Porcentaje', 'Horas','Pasajeros_por_dia','color']]
)
Los recorridos en la tabla original aparecen del color de la Línea de subte, con fondo gris. Para ello, se genera un diccionario que asigne el color de la línea a cada uno de los recorridos.
= dict(zip(datos_tabla['Recorrido'], datos_tabla['color']))
color_mapping pprint.pprint(color_mapping)
{'Congreso de Tucumán - Catedral': '#02db2e',
'Constitución - Retiro': '#233aa8',
'Hospitales - Facultad de Derecho': '#ffdd00',
'J.M. Rosas - L.N. Alem': '#eb0909',
'Plaza de Mayo - San Pedrito': '#18cccc',
'Retiro - Plaza de los Virreyes': '#c618cc'}
También se genera un diccionario que asignará el color gris a cada uno de los recorridos. Esta es una forma sencilla de colorear toda una columna, aunque puede existir alguna alternativa más simple.
= dict(zip(datos_tabla['Recorrido'], ['#f0f0f0']*6))
color_mapping_back pprint.pprint(color_mapping_back)
{'Congreso de Tucumán - Catedral': '#f0f0f0',
'Constitución - Retiro': '#f0f0f0',
'Hospitales - Facultad de Derecho': '#f0f0f0',
'J.M. Rosas - L.N. Alem': '#f0f0f0',
'Plaza de Mayo - San Pedrito': '#f0f0f0',
'Retiro - Plaza de los Virreyes': '#f0f0f0'}
Al generar la tabla, se elimina la columna de color, ya que no es relevante. Luego de aplicar el .style, se utiliza la función applymap() para definir el formato css de la columna de recorrido a partir de los diccionarios generadods anteriromente. Se añaden el título y subtítulo como en el caso anterior.
='Uso del subte en la Ciudad Autónoma de Buenos Aires'
titulo = 'Período de analisis: Noviembre 2021'
subtitulo
= (datos_tabla
tabla_inicial 'color',axis=1)
.drop(
.stylelambda v: f"color: {color_mapping.get(v, 'black')}")
.applymap(lambda v: f"background-color: {color_mapping_back.get(v, 'white')}")
.applymap(
.set_table_styles(custom_style)f"""
.set_caption( <h1><span style="color: darkblue">{titulo}</span><br></h1>
<span style="color: black">{subtitulo}</span><br><br>
"""
)='index')
.hide(axisformat({
.'Variacion_oct': '{:,.2%}'.format
})
)
tabla_inicial
Linea | Recorrido | Estacion_mas_usada | Mapa | Usuarios | Variacion_oct | Porcentaje | Horas | Pasajeros_por_dia |
---|---|---|---|---|---|---|---|---|
A | Plaza de Mayo - San Pedrito | San Pedrito | A | 2.81M | 14.97% | A | A | A |
B | J.M. Rosas - L.N. Alem | Federico Lacroze | B | 3.48M | 12.53% | B | B | B |
C | Constitución - Retiro | Constitucion | C | 2.13M | 19.51% | C | C | C |
D | Congreso de Tucumán - Catedral | Congreso de Tucuman | D | 3.0M | 11.58% | D | D | D |
E | Retiro - Plaza de los Virreyes | Retiro E | E | 1.12M | 15.50% | E | E | E |
H | Hospitales - Facultad de Derecho | Santa Fe | H | 1.52M | 9.22% | H | H | H |
En la tabla original se observa que las Líneas aparecen identificadas con una letra de color. Esta letra es una imagen, que se puede incluir en la tabla a partir del archivo .png de esa imagen. Estos archivos se encuentran almacenados localmente, con lo cual se pueden incluir en la tabla de la siguiente forma:
def map_linea_img(i):
= f'lineas/{i.lower()}.jpg'
path return '<img src="'+ path + '" width="15" >'
Se aplica la función map_linea_img() a la columna linea del styler object:
(tabla_inicialformat(formatter={
.'Linea':map_linea_img,
'Variacion_oct': '{:,.2%}'.format
}) )
Linea | Recorrido | Estacion_mas_usada | Mapa | Usuarios | Variacion_oct | Porcentaje | Horas | Pasajeros_por_dia |
---|---|---|---|---|---|---|---|---|
Plaza de Mayo - San Pedrito | San Pedrito | A | 2.81M | 14.97% | A | A | A | |
J.M. Rosas - L.N. Alem | Federico Lacroze | B | 3.48M | 12.53% | B | B | B | |
Constitución - Retiro | Constitucion | C | 2.13M | 19.51% | C | C | C | |
Congreso de Tucumán - Catedral | Congreso de Tucuman | D | 3.0M | 11.58% | D | D | D | |
Retiro - Plaza de los Virreyes | Retiro E | E | 1.12M | 15.50% | E | E | E | |
Hospitales - Facultad de Derecho | Santa Fe | H | 1.52M | 9.22% | H | H | H |
#https://stackoverflow.com/questions/47038538/insert-matplotlib-images-into-a-pandas-dataframe
Este es uno de los puntos más complicados, por eso es necesario realizarlo de forma ordenada. Para los gráficos incluidos en la tabla, se comenzará generando el gráfico para una línea. Luego de observar que funciona, se incluye en la tabla para cada una de las líneas, mediante una función map_plot_{nombre_del_grafico}(). Se comienza por el caso más simple, de la evolución del uso de cada línea en el mes de noviembre 2021:
Se obtiene la data de la cantidad de pasajeros por fecha en una línea particular:
= 'A'
i
=(df
data_linea"linea==@i")
.query('fecha', as_index=False)
.groupby(
.pax_totalsum()
. )
Se utiliza plotnine para generar el gráfico de la evolución de la línea A:
=(ggplot(
p= data_linea,
data = aes(x='fecha', y='pax_total', group=1)
mapping +
) +
geom_line()+
theme_minimal()='',y='N')+
labs(x
scale_x_datetime(= date_format("%Y-%m"),
labels =date_breaks('7 days')
breaks+
)+
theme_void()
theme(=element_text(size=8),
text=element_text(vjust=-0.5))+
axis_text_x= '', y = '')
labs(x
)
print(p)
Habiendo definido el gráfico correctamente, se convierte en función:
def fig_evol_pax_total(i):
=df.query("linea==@i")
data_linea
= data_linea.color.max()
color
=(data_linea
data_linea'fecha', as_index=False)
.groupby(
.pax_totalsum()
.
)
= (ggplot(
p = data_linea,
data = aes(x='fecha', y='pax_total',group=1)
mapping +
) =color)+
geom_line(color+
theme_minimal()='',y='N')+
labs(x
scale_x_datetime(= date_format("%Y-%m"),
labels =date_breaks('7 days')
breaks+
)= '', y = '') +
labs(x +
theme_void()
theme(= element_rect(fill=None),
panel_background= element_rect(fill=None),
plot_background =element_text(size=7),
text=element_text(vjust=-0.5)
axis_text_x
)
)return p
La función anterior retorna el gráfico para una línea i dada. Sin embargo, lo que se busca mappear en la tabla no es el gráfico, sino una imagen del gráfico. Para ello, se genera una función intermedia que toma el gráfico y lo convierte al path html de una imagen temporal:
def plotnine2html(p,i, width=5, height=2):
= BytesIO()
figfile format='png', width=width, height=height, units='in')
p.save(figfile, 0)
figfile.seek(= base64.b64encode(figfile.getvalue()).decode()
figdata_png = f'<img src="data:image/png;base64,{figdata_png}" />'
imgstr
return imgstr
Finalmente, se genera la función de mappeo, que será utilizada para generar los gráficos en la tabla:
def map_plot_evol(i):
= fig_evol_pax_total(i)
fig return plotnine2html(fig,i)
Ahora es posible aplicar la función tal como en el caso de las imagenes de las líneas de subte:
(tabla_inicial
format(
.={
formatter'Linea':map_linea_img,
'Pasajeros_por_dia': map_plot_evol,
'Variacion_oct': '{:,.2%}'.format
}) )
Linea | Recorrido | Estacion_mas_usada | Mapa | Usuarios | Variacion_oct | Porcentaje | Horas | Pasajeros_por_dia |
---|---|---|---|---|---|---|---|---|
Plaza de Mayo - San Pedrito | San Pedrito | A | 2.81M | 14.97% | A | A | ||
J.M. Rosas - L.N. Alem | Federico Lacroze | B | 3.48M | 12.53% | B | B | ||
Constitución - Retiro | Constitucion | C | 2.13M | 19.51% | C | C | ||
Congreso de Tucumán - Catedral | Congreso de Tucuman | D | 3.0M | 11.58% | D | D | ||
Retiro - Plaza de los Virreyes | Retiro E | E | 1.12M | 15.50% | E | E | ||
Hospitales - Facultad de Derecho | Santa Fe | H | 1.52M | 9.22% | H | H |
Al igual que en el caso anterior, primero se construye un gráfico (mapa) individual y luego se transforma en las funciones necesarias para el mappeo del gráfico a cada línea.
Se obtienen los datos del mapa:
= 'http://cdn.buenosaires.gob.ar/datosabiertos/datasets/barrios/barrios.geojson'
url_mapa = gpd.read_file(url_mapa) mapa
En este caso, se genera directamente la función del gráfico aplicandola sobre una línea:
def fig_mapa(i):
"""
Generación de mapa de uso de cada lìnea
"""
=(df_pasajeros_estaciones
data_linea"linea==@i")
.query(
.assign(= lambda x:
pax_percent 'pax_total']/
x["linea==@i")['pax_total'].sum()*100
df_pasajeros_estaciones.query(
)
)= data_linea.color.max()
color
= round(
lbreaks 'pax_percent'].quantile([0,0.25,0.5,0.75,1]),2
data_linea[
)
= (ggplot(data=mapa)+
p ='white', color = "black", size = 0.1)+
geom_map(fill=data_linea,
geom_point(data=aes(x='long',y='lat', size='pax_percent'),
mapping=0.5, color='black', shape='o', fill=color)+
alpha
scale_size_continuous(=lbreaks,
lbreaksrange=[1,10],
= [
limits 'pax_percent'].min()-1,
data_linea['pax_percent'].max()+1
data_linea[
],=lambda l: [f'{round(i)}%' for i in l])+
labels+
theme_void()='right')+
theme(legend_position='%')
labs(size
)
return p
print(fig_mapa(i=i)+theme(legend_position='none'))
Se genera la función de mappeo:
def map_plot_mapa(i):
= fig_mapa(i)
fig return plotnine2html(fig, i, width=5, height=5)
Se incluye el plot en la tabla:
(tabla_inicial
format(
.={
formatter'Linea':map_linea_img,
'Pasajeros_por_dia': map_plot_evol,
'Mapa':map_plot_mapa,
'Variacion_oct': '{:,.2%}'.format
}) )
Linea | Recorrido | Estacion_mas_usada | Mapa | Usuarios | Variacion_oct | Porcentaje | Horas | Pasajeros_por_dia |
---|---|---|---|---|---|---|---|---|
Plaza de Mayo - San Pedrito | San Pedrito | 2.81M | 14.97% | A | A | |||
J.M. Rosas - L.N. Alem | Federico Lacroze | 3.48M | 12.53% | B | B | |||
Constitución - Retiro | Constitucion | 2.13M | 19.51% | C | C | |||
Congreso de Tucumán - Catedral | Congreso de Tucuman | 3.0M | 11.58% | D | D | |||
Retiro - Plaza de los Virreyes | Retiro E | 1.12M | 15.50% | E | E | |||
Hospitales - Facultad de Derecho | Santa Fe | 1.52M | 9.22% | H | H |
Una de las funcionalidades de gt permite transformar una columna con listas de porecntajes en gráficos de porcentajes. En este caso, se decidió hacerlo con plotnine.
def fig_percent(i):
= (df
temp 'linea==@i')
.query(
.assign(= lambda x: pd.cut(
hora_grupo 'hora'], bins=3, labels = ['Mañana', 'Tarde', 'Noche'])
x[
)'linea','hora_grupo'], as_index=False)
.groupby([= ('pax_total','sum'))
.agg(pax_total
)'perc']=round(
temp['pax_total'] / temp.groupby('linea')['pax_total'].transform('sum')*100,2)
temp['perc_lab'] = [str(i)+'%' for i in temp['perc']]
temp[
=(ggplot(data=temp,
p=aes(x='linea', y='perc', fill='hora_grupo', label='perc_lab'))+
mapping= position_stack(reverse=True))+
geom_col(position
geom_text(= position_stack(vjust = .5, reverse=True),
position ='white', size=8)+
color+
coord_flip()'grey', '#A3B1C9','#4C699E'])+
scale_fill_manual([+
theme_void()='none')
theme(legend_position
)return p
print(fig_percent(i='A'))
Se genera la función de mappeo:
def map_plot_percent(i):
= fig_percent(i)
fig return plotnine2html(fig,i, width=4, height=0.4)
Se incluye el plot en la tabla:
(tabla_inicial
format(
.={
formatter'Linea':map_linea_img,
'Pasajeros_por_dia': map_plot_evol,
'Mapa':map_plot_mapa,
'Porcentaje':map_plot_percent,
'Variacion_oct': '{:,.2%}'.format
}) )
Linea | Recorrido | Estacion_mas_usada | Mapa | Usuarios | Variacion_oct | Porcentaje | Horas | Pasajeros_por_dia |
---|---|---|---|---|---|---|---|---|
Plaza de Mayo - San Pedrito | San Pedrito | 2.81M | 14.97% | A | ||||
J.M. Rosas - L.N. Alem | Federico Lacroze | 3.48M | 12.53% | B | ||||
Constitución - Retiro | Constitucion | 2.13M | 19.51% | C | ||||
Congreso de Tucumán - Catedral | Congreso de Tucuman | 3.0M | 11.58% | D | ||||
Retiro - Plaza de los Virreyes | Retiro E | 1.12M | 15.50% | E | ||||
Hospitales - Facultad de Derecho | Santa Fe | 1.52M | 9.22% | H |
En este caso se utiliza R para el cálculo de la hora promedio a través del paquete {circular}
library(circular)
library(reticulate)
library(tidyverse)
library(reshape)
get_hour <- function(.linea, .df) {
temp <- .df %>%
filter(linea == .linea) %>%
select(hora, pax_total)
hora <- untable(temp, num = temp$pax_total) %>%
select(-pax_total) %>%
mutate(hora_circular = circular(hora,
template = "clock24",
units = "hours")) %>%
summarise(hora = mean(hora_circular)) %>%
pull(hora)
as.numeric(hora) %% 24
}
df_horas_promedio = data.frame(Linea=py$df %>% pull(linea) %>% unique()) %>%
mutate(hora_promedio=map(Linea, ~get_hour(.linea=.x, .df=py$df)))
Se define la función que genera los gráficos circulares:
def gen_clock_plot(df_, hora_promedio, x='hora', y='pax_total', color='blue'):
"""
Generar un gráfico de barras con coordenadas polares
Inputs:
- df: dataframe
- x: nombre de la variable x
- y: nombre de la variable y
"""
= plt.subplots(figsize=(4,4))
fig, ax = plt.subplot(111, polar=True)
ax
= df_[y].astype('float').to_numpy()
cr = df_[x].astype('float').to_numpy()
hour = 24
N = 2
bottom = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
theta = (2*np.pi) / N
width = ax.bar(theta, cr,
bars =width,
width=bottom,
bottom='lightgrey',
color='white')
edgecolor
ax.vlines(= hora_promedio*theta.max()/24,
x=0, ymax=df_['pax_total'].max(),
ymin=color)
color"N")
ax.set_theta_zero_location(-1)
ax.set_theta_direction(= ['0:00', '3:00', '6:00', '9:00', '12:00', '15:00', '18:00', '21:00']
ticks
ax.set_xticklabels(ticks)return fig
def fig_clock_plot(i):
=df.query("linea==@i")
data_linea= data_linea.color.max()
color
= (pd.DataFrame({'hora':range(0,24)})
data_hora
.merge(df'linea==@i')
.query('hora', as_index=False)
.groupby(= ('pax_total','sum')),
.agg(pax_total ='left'
how
)0)
.fillna(
)= r.df_horas_promedio.query("Linea==@i").hora_promedio
hora_promedio
return gen_clock_plot(data_hora, color=color, hora_promedio=hora_promedio)
=i) fig_clock_plot(i
Se generan las funciones de mappeo:
def clock2inlinehtml(p,i):
= BytesIO()
figfile format='png', dpi=100, transparent=True)
plt.savefig(figfile, 0)
figfile.seek(= base64.b64encode(figfile.getvalue()).decode()
figdata_png = f'<img src="data:image/png;base64,{figdata_png}" />'
imgstr
figfile.close()return imgstr
def map_plot_reloj(i):
=(5,5))
plt.figure(figsize= fig_clock_plot(i)
fig return clock2inlinehtml(fig,i)
Se añaden los gráficos al df. Se visualizará en el paso siguiente, junto con algunas transformaciones adicionales:
= (tabla_inicial
tabla_plots
format(
.={
formatter'Linea':map_linea_img,
'Pasajeros_por_dia': map_plot_evol,
'Mapa':map_plot_mapa,
'Porcentaje':map_plot_percent,
'Horas':map_plot_reloj,
'Variacion_oct': '{:,.2%}'.format
}) )
Algunas cuestiones de estilo final:
= "<div><br>Elaboración propia en base a datos del Portal de datos abiertos de la Ciudad de Buenos Aires</br></div>"
fuente
= "<div>(1): % de pasajeros por estación en relación al total de pasajeros en esa línea. Para los cortes se utilizaron los cuantiles de la distribución. Se observa que la Línea C presentó un uso muy elevado en las cabeceras, mientras que en el resto de las líneas el uso fue más distribuido.</div>"
caption_porcentaje
= """<div>(2): % Variación en relación a Octubre 2021.</div>""" caption_variacion
= HTML(tabla_plots
tabla_final
.to_html()'Mapa','% Pasajeros por estación (1)')
.replace('Porcentaje','% Pasajeros por hora')
.replace('Variacion_oct','% Variación (2)')
.replace('Horas','Pasajeros por hora')
.replace('Estacion_mas_usada','Estación más utilizada')
.replace(+ f'<caption>{caption_porcentaje}<br>{caption_variacion}<br>{fuente}</>'
)
tabla_final
Linea | Recorrido | Estación más utilizada | % Pasajeros por estación (1) | Usuarios | % Variación (2) | % Pasajeros por hora | Pasajeros por hora | Pasajeros_por_dia |
---|---|---|---|---|---|---|---|---|
Plaza de Mayo - San Pedrito | San Pedrito | 2.81M | 14.97% | |||||
J.M. Rosas - L.N. Alem | Federico Lacroze | 3.48M | 12.53% | |||||
Constitución - Retiro | Constitucion | 2.13M | 19.51% | |||||
Congreso de Tucumán - Catedral | Congreso de Tucuman | 3.0M | 11.58% | |||||
Retiro - Plaza de los Virreyes | Retiro E | 1.12M | 15.50% | |||||
Hospitales - Facultad de Derecho | Santa Fe | 1.52M | 9.22% |
import dataframe_image as dfi
'df_styled.png') dfi.export(tabla_plots,
En este post se mostraron varias formas de modificar el estilo en tablas pandas. Esto no quiere decir que sean la única o mejor forma de realizar estos pasos. Cualquier comentario es bienvenido!
Karina Bartolome, Linkedin, Twitter, Github, Blogpost
For attribution, please cite this work as
Bartolomé (2022, Sept. 29). Karina Bartolome: Formato en tablas Pandas. Retrieved from https://karbartolome-blog.netlify.app/posts/tablas-pandas/
BibTeX citation
@misc{bartolomé2022formato, author = {Bartolomé, Karina}, title = {Karina Bartolome: Formato en tablas Pandas}, url = {https://karbartolome-blog.netlify.app/posts/tablas-pandas/}, year = {2022} }