Disculpas si me han preguntado algo similar antes, busqué pero no pude encontrar una solución.

Mi conjunto de datos se parece a eso

data1 = {'Group':['Winner','Winner','Winner','Loser','Loser','Loser'],
        'MathStudy': ['Read','Read','Notes','Cheat','Cheat','Read'],
        'ScienceStudy': ['Notes','Read','Cheat','Cheat','Read','Notes']}
df1 = pd.DataFrame(data=data1)

enter image description here

Me gustaría obtener un% del total de cada categoría para cada grupo, como se muestra a continuación. En mi conjunto de datos, la cantidad de ganadores y perdedores cambia, por lo que se agradece una solución flexible. ingrese la descripción de la imagen aquí

¡Gracias de antemano!

0
AxW 27 feb. 2021 a las 00:03

3 respuestas

La mejor respuesta

Utilice DataFrame.melt con crosstab y { Parámetro {X2}}:

df1 = df1.melt('Group', var_name='Type')

df2 = pd.crosstab([df1['Group'], df1['Type']], df1['value'], normalize=0)
print (df2)
value                   Cheat     Notes      Read
Group  Type                                      
Loser  MathStudy     0.666667  0.000000  0.333333
       ScienceStudy  0.333333  0.333333  0.333333
Winner MathStudy     0.000000  0.333333  0.666667
       ScienceStudy  0.333333  0.333333  0.333333
 

Por último, si es necesario MultiIndex a las columnas con eliminar value nombre de la columna, agregue DataFrame.rename_axis con DataFrame.reset_index:

df2 = df2.rename_axis(columns=None).reset_index()
print (df2)
    Group          Type     Cheat     Notes      Read
0   Loser     MathStudy  0.666667  0.000000  0.333333
1   Loser  ScienceStudy  0.333333  0.333333  0.333333
2  Winner     MathStudy  0.000000  0.333333  0.666667
3  Winner  ScienceStudy  0.333333  0.333333  0.333333
4
jezrael 26 feb. 2021 a las 21:07

La solución de @jezrael es intuitiva y lo que haría yo de primera mano. Sin embargo, aprendí recientemente que melt generalmente tiene un desempeño deficiente. Aquí hay una alternativa si el rendimiento es importante, p. Ej. en códigos que se utilizan repetidamente:

g = df1.groupby('Group')
cols = ['MathStudy', 'ScienceStudy']
out = (pd.concat({col:g[col].value_counts(normalize=True) for col in cols})
   .unstack(level=-1, fill_value=0)
)

Con tiempo de ejecución:

2.9 ms ± 96.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

En comparación con el enfoque melt:

9.44 ms ± 261 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Salida:

                        Cheat     Notes      Read
MathStudy    Loser   0.666667  0.000000  0.333333
             Winner  0.000000  0.333333  0.666667
ScienceStudy Loser   0.333333  0.333333  0.333333
             Winner  0.333333  0.333333  0.333333

Nota : pd.crosstab es esencialmente groupby() con algo de contabilidad adicional. Y groupby en dos columnas suele ser mucho más lento.

3
Quang Hoang 26 feb. 2021 a las 21:22

Aquí hay otra alternativa:

g = df.set_index('Group').stack().str.get_dummies().groupby(level=[0,1]).sum()
g.div(g.sum(axis=1),axis=0).round(2)
1
rhug123 26 feb. 2021 a las 21:28