Obter estatísticas para cada grupo (como contagem, média, etc.) usando os pandas GroupBy?

439

Eu tenho um quadro de dados dfe uso várias colunas para groupby:

df['col1','col2','col3','col4'].groupby(['col1','col2']).mean()

Da maneira acima, quase recebo a tabela (quadro de dados) necessária. O que está faltando é uma coluna adicional que contém o número de linhas em cada grupo. Em outras palavras, quero dizer, mas também gostaria de saber quantos números foram usados ​​para obter esses meios. Por exemplo, no primeiro grupo, existem 8 valores e, no segundo, 10 e assim por diante.

Resumindo: Como obtenho estatísticas em grupo para um dataframe?

romano
fonte

Respostas:

427

No groupbyobjeto, a aggfunção pode fazer uma lista para aplicar vários métodos de agregação ao mesmo tempo. Isso deve fornecer o resultado que você precisa:

df[['col1', 'col2', 'col3', 'col4']].groupby(['col1', 'col2']).agg(['mean', 'count'])
Boud
fonte
2
Eu acho que você precisa da referência da coluna para ser uma lista. Você quer dizer: df[['col1','col2','col3','col4']].groupby(['col1','col2']).agg(['mean', 'count'])
rysqui
43
Isso cria quatro colunas de contagem, mas como obter apenas uma? (A pergunta pede "uma coluna adicional" e é isso que eu gostaria também.)
Jaan
16
Por favor, veja minha resposta se você deseja obter apenas uma countcoluna por grupo.
Pedro M Duarte
E se eu tiver um número separado de contagens e, em vez de contar as linhas do tipo agrupado, preciso adicionar a coluna Contagens.
Abhishek Bhatia
@Jaan result = df['col1','col2','col3','col4'].groupby(['col1', 'col2']).mean() ; counts = times.groupby(['col1', 'col2']).size() ; result['count'] = counts
alvitawa
913

Resposta rápida:

A maneira mais simples de obter contagens de linhas por grupo é chamando .size(), que retorna Series:

df.groupby(['col1','col2']).size()


Normalmente, você deseja esse resultado como DataFrame(em vez de a Series) para poder:

df.groupby(['col1', 'col2']).size().reset_index(name='counts')


Se você quiser descobrir como calcular as contagens de linhas e outras estatísticas para cada grupo, continue lendo abaixo.


Exemplo detalhado:

Considere o seguinte exemplo de quadro de dados:

In [2]: df
Out[2]: 
  col1 col2  col3  col4  col5  col6
0    A    B  0.20 -0.61 -0.49  1.49
1    A    B -1.53 -1.01 -0.39  1.82
2    A    B -0.44  0.27  0.72  0.11
3    A    B  0.28 -1.32  0.38  0.18
4    C    D  0.12  0.59  0.81  0.66
5    C    D -0.13 -1.65 -1.64  0.50
6    C    D -1.42 -0.11 -0.18 -0.44
7    E    F -0.00  1.42 -0.26  1.17
8    E    F  0.91 -0.47  1.35 -0.34
9    G    H  1.48 -0.63 -1.14  0.17

Primeiro vamos usar .size()para obter a contagem de linhas:

In [3]: df.groupby(['col1', 'col2']).size()
Out[3]: 
col1  col2
A     B       4
C     D       3
E     F       2
G     H       1
dtype: int64

Então vamos usar .size().reset_index(name='counts')para obter a contagem de linhas:

In [4]: df.groupby(['col1', 'col2']).size().reset_index(name='counts')
Out[4]: 
  col1 col2  counts
0    A    B       4
1    C    D       3
2    E    F       2
3    G    H       1


Incluindo resultados para mais estatísticas

Quando você deseja calcular estatísticas sobre dados agrupados, geralmente é assim:

In [5]: (df
   ...: .groupby(['col1', 'col2'])
   ...: .agg({
   ...:     'col3': ['mean', 'count'], 
   ...:     'col4': ['median', 'min', 'count']
   ...: }))
Out[5]: 
            col4                  col3      
          median   min count      mean count
