Pandas agregam contagem distinta

92

Digamos que eu tenha um log de atividade do usuário e desejo gerar um relatório de duração total e o número de usuários únicos por dia.

import numpy as np
import pandas as pd
df = pd.DataFrame({'date': ['2013-04-01','2013-04-01','2013-04-01','2013-04-02', '2013-04-02'],
    'user_id': ['0001', '0001', '0002', '0002', '0002'],
    'duration': [30, 15, 20, 15, 30]})

Agregar a duração é bastante simples:

group = df.groupby('date')
agg = group.aggregate({'duration': np.sum})
agg
            duration
date
2013-04-01        65
2013-04-02        45

O que eu gostaria de fazer é somar a duração e contar as distinções ao mesmo tempo, mas não consigo encontrar um equivalente para count_distinct:

agg = group.aggregate({ 'duration': np.sum, 'user_id': count_distinct})

Isso funciona, mas certamente há uma maneira melhor, não?

group = df.groupby('date')
agg = group.aggregate({'duration': np.sum})
agg['uv'] = df.groupby('date').user_id.nunique()
agg
            duration  uv
date
2013-04-01        65   2
2013-04-02        45   1

Estou pensando que só preciso fornecer uma função que retorne a contagem de itens distintos de um objeto Series para a função agregada, mas não tenho muito contato com as várias bibliotecas à minha disposição. Além disso, parece que o objeto groupby já conhece essa informação, então eu não estaria apenas duplicando o esforço?

Dave
fonte

Respostas:

153

Que tal:

>>> df
         date  duration user_id
0  2013-04-01        30    0001
1  2013-04-01        15    0001
2  2013-04-01        20    0002
3  2013-04-02        15    0002
4  2013-04-02        30    0002
>>> df.groupby("date").agg({"duration": np.sum, "user_id": pd.Series.nunique})
            duration  user_id
date                         
2013-04-01        65        2
2013-04-02        45        1
>>> df.groupby("date").agg({"duration": np.sum, "user_id": lambda x: x.nunique()})
            duration  user_id
date                         
2013-04-01        65        2
2013-04-02        45        1
DSM
fonte
1
É isso aí. pd.Series.nunique é o que não consegui encontrar, bem, não conseguia funcionar corretamente. Bastante óbvio em retrospectiva. Obrigado!
dave
5
Esta resposta está desatualizada. Agora você pode usar nuniquediretamente. Veja a solução de @Blodwyn Pig abaixo
Ted Petrou
Obrigado @TedPetrou, sou o codificador anteriormente conhecido como Blodwyn Pig;)
Ricky McMaster
Ei, você sabe como obter uma contagem não duplicada?
Ambleu
59

'nunique' é uma opção para .agg () desde pandas 0.20.0, então:

df.groupby('date').agg({'duration': 'sum', 'user_id': 'nunique'})
Ricky McMaster
fonte
É possível agg e obter os valores únicos? algo comoduration: np.unique
cara
@guy Trydf.groupby('date').agg({'user_id': lambda s: s.unique().reset_index(drop=True)})
BallpointBen
Como obtemos a saída?
17

Apenas adicionando as respostas já fornecidas, a solução usando a string "nunique"parece muito mais rápida, testada aqui em um dataframe de aproximadamente 21 milhões de linhas e agrupada em aproximadamente 2 milhões

%time _=g.agg({"id": lambda x: x.nunique()})
CPU times: user 3min 3s, sys: 2.94 s, total: 3min 6s
Wall time: 3min 20s

%time _=g.agg({"id": pd.Series.nunique})
CPU times: user 3min 2s, sys: 2.44 s, total: 3min 4s
Wall time: 3min 18s

%time _=g.agg({"id": "nunique"})
CPU times: user 14 s, sys: 4.76 s, total: 18.8 s
Wall time: 24.4 s
user6903745
fonte
1
Boa pegada! Eu acho que é b / c em um caso de "lambda" / "outra função", ela é aplicada sequencialmente, enquanto as funções "conhecidas" são aplicadas a toda a coluna de uma forma vetorizada.
Ufos
qual solução é da @Blodwyn Pig?
Chogg
@Chogg, o mais rápido!
m-dz
@Chogg - desculpe, eu mudei meu nome de usuário. Fui eu.
Ricky McMaster,