Los pandas obtienen los primeros registros n dentro de cada grupo


162

Supongamos que tengo pandas DataFrame como este:

>>> df = pd.DataFrame({'id':[1,1,1,2,2,2,2,3,4],'value':[1,2,3,1,2,3,4,1,1]})
>>> df
   id  value
0   1      1
1   1      2
2   1      3
3   2      1
4   2      2
5   2      3
6   2      4
7   3      1
8   4      1

Quiero obtener un nuevo DataFrame con los 2 registros principales para cada ID, como este:

   id  value
0   1      1
1   1      2
3   2      1
4   2      2
7   3      1
8   4      1

Puedo hacerlo numerando registros dentro de un grupo tras otro por:

>>> dfN = df.groupby('id').apply(lambda x:x['value'].reset_index()).reset_index()
>>> dfN
   id  level_1  index  value
0   1        0      0      1
1   1        1      1      2
2   1        2      2      3
3   2        0      3      1
4   2        1      4      2
5   2        2      5      3
6   2        3      6      4
7   3        0      7      1
8   4        0      8      1
>>> dfN[dfN['level_1'] <= 1][['id', 'value']]
   id  value
0   1      1
1   1      2
3   2      1
4   2      2
7   3      1
8   4      1

Pero, ¿hay un enfoque más efectivo / elegante para hacer esto? Y también hay un enfoque más elegante para los registros de números dentro de cada grupo (como la función de ventana SQL row_number () ).


Posible duplicado del marco
ssoler

1
"top-n" no significa "las n filas más altas / primeras / cabeceras", ¡como si estuviera buscando! Significa "las n filas con los valores más grandes".
smci

Respuestas:


181

Has probado df.groupby('id').head(2)

Ouput generado:

>>> df.groupby('id').head(2)
       id  value
id             
1  0   1      1
   1   1      2 
2  3   2      1
   4   2      2
3  7   3      1
4  8   4      1

(Tenga en cuenta que es posible que deba ordenar / ordenar antes, según sus datos)

EDITAR: Según lo mencionado por el interrogador, use df.groupby('id').head(2).reset_index(drop=True)para eliminar el índice múltiple y aplanar los resultados.

>>> df.groupby('id').head(2).reset_index(drop=True)
    id  value
0   1      1
1   1      2
2   2      1
3   2      2
4   3      1
5   4      1

1
Sí, creo que eso es todo. Pasó por alto esto de alguna manera. ¿Conoces una buena manera de numerar registros dentro del grupo?
Roman Pekar

44
Para obtener resultados que necesito, también agregué.reset_index(drop=True)
Roman Pekar el

1
github.com/pydata/pandas/pull/5510 acaba de fusionarse; estará en 0.13, nuevo método para hacer exactamente esto llamado cumcount(numere los registros en cada grupo)
Jeff

1
@Jeff buenas noticias. Desearía tener más tiempo para contribuir a Pandas :(
Roman Pekar

3
Para hacer que @dorvak su respuesta sea más completa, si quieres los 2 valores más pequeños, identonces hazlo df.sort_values(['id', 'value'], axis=0).groupby('id').head(2). Otro ejemplo, el mayor valor por ides dado por df.sort_values(['id', 'value'], axis=0).groupby('id').tail(1).
Elmex80s

130

Desde 0.14.1 , ahora puede hacer nlargesty nsmallesten un groupbyobjeto:

In [23]: df.groupby('id')['value'].nlargest(2)
Out[23]: 
id   
1   2    3
    1    2
2   6    4
    5    3
3   7    1
4   8    1
dtype: int64

Hay una ligera extrañeza que se obtiene el índice original en allí también, pero esto podría ser realmente útil en función de lo que su índice original era .

Si no le interesa, puede .reset_index(level=1, drop=True)deshacerse de él por completo.

(Nota: desde 0.17.1 también podrá hacer esto en un DataFrameGroupBy, pero por ahora solo funciona con Seriesy SeriesGroupBy.)


Hay una manera de llegar unique_limit(n)? ¿Como si quisiera los primeros n valores únicos? Si lo pido nlargest, clasificará todo el df que puede ser costoso
citynorman

2
¿Esto no funciona para los casos en que haces un agregado en el grupo? Por ejemplo, df.groupby([pd.Grouper(freq='M'), 'A'])['B'].count().nlargest(5, 'B') esto solo devuelve el top 5 en general de toda la serie, no por cada grupo
geominded

La afirmación de que ahora esto también es posible en DataFrameGroupBys parece ser falsa, la solicitud de extracción vinculada parece agregarse solo nlargesta DataFrames simples . Lo cual es bastante desafortunado, porque ¿qué pasa si desea seleccionar más de una columna?
oulenz

7

A veces, ordenar todos los datos por adelantado lleva mucho tiempo. Podemos agrupar primero y hacer topk para cada grupo:

g = df.groupby(['id']).apply(lambda x: x.nlargest(topk,['value'])).reset_index(drop=True)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.