Pandas groupby: Cómo conseguir una unión de cuerdas

122

Tengo un marco de datos como este:

   A         B       C
0  1  0.749065    This
1  2  0.301084      is
2  3  0.463468       a
3  4  0.643961  random
4  1  0.866521  string
5  2  0.120737       !

Vocación

In [10]: print df.groupby("A")["B"].sum()

volverá

A
1    1.615586
2    0.421821
3    0.463468
4    0.643961

Ahora me gustaría hacer "lo mismo" para la columna "C". Debido a que esa columna contiene cadenas, sum () no funciona (aunque podría pensar que concatenaría las cadenas). Lo que realmente me gustaría ver es una lista o un conjunto de cadenas para cada grupo, es decir

A
1    {This, string}
2    {is, !}
3    {a}
4    {random}

He estado tratando de encontrar formas de hacer esto.

Series.unique () ( http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.unique.html ) no funciona, aunque

df.groupby("A")["B"]

es un

pandas.core.groupby.SeriesGroupBy object

así que esperaba que cualquier método de Series funcionara. ¿Algunas ideas?

Ana
fuente

Respuestas:

178
In [4]: df = read_csv(StringIO(data),sep='\s+')

In [5]: df
Out[5]: 
   A         B       C
0  1  0.749065    This
1  2  0.301084      is
2  3  0.463468       a
3  4  0.643961  random
4  1  0.866521  string
5  2  0.120737       !

In [6]: df.dtypes
Out[6]: 
A      int64
B    float64
C     object
dtype: object

Cuando aplica su propia función, no hay exclusiones automáticas de columnas no numéricas. Esto es más lento, sin embargo, que la aplicación de .sum()lagroupby

In [8]: df.groupby('A').apply(lambda x: x.sum())
Out[8]: 
   A         B           C
A                         
1  2  1.615586  Thisstring
2  4  0.421821         is!
3  3  0.463468           a
4  4  0.643961      random

sum por defecto concatena

In [9]: df.groupby('A')['C'].apply(lambda x: x.sum())
Out[9]: 
A
1    Thisstring
2           is!
3             a
4        random
dtype: object

Puedes hacer prácticamente lo que quieras

In [11]: df.groupby('A')['C'].apply(lambda x: "{%s}" % ', '.join(x))
Out[11]: 
A
1    {This, string}
2           {is, !}
3               {a}
4          {random}
dtype: object

Haciendo esto en un cuadro completo, un grupo a la vez. La clave es devolver unSeries

def f(x):
     return Series(dict(A = x['A'].sum(), 
                        B = x['B'].sum(), 
                        C = "{%s}" % ', '.join(x['C'])))

In [14]: df.groupby('A').apply(f)
Out[14]: 
   A         B               C
A                             
1  2  1.615586  {This, string}
2  4  0.421821         {is, !}
3  3  0.463468             {a}
4  4  0.643961        {random}
Jeff
fuente
Parece que estas operaciones ahora están vectorizadas eliminando la necesidad de applyy lambdas. Vine aquí preguntándome por qué en pandasrealidad concats y no devuelve un error al sumar cadenas.
NelsonGon
1
Si está intentando concatizar cadenas y agregar un carácter en el medio, la solución .agg recomendada por @voithos a continuación es mucho más rápida que la .apply recomendada aquí. En mis pruebas, me estaba volviendo 5-10 veces más rápido.
Doubledown
70

Puede utilizar el applymétodo para aplicar una función arbitraria a los datos agrupados. Entonces, si quieres un juego, aplica set. Si quieres una lista, aplica list.

>>> d
   A       B
0  1    This
1  2      is
2  3       a
3  4  random
4  1  string
5  2       !
>>> d.groupby('A')['B'].apply(list)
A
1    [This, string]
2           [is, !]
3               [a]
4          [random]
dtype: object

Si quieres algo más, escribe una función que haga lo que quieras y luego applyeso.

BrenBarn
fuente
Funciona bien, pero falta la columna A.
Vineesh TP
@VineeshTP: La columna A se usó como columna de agrupación, por lo que está en el índice, como puede ver en el ejemplo. Puede recuperarlo como una columna usando .reset_index().
BrenBarn
30

Es posible que pueda utilizar la función aggregate(o agg) para concatenar los valores. (Código no probado)

df.groupby('A')['B'].agg(lambda col: ''.join(col))
voithos
fuente
Realmente funciona. Asombroso. Como @voithos mencionó "no probado", no fui muy optimista. Bit probé su versión como una entrada en un diccionario agg y funcionó como se esperaba: .agg ({'tp': 'sum', 'BaseWgt': 'max', 'TP_short': lambda col: ',' .join (col)}) hizo mi día
matthhias
2
Si está intentando concatenar cadenas junto con algún tipo de separador, he encontrado que esta sugerencia de .agg es mucho más rápida que .apply. Para un conjunto de datos de más de 600k cadenas de texto, obtuve resultados idénticos entre 5 y 10 veces más rápido.
Doubledown
14

Puedes probar esto:

df.groupby('A').agg({'B':'sum','C':'-'.join})
usuario3241146
fuente
2
De la revisión: ¿podría agregar más explicación a su respuesta?
toti08
1
Groupby se aplica en la columna 'A' y con la función agg podría usar diferentes funciones en diferentes columnas, decir sumar los elementos en la columna 'C', concatenar los elementos en la columna 'C' mientras se inserta un '-' entre las palabras
user3241146
8

una solución simple sería:

>>> df.groupby(['A','B']).c.unique().reset_index()
UserYmY
fuente
esta debería ser la respuesta correcta. te da una respuesta limpia. ¡muchas gracias!
imsrgadich
Si, en caso de que alguien esté interesado en unir el contenido de la lista en una cadena df.groupby(['A','B']).c.unique().apply(lambda x: ';'.join(x)).reset_index()
Vivek-Ananth
8

Agregaciones con nombre con pandas >= 0.25.0

Desde la versión 0.25.0 de pandas, hemos nombrado agregaciones en las que podemos agrupar, agregar y al mismo tiempo asignar nuevos nombres a nuestras columnas. De esta forma no obtendremos las columnas MultiIndex, y los nombres de las columnas tienen más sentido dados los datos que contienen:


agregar y obtener una lista de cadenas

grp = df.groupby('A').agg(B_sum=('B','sum'),
                          C=('C', list)).reset_index()

print(grp)
   A     B_sum               C
0  1  1.615586  [This, string]
1  2  0.421821         [is, !]
2  3  0.463468             [a]
3  4  0.643961        [random]

agregar y unir las cuerdas

grp = df.groupby('A').agg(B_sum=('B','sum'),
                          C=('C', ', '.join)).reset_index()

print(grp)
   A     B_sum             C
0  1  1.615586  This, string
1  2  0.421821         is, !
2  3  0.463468             a
3  4  0.643961        random
Erfan
fuente
6

Si desea sobrescribir la columna B en el marco de datos, esto debería funcionar:

    df = df.groupby('A',as_index=False).agg(lambda x:'\n'.join(x))
Amit
fuente
2

Siguiendo la buena respuesta de @ Erfan, la mayoría de las veces, en un análisis de valores agregados, desea las combinaciones posibles únicas de estos valores de caracteres existentes:

unique_chars = lambda x: ', '.join(x.unique())
(df
 .groupby(['A'])
 .agg({'C': unique_chars}))
Paul Rougieux
fuente