col1 col2                                   
A    B    -0.810 -1.32     4 -0.372500     4
C    D    -0.110 -1.65     3 -0.476667     3
E    F     0.475 -0.47     2  0.455000     2
G    H    -0.630 -0.63     1  1.480000     1

O resultado acima é um pouco chato de lidar por causa dos rótulos das colunas aninhadas e também porque as contagens de linhas são por coluna.

Para obter mais controle sobre a saída, costumo dividir as estatísticas em agregações individuais que depois combino join. Se parece com isso:

In [6]: gb = df.groupby(['col1', 'col2'])
   ...: counts = gb.size().to_frame(name='counts')
   ...: (counts
   ...:  .join(gb.agg({'col3': 'mean'}).rename(columns={'col3': 'col3_mean'}))
   ...:  .join(gb.agg({'col4': 'median'}).rename(columns={'col4': 'col4_median'}))
   ...:  .join(gb.agg({'col4': 'min'}).rename(columns={'col4': 'col4_min'}))
   ...:  .reset_index()
   ...: )
   ...: 
Out[6]: 
  col1 col2  counts  col3_mean  col4_median  col4_min
0    A    B       4  -0.372500       -0.810     -1.32
1    C    D       3  -0.476667       -0.110     -1.65
2    E    F       2   0.455000        0.475     -0.47
3    G    H       1   1.480000       -0.630     -0.63



Notas de rodapé

O código usado para gerar os dados de teste é mostrado abaixo:

In [1]: import numpy as np
   ...: import pandas as pd 
   ...: 
   ...: keys = np.array([
   ...:         ['A', 'B'],
   ...:         ['A', 'B'],
   ...:         ['A', 'B'],
   ...:         ['A', 'B'],
   ...:         ['C', 'D'],
   ...:         ['C', 'D'],
   ...:         ['C', 'D'],
   ...:         ['E', 'F'],
   ...:         ['E', 'F'],
   ...:         ['G', 'H'] 
   ...:         ])
   ...: 
   ...: df = pd.DataFrame(
   ...:     np.hstack([keys,np.random.randn(10,4).round(2)]), 
   ...:     columns = ['col1', 'col2', 'col3', 'col4', 'col5', 'col6']
   ...: )
   ...: 
   ...: df[['col3', 'col4', 'col5', 'col6']] = \
   ...:     df[['col3', 'col4', 'col5', 'col6']].astype(float)
   ...: 


Aviso Legal:

Se algumas das colunas que você está agregando tiverem valores nulos, você realmente deseja observar as contagens de linhas do grupo como uma agregação independente para cada coluna. Caso contrário, você poderá se enganar sobre quantos registros estão realmente sendo usados ​​para calcular coisas como a média, porque os pandas descartarão NaNentradas no cálculo da média sem informar sobre isso.

Pedro M Duarte
fonte
1
Ei, eu realmente gosto da sua solução, principalmente a última, onde você usa o encadeamento de métodos. No entanto, como muitas vezes é necessário aplicar funções de agregação diferentes a colunas diferentes, também é possível concaturar os quadros de dados resultantes usando pd.concat. Este talvez mais fácil de ler do que o encadeamento subsqeuent
Quickbeam2k1
4
solução agradável, mas para In [5]: counts_df = pd.DataFrame(df.groupby('col1').size().rename('counts')), talvez seja melhor para definir o tamanho () como uma nova coluna se você gostaria de manipular a trama de dados para análise posterior, que deve sercounts_df = pd.DataFrame(df.groupby('col1').size().reset_index(name='counts')
LancelotHolmes
2
Obrigado pelo bit "Incluindo resultados para mais estatísticas"! Como minha próxima pesquisa foi sobre o achatamento do multi-índice resultante nas colunas, vincularei à resposta aqui: stackoverflow.com/a/50558529/1026 #
Nickolay
Ótimo! Você poderia me dar uma dica de como adicionar isnullessa consulta para incluí -la em uma coluna também? 'col4': ['median', 'min', 'count', 'isnull']
precisa saber é
38

Uma função para governá-los todos: GroupBy.describe

Retornos count, mean, std, e outras estatísticas úteis por grupo.

df.groupby(['col1', 'col2'])['col3', 'col4'].describe()

# Setup
np.random.seed(0)
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                          'two', 'two', 'one', 'three'],
                   'C' : np.random.randn(8),
                   'D' : np.random.randn(8)})

