如何按照月份对字典列表进行分组?

39
我有一个字典列表,我正在尝试将它们组织到相同的月份和年份中:
[{'date':'2008-04-23','value':'1'},
{'date':'2008-04-01','value':'8'},
{'date':'2008-04-05','value':'3'},
{'date':'2009-04-19','value':'5'},
{'date':'2009-04-21','value':'8'},
{'date':'2010-09-09','value':'3'},
{'date':'2010-09-10','value':'4'},
]

我想要的是这样一个字典列表:

[{'date':2008-04-01,'value':'12'},
{'date':2009-04-01,'value':'13'},
{'date':2010-09-01,'value':'7'},
]

这是我的代码,它只打印一个空列表:

from datetime import datetime

myList = [{'date':'2008-04-23','value':'1'}, {'date':'2008-04-01','value':'8'}, {'date':'2008-04-05','value':'3'}, {'date':'2009-04-19','value':'5'}, {'date':'2009-04-21','value':'8'},{'date':'2010-09-09','value':'3'},
    {'date':'2010-09-10','value':'4'},
    ]

newList = []
newDict = {}

for cnt in range(len(myList)):
    for k,v in myList[cnt].iteritems():
        if k == 'date':
            d = datetime.strptime(v,'%Y-%m-%d').date()
            for elem in newList:
                if elem['date'] != d:
                    newList.append({'date':d,'value':myList[cnt]['value']})
                else:
                    newList[cnt]['value'] += myList[cnt]['value']

print newList   
3个回答

70

首先,我会对数据进行排序1

>>> lst = [{'date':'2008-04-23','value':'1'},
... {'date':'2008-04-01','value':'8'},
... {'date':'2008-04-05','value':'3'},
... {'date':'2009-04-19','value':'5'},
... {'date':'2009-04-21','value':'8'},
... {'date':'2010-09-09','value':'3'},
... {'date':'2010-09-10','value':'4'},
... ]
>>> lst.sort(key=lambda x:x['date'][:7])
>>> lst
[{'date': '2008-04-23', 'value': '1'}, {'date': '2008-04-01', 'value': '8'}, {'date': '2008-04-05', 'value': '3'}, {'date': '2009-04-19', 'value': '5'}, {'date': '2009-04-21', 'value': '8'}, {'date': '2010-09-09', 'value': '3'}, {'date': '2010-09-10', 'value': '4'}]

然后,我会使用 itertools.groupby 进行分组:

>>> from itertools import groupby
>>> for k,v in groupby(lst,key=lambda x:x['date'][:7]):
...    print k, list(v)
... 
2008-04 [{'date': '2008-04-23', 'value': '1'}, {'date': '2008-04-01', 'value': '8'}, {'date': '2008-04-05', 'value': '3'}]
2009-04 [{'date': '2009-04-19', 'value': '5'}, {'date': '2009-04-21', 'value': '8'}]
2010-09 [{'date': '2010-09-09', 'value': '3'}, {'date': '2010-09-10', 'value': '4'}]
>>> 
现在,为了获得你想要的输出:
>>> for k,v in groupby(lst,key=lambda x:x['date'][:7]):
...     print {'date':k+'-01','value':sum(int(d['value']) for d in v)}
... 
{'date': '2008-04-01', 'value': 12}
{'date': '2009-04-01', 'value': 13}
{'date': '2010-09-01', 'value': 7}

1根据您的数据,它似乎已经按此顺序排序,因此您可能可以跳过这一步。


+1。但他似乎还想要对每个组中的值进行求和,并将结果映射到该月份的任意日期?该月份的第一天?...等等。 - abarnert
@abarnert -- 感谢您指出这一点。我已经在我的答案中添加了一些内容来解决这个问题... - mgilson
为什么要使用[:7]?为什么是7? - Micheal J. Roberts
@WindUpLordVexxos [:7] 用于切片字符串并获取日期字符串的前6个字符,从而以YYYY-MM格式给出日期。此格式用作groupby操作的键。 - albert

24

使用itertools.groupby

data = [{'date':'2008-04-23','value':'1'},
    {'date':'2008-04-01','value':'8'},
    {'date':'2008-04-05','value':'3'},
    {'date':'2009-04-19','value':'5'},
    {'date':'2009-04-21','value':'8'},
    {'date':'2010-09-09','value':'3'},
    {'date':'2010-09-10','value':'4'},
    ]

import itertools

key = lambda datum: datum['date'].rsplit('-', 1)[0]

data.sort(key=key)

result = [{
            'date': key + '-01',
            'value': sum(int(item['value']) for item in group)
           } for key, group in itertools.groupby(data, key=key)]

print result

# [{'date': '2008-04-01', 'value': 12},
#  {'date': '2009-04-01', 'value': 13},
#  {'date': '2010-09-01', 'value': 7}]

9

接受的答案是正确的,但由于排序,它的时间复杂度为O(n lg n)。这里是(平摊)O(n)解决方案。

>>> L=[{'date':'2008-04-23','value':'1'},
... {'date':'2008-04-01','value':'8'},
... {'date':'2008-04-05','value':'3'},
... {'date':'2009-04-19','value':'5'},
... {'date':'2009-04-21','value':'8'},
... {'date':'2010-09-09','value':'3'},
... {'date':'2010-09-10','value':'4'},
... ]

这就是 Counter 的用途所在:
>>> import collections
>>> value_by_month = collections.Counter()
>>> for d in L:
...     value_by_month[d['date'][:7]+'-01'] += int(d['value'])
...
>>> value_by_month
Counter({'2009-04-01': 13, '2008-04-01': 12, '2010-09-01': 7})

如果你的输出必须是一个dict对象:

>>> dict(value_by_month)
{'2008-04-01': 12, '2009-04-01': 13, '2010-09-01': 7}

附加福利:如果您想避免导入。

首先,创建一个字典月份 -> 值列表setdefault函数对于构建此类型的字典非常方便:

>>> values_by_month = {}
>>> for d in L:
...     values_by_month.setdefault(d['date'][:7], []).append(int(d['value']))
...
>>> values_by_month
{'2008-04': [1, 8, 3], '2009-04': [5, 8], '2010-09': [3, 4]}

其次,按月份累加数值并将日期设置为第一天:

>>> [{'date':m+'-01', 'value':sum(vs)} for m, vs in values_by_month.items()]
[{'date': '2008-04-01', 'value': 12}, {'date': '2009-04-01', 'value': 13}, {'date': '2010-09-01', 'value': 7}]

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