pandas dataframe groupby datetime mês

98

Considere um arquivo csv:

string,date,number
a string,2/5/11 9:16am,1.0
a string,3/5/11 10:44pm,2.0
a string,4/22/11 12:07pm,3.0
a string,4/22/11 12:10pm,4.0
a string,4/29/11 11:59am,1.0
a string,5/2/11 1:41pm,2.0
a string,5/2/11 2:02pm,3.0
a string,5/2/11 2:56pm,4.0
a string,5/2/11 3:00pm,5.0
a string,5/2/14 3:02pm,6.0
a string,5/2/14 3:18pm,7.0

Posso ler e reformatar a coluna de data no formato datetime:

b=pd.read_csv('b.dat')
b['date']=pd.to_datetime(b['date'],format='%m/%d/%y %I:%M%p')

Tenho tentado agrupar os dados por mês. Parece que deveria haver uma maneira óbvia de acessar o mês e agrupar por ele. Mas eu não consigo fazer isso. Alguém sabe como?

O que estou tentando atualmente é reindexar pela data:

b.index=b['date']

Posso acessar o mês assim:

b.index.month

No entanto, não consigo encontrar uma função para agrupar por mês.

atomh33ls
fonte
Se você tiver dificuldade em aplicar qualquer uma das respostas, lembre-se de que nesta pergunta (e, portanto, nas respostas) o valor Datetime é atribuído ao índice do Dataframe. Uma dica / lembrete rápido pode ser o seguinte: se você tiver uma coluna Datetime, poderá acessar o valor único Yeay / Mês / Dia / Hora / Minuto simplesmente fazendomy_df.my_column.dt.month
Federico Dorato

Respostas:

181

Conseguiu fazer isso:

b = pd.read_csv('b.dat')
b.index = pd.to_datetime(b['date'],format='%m/%d/%y %I:%M%p')
b.groupby(by=[b.index.month, b.index.year])

Ou

b.groupby(pd.Grouper(freq='M'))  # update for v0.21+
atomh33ls
fonte
54
Acho que as formas mais pandônicas são usar resample(quando ele fornece a funcionalidade de que você precisa) ou usar TimeGrouper:df.groupby(pd.TimeGrouper(freq='M'))
Karl D.
10
para obter a soma ou média do DataFrame do resultado, df.groupby(pd.TimeGrouper(freq='M')).sum()oudf.groupby(pd.TimeGrouper(freq='M')).mean()
Alexandre
9
pd.TimeGrouperfoi preterido em favor de pd.Grouper, que é um pouco mais flexível, mas ainda aceita freqe levelargumentos.
BallpointBen
o primeiro método não parece funcionar. Ele dá o erro 'Série objeto não tem atributo' mês '' para uma Série criada via to_datetime.
ely
1
@ely A resposta depende implicitamente das linhas da pergunta original, onde bé fornecido um índice após ser lido em CSV. Adicione b.index = pd.to_datetime(b['date'],format='%m/%d/%y %I:%M%p')depois da linha b = pd.read_csv('b.dat'). [Eu também editei a resposta agora]
mercadoria,
77

(atualização: 2018)

Observe que pd.Timegrouperestá depreciado e será removido. Use em seu lugar:

 df.groupby(pd.Grouper(freq='M'))
PandasRocks
fonte
2
Encontre os documentos do Grouper aqui e as especificações de frequência ( freq=...) aqui . Alguns exemplos são freq=Dpara dias , freq=Bpara dias úteis , freq=Wpor semanas ou mesmo freq=Qpara bairros .
Kim,
3
Achei útil usar 'key' para evitar ter que reindexar o df, da seguinte maneira: df.groupby (pd.Grouper (key = 'your_date_column', freq = 'M'))
Edward
14

Uma solução que evita o MultiIndex é criar uma nova datetimeconfiguração de coluna dia = 1. Em seguida, agrupe por esta coluna.

Normalizar dia do mês

df = pd.DataFrame({'Date': pd.to_datetime(['2017-10-05', '2017-10-20', '2017-10-01', '2017-09-01']),
                   'Values': [5, 10, 15, 20]})

# normalize day to beginning of month, 4 alternative methods below
df['YearMonth'] = df['Date'] + pd.offsets.MonthEnd(-1) + pd.offsets.Day(1)
df['YearMonth'] = df['Date'] - pd.to_timedelta(df['Date'].dt.day-1, unit='D')
df['YearMonth'] = df['Date'].map(lambda dt: dt.replace(day=1))
df['YearMonth'] = df['Date'].dt.normalize().map(pd.tseries.offsets.MonthBegin().rollback)

Em seguida, use groupbynormalmente:

g = df.groupby('YearMonth')

res = g['Values'].sum()

# YearMonth
# 2017-09-01    20
# 2017-10-01    30
# Name: Values, dtype: int64

Comparação com pd.Grouper

O benefício sutil dessa solução é, ao contrário pd.Grouper, o índice de garoupa é normalizado para o início de cada mês, e não para o final e, portanto, você pode facilmente extrair grupos por meio de get_group:

some_group = g.get_group('2017-10-01')

Calcular o último dia de outubro é um pouco mais complicado. pd.Grouper, a partir da v0.23, oferece suporte a um conventionparâmetro, mas isso é aplicável apenas para um PeriodIndexgaroupa

Comparação com conversão de string

Uma alternativa à ideia acima é converter para uma string, por exemplo, converter data 2017-10-XXe hora para string '2017-10'. No entanto, isso não é recomendado, pois você perde todos os benefícios de eficiência de uma datetimesérie (armazenada internamente como dados numéricos em um bloco de memória contíguo) versus uma objectsérie de strings (armazenada como uma matriz de ponteiros).

jpp
fonte
Consulte esta resposta para saber como utilizar os deslocamentos quando já existem valores day = 1: stackoverflow.com/a/45831333/9987623 .
AlexK
@AlexK, pd.tseries.offsetstem vantagem sobre pd.tseries.MonthBegin?
jpp de
desculpe, eu não sei o suficiente para diferenciá-los. Acabei de adicionar o comentário porque seu df['YearMonth'] = df['Date'] - pd.offsets.MonthBegin(1)código acima altera qualquer data que já seja o primeiro dia do mês para o primeiro dia do mês anterior.
AlexK
@AlexK, bom local, atualizei a resposta de acordo.
jpp
8

Solução ligeiramente alternativa para @jpp's, mas gerando uma YearMonthstring:

df['YearMonth'] = pd.to_datetime(df['Date']).apply(lambda x: '{year}-{month}'.format(year=x.year, month=x.month))

res = df.groupby('YearMonth')['Values'].sum()
tsando
fonte