根据字典元素的值筛选子列表

1 投票
2 回答
4137 浏览
提问于 2025-04-17 17:43

我有一个列表,这个列表里包含了一些字典。我想从这个列表中筛选出一些字典,筛选的标准是比较字典里某个元素的值(在这个例子中,我想每个日期只选一个字典,选出的字典是那个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 个回答

4

你可以使用 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语言)。

1

你基本上是想根据 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'}]

撰写回答