¿Cómo agrupar filas del marco de datos en la lista en pandas groupby?

274

Tengo un marco de datos de pandas dfcomo:

a b
A 1
A 2
B 5
B 5
B 4
C 6

Quiero agrupar por la primera columna y obtener la segunda columna como listas en filas :

A [1,2]
B [5,5,4]
C [6]

¿Es posible hacer algo así usando pandas groupby?

Abhishek Thakur
fuente

Respuestas:

394

Puede hacer esto usando groupbypara agrupar en la columna de interés y luego apply listpara cada grupo:

In [1]: df = pd.DataFrame( {'a':['A','A','B','B','B','C'], 'b':[1,2,5,5,4,6]})
        df

Out[1]: 
   a  b
0  A  1
1  A  2
2  B  5
3  B  5
4  B  4
5  C  6

In [2]: df.groupby('a')['b'].apply(list)
Out[2]: 
a
A       [1, 2]
B    [5, 5, 4]
C          [6]
Name: b, dtype: object

In [3]: df1 = df.groupby('a')['b'].apply(list).reset_index(name='new')
        df1
Out[3]: 
   a        new
0  A     [1, 2]
1  B  [5, 5, 4]
2  C        [6]
EdChum
fuente
77
Esto lleva mucho tiempo si el conjunto de datos es enorme, digamos 10 millones de filas. ¿Hay alguna forma más rápida de hacer esto? Sin embargo, el número de exclusivos en 'a' es de alrededor de 500k
Abhishek Thakur
66
groupby es notoriamente lento y tiene mucha memoria, lo que podría hacer es ordenar por la columna A, luego encontrar idxmin e idxmax (probablemente almacene esto en un dict) y usar esto para cortar su marco de datos sería más rápido, creo
EdChum
1
Cuando probé esta solución con mi problema (que tenía varias columnas para groupBy y group), no funcionó: los pandas enviaron 'La función no se reduce'. Luego solía tupleseguir la segunda respuesta aquí: stackoverflow.com/questions/19530568/… . Consulte la segunda respuesta en stackoverflow.com/questions/27439023/… para obtener una explicación.
Andarin
Esta solución es buena, pero ¿hay alguna forma de almacenar un conjunto de listas, lo que significa que puedo eliminar los duplicados y luego almacenarlos?
Sriram Arvind Lakshmanakumar
1
@PoeteMaudit Lo siento, no entiendo lo que estás preguntando y hacer preguntas en los comentarios es una mala forma en SO. ¿Estás preguntando cómo concatenar múltiples columnas en una sola lista?
EdChum
47

Si el rendimiento es importante, baje al nivel numpy:

import numpy as np

df = pd.DataFrame({'a': np.random.randint(0, 60, 600), 'b': [1, 2, 5, 5, 4, 6]*100})

def f(df):
         keys, values = df.sort_values('a').values.T
         ukeys, index = np.unique(keys, True)
         arrays = np.split(values, index[1:])
         df2 = pd.DataFrame({'a':ukeys, 'b':[list(a) for a in arrays]})
         return df2

Pruebas:

In [301]: %timeit f(df)
1000 loops, best of 3: 1.64 ms per loop

In [302]: %timeit df.groupby('a')['b'].apply(list)
100 loops, best of 3: 5.26 ms per loop
BM
fuente
8
¿Cómo podríamos usar esto si estamos agrupando por dos o más teclas, por ejemplo, con en .groupby([df.index.month, df.index.day])lugar de solo .groupby('a')?
ru111
25

Una forma práctica de lograr esto sería:

df.groupby('a').agg({'b':lambda x: list(x)})

Examine la escritura de agregaciones personalizadas: https://www.kaggle.com/akshaysehgal/how-to-group-by-aggregate-using-py

Anamika Modi
fuente
55
lambda args: f(args)es equivalente af
BallpointBen
66
En realidad, solo agg(list)es suficiente. Ver también aquí .
cs95
!! Estaba buscando en Google alguna sintaxis y me di cuenta de que mi propio cuaderno estaba referenciado para la solución jajaja. Gracias por vincular esto. Solo para agregar, dado que 'list' no es una función en serie, deberá usarlo con apply df.groupby('a').apply(list)o usarlo con agg como parte de un dict df.groupby('a').agg({'b':list}). También podría usarlo con lambda (que recomiendo) ya que puede hacer mucho más con él. Ejemplo: df.groupby('a').agg({'c':'first', 'b': lambda x: x.unique().tolist()})que le permite aplicar una función de serie a col c y una función única y luego una lista a col b.
Akshay Sehgal
21

Como decías, el groupbymétodo de un pd.DataFrameobjeto puede hacer el trabajo.

Ejemplo

 L = ['A','A','B','B','B','C']
 N = [1,2,5,5,4,6]

 import pandas as pd
 df = pd.DataFrame(zip(L,N),columns = list('LN'))


 groups = df.groupby(df.L)

 groups.groups
      {'A': [0, 1], 'B': [2, 3, 4], 'C': [5]}

que proporciona una descripción de los grupos basada en índices.

Para obtener elementos de grupos individuales, puede hacer, por ejemplo

 groups.get_group('A')

     L  N
  0  A  1
  1  A  2

  groups.get_group('B')

     L  N
  2  B  5
  3  B  5
  4  B  4