from IPython.display import display

with pd.option_context('precision', 2):
    display(df.groupby(['A', 'B'])['C'].describe())

           count  mean   std   min   25%   50%   75%   max
A   B                                                     
bar one      1.0  0.40   NaN  0.40  0.40  0.40  0.40  0.40
    three    1.0  2.24   NaN  2.24  2.24  2.24  2.24  2.24
    two      1.0 -0.98   NaN -0.98 -0.98 -0.98 -0.98 -0.98
foo one      2.0  1.36  0.58  0.95  1.15  1.36  1.56  1.76
    three    1.0 -0.15   NaN -0.15 -0.15 -0.15 -0.15 -0.15
    two      2.0  1.42  0.63  0.98  1.20  1.42  1.65  1.87

Para obter estatísticas específicas, basta selecioná-las,

df.groupby(['A', 'B'])['C'].describe()[['count', 'mean']]

           count      mean
A   B                     
bar one      1.0  0.400157
    three    1.0  2.240893
    two      1.0 -0.977278
foo one      2.0  1.357070
    three    1.0 -0.151357
    two      2.0  1.423148

describefunciona para várias colunas (altere ['C']para ['C', 'D']- ou remova-o completamente - e veja o que acontece, o resultado é um quadro de dados em coluna MultiIndexed).

Você também obtém estatísticas diferentes para dados de sequência. Aqui está um exemplo,

df2 = df.assign(D=list('aaabbccc')).sample(n=100, replace=True)

with pd.option_context('precision', 2):
    display(df2.groupby(['A', 'B'])
               .describe(include='all')
               .dropna(how='all', axis=1))

              C                                                   D                
          count  mean       std   min   25%   50%   75%   max count unique top freq
A   B                                                                              
bar one    14.0  0.40  5.76e-17  0.40  0.40  0.40  0.40  0.40    14      1   a   14
    three  14.0  2.24  4.61e-16  2.24  2.24  2.24  2.24  2.24    14      1   b   14
    two     9.0 -0.98  0.00e+00 -0.98 -0.98 -0.98 -0.98 -0.98     9      1   c    9
foo one    22.0  1.43  4.10e-01  0.95  0.95  1.76  1.76  1.76    22      2   a   13
    three  15.0 -0.15  0.00e+00 -0.15 -0.15 -0.15 -0.15 -0.15    15      1   c   15
    two    26.0  1.49  4.48e-01  0.98  0.98  1.87  1.87  1.87    26      2   b   15

Para mais informações, consulte a documentação .

cs95
fonte
Nem todas as distribuições são normais. IQR seria incrível.
Brad
7

Podemos fazer isso facilmente usando groupby e count. Mas, devemos lembrar de usar reset_index ().

df[['col1','col2','col3','col4']].groupby(['col1','col2']).count().\
reset_index()
Nimesh
fonte
3
Essa solução funciona desde que não haja valor nulo nas colunas; caso contrário, pode ser enganosa (a contagem será menor que o número real de observação por grupo).
Adrien Pacifico
4

Para obter várias estatísticas, reduza o índice e mantenha os nomes das colunas:

df = df.groupby(['col1','col2']).agg(['mean', 'count'])
df.columns = [ ' '.join(str(i) for i in col) for col in df.columns]
df.reset_index(inplace=True)
df

Produz:

** insira a descrição da imagem aqui **

Jake Drew
fonte
1

Crie um objeto de grupo e chame métodos como o exemplo abaixo:

grp = df.groupby(['col1',  'col2',  'col3']) 

grp.max() 
grp.mean() 
grp.describe() 
Mahendra
fonte
1

Por favor, tente este código

new_column=df[['col1', 'col2', 'col3', 'col4']].groupby(['col1', 'col2']).count()
df['count_it']=new_column
df

Eu acho que esse código adicionará uma coluna chamada 'conte-o', que conte cada grupo

Ichsan
fonte