根据字典元素的值筛选子列表
我有一个列表,这个列表里包含了一些字典。我想从这个列表中筛选出一些字典,筛选的标准是比较字典里某个元素的值(在这个例子中,我想每个日期只选一个字典,选出的字典是那个realtime_start
值最大的)。
这里有一个示例列表:
obs = [{'date': '2012-10-01',
'realtime_end': '2013-02-18',
'realtime_start': '2012-11-15',
'value': '231.751'},
{'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '2013-02-18',
'realtime_start': '2012-12-14',
'value': '231.025'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'},
{'date': '2012-12-01',
'realtime_end': '2013-02-18',
'realtime_start': '2013-01-16',
'value': '230.979'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-19',
'value': '231.137'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-19',
'value': '231.197'},
{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-21',
'value': '231.198'},
{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-21',
'value': '231.222'}]
我希望筛选后的列表只包含每个日期对应的一个字典,并且这个字典是realtime_start
值最大的。
在这种情况下,筛选后的列表应该是:
sub = [ {'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-19',
'value': '231.197'},
{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-21',
'value': '231.222'}]
另外,假设我指定了一个最大日期:
maxDate = "2013-02-21"
我该如何筛选,使得realtime_start
的值不大于maxDate?在这种情况下,我希望得到以下的筛选结果:
sub2 = [ {'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-19',
'value': '231.137'},
{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-21',
'value': '231.198'} ]
我该如何在Python 2.7.3中写这样的筛选操作?在Python中可以做到吗?
谢谢
2 个回答
你可以使用 itertools.groupby
:
>>> import itertools
>>> # sort so that the same dates are contiguous
>>> obs.sort(key=lambda x: x['date'])
>>> grouped = itertools.groupby(obs, lambda x: x['date'])
>>> m = [max(g, key=lambda x: x['realtime_start']) for k, g in grouped]
>>>
>>> import pprint
>>> pprint.pprint(m)
[{'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-19',
'value': '231.197'},
{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-21',
'value': '231.222'}]
你还可以添加其他条件:
>>> grouped = itertools.groupby(obs, lambda x: x['date'])
>>> m = [max((w for w in g if w['realtime_start'] <= maxDate),
key=lambda x: x['realtime_start']) for k, g in grouped]
>>> pprint.pprint(m)
[{'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-19',
'value': '231.137'},
{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-21',
'value': '231.198'}]
不过我建议你看看我最喜欢的Python数据处理库 pandas:它非常适合处理表格数据和时间序列数据,用它来处理数据会比你自己写的要简单得多(而且功能上更像R语言)。
你基本上是想根据 date
字段把你的数据分组,然后对每个 date
相关的数据组进行操作。我通常用一个普通的 dict
来处理这种情况。在这种情况下,我把 dict
想象成一种特殊的 set
——可以称之为“装饰过的集合”,它的每个元素(必须是可哈希的)都“装饰”了一些(通常是不可哈希的)负载(也就是关联的字典值)。在你的例子中,这个“装饰过的集合”的每个元素都是 obs
中所有字典的 date
字段的可能值,而它的关联负载则是所有在 obs
中以该键作为 date
字段的字典列表。
因此,
In [4]: dobs = dict()
In [5]: for o in obs:
...: d = o['date']
...: if d not in dobs:
...: dobs[d] = []
...: dobs[d].append(o)
...:
可以使用 dict.setdefault
来更简洁地写 for
循环的主体,像这样:
In [7]: for o in obs:
...: dobs.setdefault(o['date'], []).append(o)
...:
或者可以提前用空列表填充字典,然后直接在这些列表中添加内容,而不需要检查键是否已经在字典中:
In [9]: dobs = dict([(d, []) for d in set([e['date'] for e in obs])])
In [10]: for o in obs:
....: dobs[o['date']].append(o)
....:
经过上述任一步骤,你将得到一个字典 dobs
,它的键是 date
,值是所有在 obs
中以对应键作为 date
值的字典的 列表。
现在你可以对这个字典进行各种操作,应用任何函数到它的值上。例如,要提取每个 date
中最近的 realtime_start
字典,你可以这样做:
In [11]: rts = lambda x: x['realtime_start']
In [12]: [sorted(e, key=rts)[-1] for e in dobs.values() if e]
Out[12]:
[{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-21',
'value': '231.222'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-19',
'value': '231.197'},
{'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'}]
(上面推导式末尾的 if e
条件在这里并不是必需的,但我加上它是出于“防御性编程”的考虑。如果没有它,当 dobs
中的某个值恰好为空时,代码会出错。我们知道在 dobs
中不会出现这种情况,但在更一般的情况下可能会成为问题。下面会详细说明。)
你还问到如何在将 realtime_start
限制在 2013-02-21
的情况下进行选择。对于这个问题,我觉得把问题分成两个子问题更清晰:首先,生成满足 realtime_start
限制条件的 dobs
子集;然后,对这个限制后的字典执行与之前相同的操作。因此:
In [13]: dobs2 = dict([(k, [d for d in v if d['realtime_start'] <= maxDate])
....: for k, v in dobs.items()])
In [14]: [sorted(e, key=rts)[-1] for e in dobs2.values() if e]
Out[14]:
[{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-21',
'value': '231.198'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-02-19',
'value': '231.137'},
{'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'}]
再次强调,if e
条件在这种情况下并不是必需的,但如果 maxDate
低到某些组变为空,那么它就变得非常重要。(如果没有它,尝试访问第一个遇到的空列表的最后一个元素会引发 IndexError
异常。)
你可能注意到,上述结果的顺序与您的不同。这是因为内置的 Python dict
不会保留顺序。如果原始 obs
列表的顺序很重要,那么你可以把所有对 dict
的调用替换为对 collections.OrderedDict
的调用。例如:
In [15]: from collections import OrderedDict
In [16]: dobs = OrderedDict()
In [17]: for o in obs:
....: dobs.setdefault(o['date'], []).append(o)
....:
In [18]: [sorted(e, key=rts)[-1] for e in dobs.values()]
Out[18]:
[{'date': '2012-10-01',
'realtime_end': '9999-12-31',
'realtime_start': '2012-12-19',
'value': '231.623'},
{'date': '2012-11-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-01-19',
'value': '231.071'},
{'date': '2012-12-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-19',
'value': '231.197'},
{'date': '2013-01-01',
'realtime_end': '9999-12-31',
'realtime_start': '2013-03-21',
'value': '231.222'}]