import pandas as pd
import numpy as np
import seaborn
%pylab notebook
import hashlib
import humanhash
Tablas Pivote
Es muy común la agrupación de información en formato ‘stack’ donde tenemos filas de datos que demuestran una correlación entre dos sets de valores.
Las tablas pivote son una forma de re-ordenar los datos en una estructura tabular donde podemos agrupar los valores convirtiendo las tuplas entre valores numéricos.
En este caso de ejemplo, crearemos un dataset de prueba con la diversidad de restaurantes en varias ciudades centroamericanas. En esta vamos a comenzar a trabajar con dos columnas, una donde describe cada ciudad y la otra con la variedad de cocina disponible en cada una.
data_restaurantes = {
'ciudades': ['Guatemala','Guatemala','Guatemala','Guatemala','Guatemala','Guatemala','San José','San José','San José','San José','San José','San Salvador','San Salvador','San Salvador'],
'culinaria': ['Chapina','Chapina','China','Thai','Italiana','Chapina','Italiana','China','Tica','Chapina','Tica','Tica','Italiana','China']
}
restaurantes_dataframe_pares = pd.DataFrame(data_restaurantes)
restaurantes_dataframe_pares
Podemos ver entonces este listado de valores, tupla por tupla. Que tal si queremos contar la presencia de cada tipo de cocina en cada región. Probemos utilizando entonces el comando DataFrame.pivot_table de Pandas.
Este pide unos cuantos argumentos los que podemos ver en la documentación. Unos cuantos son obvios, como el definir las filas y columnas que esperamos de la tabla objetivo. Sin embargo, lo más notable es que ya que los tipos de datos que estamos utilizando no son numéricos, es necesario que definamos una funcion de agrupación que nos permita contar la cantidad de instancias de cada combinación.
def funcion_agrupacion(elemento):
return True
agrupacion_culinaria = restaurantes_dataframe_pares.pivot_table(
index=["ciudades"],
columns="culinaria",
aggfunc=funcion_agrupacion,
fill_value=False)
agrupacion_culinaria
Hmm, esto ya se ve con la forma que queremos, sin embargo solo nos muestra la presencia o ausencia de algún tipo de cocina. Esto es fácil de explicar ya que definimos nuestra función de agrupación como retornar True si existe presencia a retornar False si No. ¿Qué tal si hacemos una mejor función de agrupación?
def funcion_agrupacion(elemento):
'''Contemos cuantas instancias de cada tupla existen.'''
## El comando len(iterable) cuenta la cantidad de elementos que tiene el objeto iterable que le pasemos
## los elementos iterables pueden ser listas normales, Series de NumPy o Pandas, o diccionarios y otros tipos de datos.
return len(elemento)
agrupacion_culinaria = restaurantes_dataframe_pares.pivot_table(index=["ciudades"], columns="culinaria", aggfunc=lambda x: funcion_agrupacion(x), fill_value=0)
agrupacion_culinaria
¡Genial! Ahora ya tenemos agrupadas estas de una forma coherente. Ahora ya podemos seguir manipulando y editando estos datos. Pero, ¿qué tal si hacemos un poco más simple esta llamada? Al final nuestra función de agrupación lo unico que hace es contar la cantidad de tuplas sobre las cuales aplica el pivote. ¿Qué tal si lo hacemos un poco más simple?
agrupacion_culinaria = restaurantes_dataframe_pares.pivot_table(
index=["ciudades"],
columns="culinaria",
aggfunc=len, ## Enviamos directamente la función de agrupación. Entre otras funcionas de agrupacíon útiles está np.sum (la función de suma de NumPy) y np.mean (media)
fill_value=0)
agrupacion_culinaria
Claro, la tabla resultante se comporta exactamente igual y tiene todas las propiedades nativas de los DataFrames. ¿Qué tal si limitamos la query a solo los lugares en ciudad de Guatemala?
agrupacion_culinaria.query('ciudades == ["Guatemala"]')
help(restaurantes_dataframe_pares.pivot_table)
## Con el argumento Margins, Panda calcula los valores sumados de los totales por agrupación.
agrupacion_culinaria_m = restaurantes_dataframe_pares.pivot_table(
index=["ciudades"],
columns="culinaria",
aggfunc=len,
fill_value=0,
margins=True,
margins_name="Total")
agrupacion_culinaria_m
Múltiples Valores y Múltiples Indices
¿Qué tal si tenemos datos que tienen una estructura Jerárquica inherente? Podemos utilizar la misma forma de multi indexación que vimos en el seminario pasado, lo importante es que a la hora de la definición del índice, Pandas es capaz de manipularlos e inteligentemente ordenar los niveles acorde.
restaurantes_dataframe_pares['estrellas'] = [5,3,3,5,3,1,2,2,4,3,4,3,2,3]
restaurantes_dataframe_estrellas = restaurantes_dataframe_pares
restaurantes_dataframe_estrellas
Pivotando sobre la especialidad culinaria y estrellas, podemos las ciudades con la mayor oferta culinaria, o cuales tienen el mejor promedio de estrellas.
agrupacion_culinaria_promedio_estrellas = restaurantes_dataframe_estrellas.pivot_table(
index=["ciudades"],
values=["culinaria", "estrellas"],
aggfunc={"culinaria":len,"estrellas":np.mean},
fill_value=0)
agrupacion_culinaria_promedio_estrellas
¿Qué tal si queremos ver cuantas estrellas en promedio tienen los restaurantes, por clase de comida, por ciudad?
agrupacion_culinaria_por_estrellas = restaurantes_dataframe_estrellas.pivot_table(
index=["culinaria"],
values=["estrellas"],
columns=["estrellas"],
aggfunc={"estrellas":np.mean},
fill_value=0)
agrupacion_culinaria_por_estrellas
agrupacion_culinaria_por_estrellas = restaurantes_dataframe_estrellas.pivot_table(
index=["culinaria"],
values=["estrellas"],
columns=["estrellas"],
aggfunc={"estrellas":len},
fill_value=0)
agrupacion_culinaria_por_estrellas
Hmm… esto no es muy útil, solo nos dice tautológicamente, que los restaurantes de ‘n’ estrellas tienen ‘n’ estrellas. ¿Pueden ver porque el error?
agrupacion_culinaria_por_estrellas = restaurantes_dataframe_estrellas.pivot_table(
index=["ciudades"],
values=["estrellas"],
columns=["culinaria"], ## Aqui es obvio ver que lo que queremos es diferenciar por variedad culinaria.
aggfunc={"estrellas":np.mean},
fill_value=0)
agrupacion_culinaria_por_estrellas
agrupacion_culinaria_por_estrellas.plot(kind="bar")
Entonces, recapitulando:
¿De qué nos sirven las tablas pivote?
¿Que clase de operación representan?
¿En que casos podemos usarlas?
Agrupando, parte dos
Ya vimos algunos de los criterios básicos de agrupación en el primer webinar, ahora podemos avanzar un poco, combinando agrupación con pivote.
Tambien podemos usar stack, que es otra forma de agrupación basada en índices.
maga_fitosanitario = pd.read_csv("MAGA - CERTIFICADOS FITOSANITARIOS.csv")
hashlib.md5("Hola".encode("UTF-8")).hexdigest()
pd.set_option('display.float_format', lambda x: '%.1f' % x)
## Vamos a limpiar un poco de información
def ofusca_nombre(nombre):
return humanhash.humanize(hashlib.md5(nombre.encode("UTF-8")).hexdigest())
maga_fitosanitario["Solicitante"] = maga_fitosanitario["Solicitante"].map(ofusca_nombre)
maga_fitosanitario["Fecha Autorización"] = maga_fitosanitario["Fecha Autorización"].map(pd.Timestamp)
def clean_q(input_object):
from re import sub ## importamos la función sub, que substituye utilizando patrones
## https://es.wikipedia.org/wiki/Expresión_regular
## NaN es un objeto especial que representa un valor numérico invalido, Not A Number.
if input_object == NaN:
return 0
inp = unicode(input_object) # De objeto a un texto
cleansed_00 = sub(r'\.000', '000', inp)
cleansed_nonchar = sub(r'[^0-9]+', '', cleansed_00)
if cleansed_nonchar == '':
return 0
return cleansed_nonchar
maga_fitosanitario["Kg. Netos"] = maga_fitosanitario["Kg. Netos"].map(clean_q).astype(float)
maga_fitosanitario["Kg. Netos"].head()
maga_fitosanitario = pd.read_csv("MAGA - CERTIFICADOS FITOSANITARIOS - LIMPIO.csv")
maga_fitosanitario.head()
maga_fitosanitario.groupby("Producto").sum().sort_values("CIF $", ascending=False).head(20)
maga_productos_pivot = maga_fitosanitario.pivot_table(
index=["Categoría", "Producto"],
values=["CIF $", "Permiso","Kg. Netos"],
aggfunc={"CIF $":np.sum,"Permiso":len, "Kg. Netos": np.sum},
fill_value=0)
maga_productos_pivot
maga_aduanas_pivot = maga_fitosanitario.pivot_table(
index=["País origen", "Aduana"],
values=["CIF $", "Kg. Netos"],
aggfunc={"CIF $":np.sum,"Kg. Netos":np.mean},
fill_value=0)
maga_aduanas_pivot
Que tal si indagamos mas en las categorias que se importan de cada país.
maga_aduanas_pivot = maga_fitosanitario.pivot_table(
index=["País origen", "Aduana", "Categoría"],
values=["CIF $"],
aggfunc={"CIF $":np.sum},
fill_value=0)
maga_aduanas_pivot
maga_aduanas_pivot_top10 = maga_aduanas_pivot.sort_values("CIF $", ascending=False).head(10)
maga_aduanas_pivot_top10.plot(kind="barh")
Tambien es util mostrar la tabla, podemos ponerle un poco de estilo con la funcionalidad de Seaborn + Pandas
cm_paleta_verde = seaborn.light_palette("green", as_cmap=True)
s = maga_aduanas_pivot_top10.style.background_gradient(cmap=cm_paleta_verde)
s
maga_aduanas_pivot = maga_fitosanitario.pivot_table(
index=["País origen", "Aduana", "Categoría"],
values=["CIF $"],
aggfunc={"CIF $":np.sum},
fill_value=0)
maga_aduanas_pivot
cm_paleta_verde = seaborn.light_palette("green", as_cmap=True)
s = maga_aduanas_pivot_top10.style.background_gradient(cmap=cm_paleta_verde)
s
maga_fitosanitario
maga_fitosanitario.groupby("Solicitante").sum().sort_values("CIF $", ascending=False).head(10)
maga_fitosanitario.pivot_table(
index=["Solicitante", "País origen", "Categoría"],
values=["CIF $"],
aggfunc={"CIF $":np.sum},
fill_value=0).sort_values("CIF $", ascending=False).head(10)
maga_fitosanitario.pivot_table(
index=["País procedencia"],
columns=["Aduana"],
values=["CIF $"],
aggfunc={"CIF $":np.sum},
fill_value=0).style.background_gradient(cmap=cm_paleta_verde)
Que tal si queremos obtener el precio por kilogramo de cada producto y en base a eso obtener los productos mas ‘preciosos’.
maga_fitosanitario.to_csv("MAGA - CERTIFICADOS FITOSANITARIOS - LIMPIO.csv")