Sidetable, el beffo de pandas
Sidetable
es una biblioteca de python que agrega métodos a los DataFrames de pandas para facilitar la exploración de datos. Según Chris Moffit (el creador de sidetable
), en el repositorio de GitHub del proyecto, sidetables
es como una versión más chida de .value_counts()
y .crosstab()
juntos para facilitar la creación de tablas resumen (? summary tables). Repositorio de sidetable
en GitHub
Este tutorial va a ser una rápida introducción a sidetable
para demostrar sus habilidades. Estaremos utilizando el conjunto de datos palmerpenguins
que tiene datos sobre 💖PINGÜINOS💖 que es una gran alternativa al dataset iris
que es comúnmente usado en estos tutoriales por que 1) los datos vienen de un estadístico racista que los publicó en una publicación sobre eugenesia🤮
me: using the iris dataset is fine. not everything Fisher did was about eugenics!
— 🔥Kareem Carr🔥 (@kareem_carr) June 11, 2020
me: *looks up the iris dataset citation* 😮 pic.twitter.com/GWymD6IG7I
Para más información de los datos visita su página oficial allisonhorst.github.io/palmerpenguis/
Para nuestra suerte la biblioteca seaborn
y su complemento seaborn-data
nos ayudan a obtener los datos rápidamente.
Prepareishon
Obvis microbis tines que tener instalado pandas
y sidetable
. en este tutorial vamos a usar seaborn
para acceder los datos entonces también necesitarás instalarlo.
pip install -U pandas sidetable seaborn
la U
es de --upgrade
, significa que actualiza las bibliotecas si ya las tienes instaladas.
import seaborn as sns
import pandas as pd
import sidetable
print(f"Versión de seaborn: {sns.__version__}")
print(f"Versión de pandas: {pd.__version__}")
print(f"Versión de sidetable: {sidetable.__version__}")
datos = sns.load_dataset('penguins')
datos.head()
Dos métodos para explorar un DataFrame rápidamente son .info()
y .describe()
los cuales te muestran lo siguiente
datos.info()
datos.describe()
.info()
te muestra una tabla con las columnas de tu DataFrame, la cuenta de valores no nulos y el dtype o data type de los datos de la columna. Mientras que describe()
te muestra estadísticas de tus columnas numéricas. La cuenta, el promedio, la deviación estandar, el mínimo y máximo y los percentiles 25, 50 (la mediana) y 75.
datos.isna().sum()
Y luego si quería saber que porcentage es del total era hacer algo así
datos.isna().sum() / len(datos)
ya que len(datos)
te da el "largo" del DataFrame, osea el número de filas.
sidetable
simplifica esto de la siguiente manera
datos.stb.missing()
y hasta lo enchula
datos.stb.missing(style = True)
La mágia de sidetable
es que agrega un accessor a los DataFrames de pandas. Al hacer import sidetable
estás habilitando el accessor .stb
. Así es como se integra a tu flujo de trabajo.
El siguiente método incluido en sidetable
es .freq()
que es como un .value_counts()
súper poderoso.
datos['island'].value_counts()
datos['island'].value_counts(normalize = True)
datos.stb.freq(['island'])
Este método también tiene el parametro style
para limpiar la columna de porcentages.
datos.stb.freq(['island'], style = True)
Algo que me encantó de esta biblioteca es que crea también estos porcentages y cuentas cumulativas. En este caso no agrega mucho contexto pero cuando trabajo con datos censales se me hace muy útil.
Habrás notado que le pasamos una lista de columnas (aunque haya sido una lista de 1. De hecho, el parámetro de la función .freq()
se llama cols
en plural. Veamos que sucede si le pasamos más de una columna.
datos.stb.freq(['island', 'species', 'sex'], style = True)
para lograr algo similar puro pandas tendría que hacer algo como
datos.groupby(['island', 'species', 'sex'])['culmen_length_mm'].count().to_frame().reset_index()
guardar este DataFrame con un nombre nuevo y crearle nuevas columnas. Con sidetable
logro esto con un sólo comando.
Esa es la mágia de sidetable
en mi opinión. No es que este ofreciendote funcionalidades que no existían en pandas. Lo que hace es hacerlas más accesibles 💖
Este método .freq()
también tiene el parámetro thresh
que sirve para agrupar valores hasta cierto valor cumulativo.
datos.stb.freq(['island', 'species'], style = True)
datos.stb.freq(['island', 'species'], style = True, thresh = .6)
Lo que sidetable
esta haciendo es mostrar tus valores hasta que llegan a tu thresh
o límite. En este caso, muestra todos los valores que van sumando hasta llegar a un valor cumulativo de 60% y los demás valores se agrupan bajo la etiqueta Others
(la cual también puedes modificar).
datos.stb.freq(['island', 'species'], style = True, thresh = .6, other_label = "baia baia tacubaya")
datos.stb.subtotal()
Puedes ver como agrega la fila Grand total a tu DataFrame. No es como que digas hijole que bruto que útil agregar un total a esta tabla pero lo más interesante aparece cuando aplicas .subtotal()
a un objeto .groupby()
datos.groupby(['species', 'sex'])[['island']].count()
datos.groupby(['species', 'sex'])[['island']].count().stb.subtotal()
Para hacer algo así en pandas tendrías que hacer algo como
datos.pivot_table(index=['species', 'sex',], values="culmen_length_mm", aggfunc='count', margins = True)
Esas son las 3 funcionalidades que sidetable
agrega a pandas: .missing()
, .freq()
y .subtotal()
.
Personalmente, voy a utilizar sidetable
en todos los cursos y talleres que imparta de ahora en adelante. Ofrece el poder de pandas pero más accesible.
Bonus
Aunque me encanten los pingüinos la verdad es que yo no uso ese tipo de datos en mi día a día así que aquí hay un ejemplo de una tabla que produciría en el trabajo.
Para este ejercicio voy a utilizar pypums
una biblioteca mia que te permite descargar datos del censo de estados unidos de manera automatizable con python.
import pandas as pd
import sidetable
from pypums import ACS
california = ACS(state = 'california', year = 2017)
data = california.as_dataframe()
data.head()
Usando sidetable
puedo encontrar estadísticas sobre la población de California rápidamente. Algo que me gusta mucho de sidetable
es que puedo agregar el parámetro value
para pasarle una columna que va a utilizar para la agregación de mis datos. En este caso, uso PWGTP
(person weight) o factor de expansión. Esto es "lo que vale" cada observación en mis datos ya que estos datos son una muestra de la población total de estados unidos (es la American Community Survey que encuesta más o menos 1% de la población anualmente).
data.stb.freq(['SEX'], value = 'PWGTP', style = True)
Algo que yo he hecho muchas veces en mi trabajo anterior era investigar cuantas personas en California de X edad son inmigrantes, por ejemplo. Este número serviría de denominador en otras estadísticas.
Ya que tenemos los datos, agrupo la columna AGEP
que tiene los valores de las edades.
mask_17 = data['AGEP'] < 18
data.loc[mask_17, 'AGE_BIN'] = '0 - 17'
# 18 - 25
mask_25 = (data['AGEP'] >= 18) & (data['AGEP'] <= 25)
data.loc[mask_25, 'AGE_BIN'] = '18 - 25'
# 26 - 35
mask_35 = (data['AGEP'] >= 26) & (data['AGEP'] <= 35)
data.loc[mask_35, 'AGE_BIN'] = '26 - 35'
# 36 - 45
mask_45 = (data['AGEP'] >= 36) & (data['AGEP'] <= 45)
data.loc[mask_45, 'AGE_BIN'] = '36 - 45'
# 46 - 55
mask_55 = (data['AGEP'] >= 46) & (data['AGEP'] <= 55)
data.loc[mask_55, 'AGE_BIN'] = '46 - 55'
# 56 - 65
mask_65 = (data['AGEP'] >= 56) & (data['AGEP'] <= 65)
data.loc[mask_65, 'AGE_BIN'] = '56 - 65'
# 65+
mask_65plus = (data['AGEP'] >= 66)
data.loc[mask_65plus, 'AGE_BIN'] = '65+'
age_bins = [
'0 - 17',
'18 - 25',
'26 - 35',
'36 - 45',
'46 - 55',
'56 - 65',
'65+',
]
data['AGE_BIN'] = pd.Categorical(data['AGE_BIN'], categories = age_bins, ordered = True)
Y ya podemos ver por ejemplo, cuantos hombres y mujeres (abajo con el binarismo 👎) hay en cada grupo.
data.stb.freq(['SEX', 'AGE_BIN'], value = 'PWGTP', style = True)
Para limpiar los datos un poco (para presentarlos en alguna junta o algo) puedo cambiar los valores (que obtuve del diccionario de datos: https://www2.census.gov/programs-surveys/acs/tech_docs/pums/data_dict/PUMS_Data_Dictionary_2018.pdf?#)
data_dict_sex = {
1: 'Male',
2: 'Female',
}
data_dict_cit = {
1: "Born in the U.S.",
2: "Born in Puerto Rico, Guam, the U.S. Virgin Islands, or the Northern Marianas",
3: "Born abroad of American parent(s)",
4: "U.S. citizen by naturalization",
5: "Not a citizen of the U.S.",
}
data['SEX'] = data['SEX'].map(data_dict_sex)
data['CIT'] = data['CIT'].map(data_dict_cit)
data['CIT'] = pd.Categorical(data['CIT'], categories = data_dict_cit.values(), ordered = True)
¡Y listo!
data.stb.freq(['CIT', 'AGE_BIN', 'SEX'], value = 'PWGTP', style = True, sort_cols=True)
O si quiero solamente aquellas personas que no nacieron en estados unidos
mask_noncitizen = data['CIT'] == 'Not a citizen of the U.S.'
data[mask_noncitizen].stb.freq(['CIT', 'AGE_BIN', 'SEX'], value = 'PWGTP', style = True, sort_cols=True)
¡Una visualización rapidín!
import altair as alt
# nota que le no estoy usando style = True
vis_data = data[mask_noncitizen].stb.freq(['CIT', 'AGE_BIN', 'SEX'], value = 'PWGTP', sort_cols=True)
alt.Chart(vis_data).mark_bar().encode(
x='PWGTP:Q',
y='SEX:O',
color='SEX:N',
row='AGE_BIN:O'
).properties(
title = "No. de personas no nacidas en EEUU en California (ACS 2017)"
)
En conclusión, sidetable
es una herramienta sencilla, fácil de usar y muy útil.
Créditos:
Aprendí de sidetable
en el podcast python bytes en el episodio #186 pero mientras escribía esta nota encontré este blog en twitter que también esta explorando sidetable
pero en inglés: https://beta.deepnote.com/article/sidetable-pandas-methods-you-didnt-know-you-needed así que gracias a ambos recursos 🤓
¿Qué te pareció la nota? Mandanos un tuit a @tacosdedatos o envianos un correo a ✉️ sugerencias@tacosdedatos.com. Y recuerda que puedes subscribirte a nuestro boletín semanal aquí debajo.
Subscríbete a 🌮 tacos de datos | Aprende visualización de datos en español.
Recibe las mejores publicaciones directamente a tu caja de entrada