Pandas groupby: Como obter uma união de cordas

122

Eu tenho um quadro de dados 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       !

Chamando

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

retornará

A
1    1.615586
2    0.421821
3    0.463468
4    0.643961

Agora eu gostaria de fazer "o mesmo" para a coluna "C". Como essa coluna contém cadeias, sum () não funciona (embora você possa pensar que concatenaria as cadeias). O que eu realmente gostaria de ver é uma lista ou conjunto de strings para cada grupo, ou seja,

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

Eu tenho tentado encontrar maneiras de fazer isso.

Series.unique () ( http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.unique.html ) não funciona, embora

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

é um

pandas.core.groupby.SeriesGroupBy object

então eu esperava que qualquer método da série funcionasse. Alguma ideia?

Anne
fonte

Respostas:

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

Quando você aplica sua própria função, não há exclusões automáticas de colunas não numéricas. Esta é mais lento, porém, que a aplicação de .sum()aogroupby

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 padrão 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

Você pode fazer praticamente o que quiser

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

Fazendo isso em um quadro inteiro, um grupo de cada vez. A chave é retornar umSeries

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
fonte
Parece estas operações são agora vectorized eliminando a necessidade de applye lambdas. Eu vim aqui me perguntando por que pandasrealmente concats e não retornar um erro ao somar seqüências de caracteres.
NelsonGon
1
Se você está tentando concat strings e adiciona um caractere no meio, a solução .agg recomendada pelo @voithos abaixo é muito mais rápida que a .apply recomendada aqui. Nos meus testes, eu estava ficando 5 a 10 vezes mais rápido.
Doubledown 12/09/19
70

Você pode usar o applymétodo para aplicar uma função arbitrária aos dados agrupados. Então, se você quer um conjunto, aplique set. Se você deseja uma lista, inscreva-se 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

Se você quiser algo mais, basta escrever uma função que faça o que você quer e depois applyaquilo.

BrenBarn
fonte
Está funcionando bem, mas a Coluna A está ausente.
Vineesh TP
@ VineshTP: a coluna A foi usada como a coluna de agrupamento, portanto está no índice, como você pode ver no exemplo. Você pode recuperá-lo como uma coluna usando .reset_index().
BrenBarn 9/10/19
30

Você pode usar a função aggregate(ou agg) para concatenar os valores. (Código não testado)

df.groupby('A')['B'].agg(lambda col: ''.join(col))
voithos
fonte
Realmente funciona. Surpreendente. Como o @voithos mencionou "não testado", eu não estava muito otimista. Bit Eu testei sua versão como uma entrada em um dicionário agg e funcionou como pretendido: .agg ({'tp': 'sum', 'BaseWgt': 'max', 'TP_short': lambda col: ',' .join (col)}) Made my day #
matthhias
2
Se você está tentando concatear seqüências de caracteres com algum tipo de separador, achei essa sugestão .agg muito mais rápida que .apply. Para um conjunto de dados de 600k + seqüências de texto, obtive resultados idênticos 5-10x mais rápido.
Doubledown 12/09/19
14

Você pode tentar o seguinte:

df.groupby('A').agg({'B':'sum','C':'-'.join})
user3241146
fonte
2
Da avaliação: você poderia adicionar mais explicações à sua resposta?
toti08
1
Groupby é aplicado na coluna 'A' e com a função agg, eu poderia usar funções diferentes em colunas diferentes, somar os elementos na coluna 'C', concatenar os elementos na coluna 'C' e inserir um '-' entre as palavras
user3241146
8

uma solução simples seria:

>>> df.groupby(['A','B']).c.unique().reset_index()
UserYmY
fonte
essa deve ser a resposta certa. faz você responder de forma limpa. Muito obrigado!
27618 imsrgadich
Se no caso de alguém está interessado em aderir ao conteúdo da lista em uma string df.groupby(['A','B']).c.unique().apply(lambda x: ';'.join(x)).reset_index()
Vivek-Ananth
8

Agregações nomeadas com pandas >= 0.25.0

Desde a versão 0.25.0 do pandas, nomeamos agregações onde podemos agrupar, agregar e ao mesmo tempo atribuir novos nomes às nossas colunas. Dessa forma, não obteremos as colunas MultiIndex, e os nomes das colunas farão mais sentido, considerando os dados que eles contêm:


agregue e obtenha uma lista de strings

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 e juntar as cordas

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
fonte
6

Se você deseja sobrescrever a coluna B no quadro de dados, isso deve funcionar:

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

Seguindo a boa resposta de @ Erfan, na maioria das vezes em uma análise de valores agregados, você deseja as combinações possíveis únicas desses valores de caracteres existentes:

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