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 () ).

Roman Pekar
fuente
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
dorvak
fuente
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.)

LondonRob
fuente
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)
Chaffee Chen
fuente