Acorbe
fuente
21

Para resolver esto para varias columnas de un marco de datos:

In [5]: df = pd.DataFrame( {'a':['A','A','B','B','B','C'], 'b':[1,2,5,5,4,6],'c'
   ...: :[3,3,3,4,4,4]})

In [6]: df
Out[6]: 
   a  b  c
0  A  1  3
1  A  2  3
2  B  5  3
3  B  5  4
4  B  4  4
5  C  6  4

In [7]: df.groupby('a').agg(lambda x: list(x))
Out[7]: 
           b          c
a                      
A     [1, 2]     [3, 3]
B  [5, 5, 4]  [3, 4, 4]
C        [6]        [4]

Esta respuesta se inspiró en la respuesta de Anamika Modi . ¡Gracias!

Markus Dutschke
fuente
12

Use cualquiera de los siguientes groupbyy aggrecetas.

# Setup
df = pd.DataFrame({
  'a': ['A', 'A', 'B', 'B', 'B', 'C'],
  'b': [1, 2, 5, 5, 4, 6],
  'c': ['x', 'y', 'z', 'x', 'y', 'z']
})
df

   a  b  c
0  A  1  x
1  A  2  y
2  B  5  z
3  B  5  x
4  B  4  y
5  C  6  z

Para agregar varias columnas como listas, use cualquiera de los siguientes:

df.groupby('a').agg(list)
df.groupby('a').agg(pd.Series.tolist)

           b          c
a                      
A     [1, 2]     [x, y]
B  [5, 5, 4]  [z, x, y]
C        [6]        [z]

Para hacer una lista grupal de una sola columna, convierta el groupby en un SeriesGroupByobjeto, luego llame SeriesGroupBy.agg. Utilizar,

df.groupby('a').agg({'b': list})  # 4.42 ms 
df.groupby('a')['b'].agg(list)    # 2.76 ms - faster

a
A       [1, 2]
B    [5, 5, 4]
C          [6]
Name: b, dtype: object
cs95
fuente
¿Están garantizados los métodos anteriores para preservar el orden? lo que significa que los elementos de la misma fila (pero diferentes columnas, b y c en su código de seguridad) tendrá el mismo índice en las listas resultantes?
Kai
@Kai oh, buena pregunta. Si y no. GroupBy ordena la salida por los valores clave del mero. Sin embargo, la clasificación es generalmente estable, por lo que se conserva el orden relativo por grupo. Para deshabilitar el comportamiento de clasificación por completo, use groupby(..., sort=False). Aquí, no habría diferencia ya que estoy agrupando en la columna A que ya está ordenada.
cs95
lo siento, no entiendo tu respuesta. ¿Puedes explicar con más detalle? Creo que esto merece su propia pregunta ..
Kai
1
Esta es una muy buena respuesta! ¿Existe también una manera de hacer que los valores de la lista sean únicos? algo como .agg (pd.Series.tolist.unique) tal vez?
Federico Gentile
1
@FedericoGentile puedes usar una lambda. Aquí hay una manera:df.groupby('a')['b'].agg(lambda x: list(set(x)))
cs95
7

Si busca una lista única mientras agrupa varias columnas, esto probablemente podría ayudar:

df.groupby('a').agg(lambda x: list(set(x))).reset_index()
Vanshika
fuente
2

Vamos a usar df.groupbycon lista y Seriesconstructor

pd.Series({x : y.b.tolist() for x , y in df.groupby('a')})
Out[664]: 
A       [1, 2]
B    [5, 5, 4]
C          [6]
dtype: object
YOBEN_S
fuente
2

Es hora de usar en agglugar de apply.

Cuando

df = pd.DataFrame( {'a':['A','A','B','B','B','C'], 'b':[1,2,5,5,4,6], 'c': [1,2,5,5,4,6]})

Si desea que varias columnas se apilen en la lista, obtenga pd.DataFrame

df.groupby('a')[['b', 'c']].agg(list)
# or 
df.groupby('a').agg(list)

Si quieres una sola columna en la lista, da como resultado ps.Series

df.groupby('a')['b'].agg(list)
#or
df.groupby('a')['b'].apply(list)

Tenga en cuenta que el resultado pd.DataFramees aproximadamente 10 veces más lento que ps.Seriescuando solo agrega una sola columna, úsela en mayúsculas y minúsculas.

Mitril
fuente
0

Aquí he agrupado elementos con "|" como separador

    import pandas as pd

    df = pd.read_csv('input.csv')

    df
    Out[1]:
      Area  Keywords
    0  A  1
    1  A  2
    2  B  5
    3  B  5
    4  B  4
    5  C  6

    df.dropna(inplace =  True)
    df['Area']=df['Area'].apply(lambda x:x.lower().strip())
    print df.columns
    df_op = df.groupby('Area').agg({"Keywords":lambda x : "|".join(x)})

    df_op.to_csv('output.csv')
    Out[2]:
    df_op
    Area  Keywords

    A       [1| 2]
    B    [5| 5| 4]
    C          [6]
Ganesh Kharad
fuente
0

La forma más fácil que he visto no es lograr la mayor parte de lo mismo al menos para una columna que es similar a la respuesta de Anamika solo con la sintaxis de tupla para la función agregada.

df.groupby('a').agg(b=('b','unique'), c=('c','unique'))
Metrd
fuente