El siguiente es un ejemplo de elementos calificados con 1, 2 o 3 estrellas. Estoy tratando de contar todas las combinaciones de calificaciones de artículos (estrellas) por mes.
En el siguiente ejemplo, el elemento 10 se calificó en el mes 1 y tiene dos calificaciones iguales a 1, una calificación igual a 2 y una calificación igual a 3.
inp = pd.DataFrame({'month':[1,1,1,1,1,2,2,2], 'item':[10,10,10,10,20,20,20,20], 'star':[1,2,1,3,3,2,2,3]} ) month item star 0 1 10 1 1 1 10 2 2 1 10 1 3 1 10 3 4 1 20 3 5 2 20 2 6 2 20 2 7 2 20 3
Para el cuadro de entrada anterior, la salida debe ser:
month item star_1_cnt star_2_cnt star_3_cnt 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
Estoy tratando de resolver el problema comenzando con el siguiente código, cuyo resultado aún debe convertirse al formato deseado del cuadro de salida y da las respuestas incorrectas:
1 20 3 (1, 1) 2 20 3 (1, 1)
De todos modos, debería haber una mejor manera de crear la tabla de salida y luego finalizar esta:
months = [1,2] items = [10,20] stars = [1,2,3] d = {'month': [], 'item': [], 'star': [], 'star_cnts': [] } for month in months: for star in stars: for item in items: star_cnts=dict(inp[(inp['item']==item) & (inp['star']==star)].value_counts()).values() d['month'].append(month) d['item'].append(item) d['star'].append(star) d['star_cnts'].append(star_cnts) pd.DataFrame(d) month item star star_cnts 0 1 10 1 (2) 1 1 20 1 () 2 1 10 2 (1) 3 1 20 2 (2) 4 1 10 3 (1) 5 1 20 3 (1, 1) 6 2 10 1 (2) 7 2 20 1 () 8 2 10 2 (1) 9 2 20 2 (2) 10 2 10 3 (1) 11 2 20 3 (1, 1)
Series.value_counts
+ Series.unstack
para convertir a marco de datos
out = inp.value_counts()\ .unstack('star', fill_value=0)\ .rename(lambda x: f'star_{x}_cnt', axis=1)\ .reset_index().rename_axis(columns=None) print(out) month item star_1_cnt star_2_cnt star_3_cnt 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
Puede usar value_counts
en todo el DataFrame y desapilar:
out = ( inp.value_counts() .unstack('star', fill_value=0) )
Alternativamente, puede usar crosstab
.
Como requiere datos 1D como entrada, puede usar tuplas:
cols = ['month','item'] out = pd.crosstab(inp[cols].apply(tuple, axis=1), inp['star']) out.index = pd.MultiIndex.from_tuples(out.index, names=cols)
Producción:
star 1 2 3 month item 1 10 2 1 1 20 0 0 1 2 20 0 2 1
Esto ya fue muy bien demostrado por @ansev
(inp.value_counts() .unstack('star', fill_value=0) .rename(lambda c: f'star_{c}_cnt', axis=1) .reset_index() .rename_axis(columns=None) )
Producción:
month item star_1_cnt star_2_cnt star_3_cnt 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
Una opción, con pivot_table :
(inp .pivot_table(index=['month', 'item'], values = 'star', columns='star', aggfunc='size', fill_value = 0) .rename(columns = lambda col: f"star_{col}_cnt") .rename_axis(columns = None) .reset_index() ) month item star_1_cnt star_2_cnt star_3_cnt 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
Otra opción, con groupby (pivot_table es un contenedor alrededor de groupby + unstack):
(inp .groupby(['month', 'item', 'star']) .size() .unstack(fill_value = 0) .rename(columns = lambda col: f"star_{col}_cnt") .rename_axis(columns = None) .reset_index() ) month item star_1_cnt star_2_cnt star_3_cnt 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
Otra opción, con pd.get_dummies
+ groupby
:
(pd.get_dummies(inp, columns=['star']) .groupby(['month', 'item']) .sum() .add_suffix('_cnt') .reset_index() ) month item star_1_cnt star_2_cnt star_3_cnt 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
en cuanto al rendimiento, solo las pruebas pueden decir: esperaría que pivot_table fuera más lento que groupby
Esto parece un buen problema para pd.get_dummies
:
new_df = ( pd.concat([df, pd.get_dummies(df['star'])], axis=1) .groupby(['month', 'item'], as_index=False) [df['star'].unique()] .sum() )
Producción:
>>> new_df month item 1 2 3 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
Renombrando, también:
u = df['star'].unique() new_df = ( pd.concat([df, pd.get_dummies(df['star'])], axis=1) .groupby(['month', 'item'], as_index=False) [u] .sum() .rename({k: f'star_{k}_cnt' for k in df['star'].unique()}, axis=1) )
Producción:
>>> new_df month item star_1_cnt star_2_cnt star_3_cnt 0 1 10 2 1 1 1 1 20 0 0 1 2 2 20 0 2 1
Una (o dos) líneas obligatorias:
# Renames the columns u = df['star'].unique() new_df = pd.concat([df, pd.get_dummies(df['star'])], axis=1).groupby(['month', 'item'], as_index=False)[u].sum().rename({k: f'star_{k}_cnt' for k in df['star'].unique()}, axis=1)