Ordenación de pandas por grupo y columna.

Dado el siguiente dataframe

In [31]: rand = np.random.RandomState(1) df = pd.DataFrame({'A': ['foo', 'bar', 'baz'] * 2, 'B': rand.randn(6), 'C': rand.rand(6) > .5}) In [32]: df Out[32]: ABC 0 foo 1.624345 False 1 bar -0.611756 True 2 baz -0.528172 False 3 foo -1.072969 True 4 bar 0.865408 False 5 baz -2.301539 True 

Me gustaría clasificarlo en grupos ( A ) por la sum agregada de B , y luego por el valor en C (no agregado). Así que básicamente consigue el orden de los grupos A con

 In [28]: df.groupby('A').sum().sort('B') Out[28]: BC A baz -2.829710 1 bar 0.253651 1 foo 0.551377 1 

Y luego por Verdadero / Falso, para que finalmente se vea así:

 In [30]: df.ix[[5, 2, 1, 4, 3, 0]] Out[30]: ABC 5 baz -2.301539 True 2 baz -0.528172 False 1 bar -0.611756 True 4 bar 0.865408 False 3 foo -1.072969 True 0 foo 1.624345 False 

¿Cómo se puede hacer esto?

    Groupby A:

     In [0]: grp = df.groupby('A') 

    Dentro de cada grupo, sume sobre B y difunda los valores usando la transformación. Entonces ordena por B:

     In [1]: grp[['B']].transform(sum).sort('B') Out[1]: B 2 -2.829710 5 -2.829710 1 0.253651 4 0.253651 0 0.551377 3 0.551377 

    Indexe el df original pasando el índice desde arriba. Esto reordenará los valores A por la sum agregada de los valores B:

     In [2]: sort1 = df.ix[grp[['B']].transform(sum).sort('B').index] In [3]: sort1 Out[3]: ABC 2 baz -0.528172 False 5 baz -2.301539 True 1 bar -0.611756 True 4 bar 0.865408 False 0 foo 1.624345 False 3 foo -1.072969 True 

    Finalmente, ordene los valores ‘C’ dentro de los grupos de ‘A’ usando la opción sort=False para conservar el orden A del paso 1:

     In [4]: f = lambda x: x.sort('C', ascending=False) In [5]: sort2 = sort1.groupby('A', sort=False).apply(f) In [6]: sort2 Out[6]: ABC A baz 5 baz -2.301539 True 2 baz -0.528172 False bar 1 bar -0.611756 True 4 bar 0.865408 False foo 3 foo -1.072969 True 0 foo 1.624345 False 

    Limpie el índice df usando reset_index con drop=True :

     In [7]: sort2.reset_index(0, drop=True) Out[7]: ABC 5 baz -2.301539 True 2 baz -0.528172 False 1 bar -0.611756 True 4 bar 0.865408 False 3 foo -1.072969 True 0 foo 1.624345 False 

    Aquí hay un enfoque más conciso …

     df['a_bsum'] = df.groupby('A')['B'].transform(sum) df.sort(['a_bsum','C'], ascending=[True, False]).drop('a_bsum', axis=1) 

    La primera línea agrega una columna al dataframe con la sum grupal. La segunda línea realiza la ordenación y luego elimina la columna adicional.

    Resultado:

      ABC 5 baz -2.301539 True 2 baz -0.528172 False 1 bar -0.611756 True 4 bar 0.865408 False 3 foo -1.072969 True 0 foo 1.624345 False 

    NOTA: la sort está en desuso, use sort_values en sort_values lugar

    Una forma de hacer esto es insertar una columna ficticia con las sums para ordenar:

     In [10]: sum_B_over_A = df.groupby('A').sum().B In [11]: sum_B_over_A Out[11]: A bar 0.253652 baz -2.829711 foo 0.551376 Name: B in [12]: df['sum_B_over_A'] = df.A.apply(sum_B_over_A.get_value) In [13]: df Out[13]: ABC sum_B_over_A 0 foo 1.624345 False 0.551376 1 bar -0.611756 True 0.253652 2 baz -0.528172 False -2.829711 3 foo -1.072969 True 0.551376 4 bar 0.865408 False 0.253652 5 baz -2.301539 True -2.829711 In [14]: df.sort(['sum_B_over_A', 'A', 'B']) Out[14]: ABC sum_B_over_A 5 baz -2.301539 True -2.829711 2 baz -0.528172 False -2.829711 1 bar -0.611756 True 0.253652 4 bar 0.865408 False 0.253652 3 foo -1.072969 True 0.551376 0 foo 1.624345 False 0.551376 

    y tal vez deberías soltar la fila ficticia:

     In [15]: df.sort(['sum_B_over_A', 'A', 'B']).drop('sum_B_over_A', axis=1) Out[15]: ABC 5 baz -2.301539 True 2 baz -0.528172 False 1 bar -0.611756 True 4 bar 0.865408 False 3 foo -1.072969 True 0 foo 1.624345 False