Python - 按月份分组日期

4

这是一个看似简单的问题,但我一开始并没有认为如此。经过一个小时的思考,我也不太确定了!
我有一个Python datetime对象列表,我想将它们绘制成图表。x轴的值是年份和月份,y轴的值是在该月发生的日期对象数量。
也许一个例子会更好地说明这个问题(dd/mm/yyyy):

[28/02/2018, 01/03/2018, 16/03/2018, 17/05/2018] 
-> ([02/2018, 03/2018, 04/2018, 05/2018], [1, 2, 0, 1])

我的第一次尝试是简单地按日期和年份分组,类似于:

import itertools
group = itertools.groupby(dates, lambda date: date.strftime("%b/%Y"))
graph = zip(*[(k, len(list(v)) for k, v in group]) # format the data for graphing

你可能已经注意到,这只会按照列表中已有的日期进行分组。在上面的示例中,四月份没有出现任何日期将被忽略。

接下来,我尝试查找开始和结束日期,并循环遍历它们之间的所有月份:

import datetime
data = [[], [],]
for year in range(min_date.year, max_date.year):
    for month in range(min_date.month, max_date.month):
        k = datetime.datetime(year=year, month=month, day=1).strftime("%b/%Y")
        v = sum([1 for date in dates if date.strftime("%b/%Y") == k])
        data[0].append(k)
        data[1].append(v)

当然,这仅在min_date.month小于max_date.month的情况下才有效,如果它们跨越多年,则不一定成立。而且,这种方法相当丑陋。
有没有一种优雅的方法来解决这个问题呢?
谢谢。
编辑:明确一点,这些日期是datetime对象,而不是字符串。它们在这里看起来像字符串只是为了可读性。

请查看计数器 https://docs.python.org/2/library/collections.html#collections.Counter - shahaf
2个回答

7
我建议使用 pandas 工具:
import pandas as pd

dates = ['28/02/2018', '01/03/2018', '16/03/2018', '17/05/2018'] 

s = pd.to_datetime(pd.Series(dates), format='%d/%m/%Y')
s.index = s.dt.to_period('m')
s = s.groupby(level=0).size()

s = s.reindex(pd.period_range(s.index.min(), s.index.max(), freq='m'), fill_value=0)
print (s)
2018-02    1
2018-03    2
2018-04    0
2018-05    1
Freq: M, dtype: int64

s.plot.bar()

图表

解释:

  1. 首先从日期列表创建 Series,并将其转换为to_datetime
  2. 通过 Series.dt.to_period 创建 PeriodIndex
  3. 按索引 (level=0) groupby 并通过 GroupBy.size 计算计数
  4. 通过 Series.reindex,使用索引的最大值和最小值创建的 PeriodIndex 添加缺失周期
  5. 最后绘制图形,例如柱状图 - Series.plot.bar

1
谢谢你的回答,但我认为对于这个问题来说,导入像pandas这样的大型库有点过头了。计数器的想法似乎更合适。无论如何,谢谢。 - EriktheRed
@EriktheRed - 没问题,但如果将来需要使用类似的数据分析,pandas更好 :) - jezrael
@EriktheRed - 但我理解你,我开始使用pandas只是因为纯Python无法处理大型CSV文件。这个库确实很好,能够极大地简化解决方案。愉快的编码! - jezrael
1
看起来我要用Pandas了。Counter其实并不是那么好用... - EriktheRed

0
使用Counter
dates = list()
import random
import collections

for y in range(2015,2019):
  for m in range(1,13):
    for i in range(random.randint(1,4)):
      dates.append("{}/{}".format(m,y))

print(dates)
counter = collections.Counter(dates)
print(counter)

对于没有出现日期的问题,您可以使用计数器的subtract方法, 生成一个包含所有日期范围的列表,每个日期仅出现一次,然后您可以使用 subtract 方法,如下所示

tmp_date_list = ["{}/{}".format(m,y) for y in range(2015,2019) for m in range(1,13)]
counter.subtract(tmp_date_list)

有没有办法在这里保持我的日期作为本地的 datetime 对象,而不是将它们转换为字符串再转回来? - EriktheRed
此外,这不包括列表中没有发生的月份。这与我的“groupby”尝试存在相同的问题。 - EriktheRed
@EriktheRed,是的,您可以使用本地的datetime对象,但您只需要创建年份和月份即可,计数器将知道如何比较它们。 - shahaf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接