在python中打包一个dicts列表

时间:2014-07-31 16:23:52

标签: python list dictionary pandas group-by

我有一个像这样的结构列表:

[
    {'state': '1', 'city': 'a'},
    {'state': '1', 'city': 'b'},
    {'state': '2', 'city': 'c'},
    {'state': '2', 'city': 'd'},
    {'state': '3', 'city': 'e'}
]

我想以这种方式打包:

[
    {'state': '1', 'cities': ['a', 'b']},
    {'state': '2', 'cities': ['c', 'd']},
    {'state': '3', 'cities': ['e']}
]

我有两个步骤可行,但速度非常慢(我的列表长度超过10000个,而且我的字符串很复杂):

def pack(iterable):

    # step 1: lists -> super slow ! contains duplicates
    listed = [{'state': i['state'],
              'cities': [c['city'] for c in iterable if c['state']==i['state']]}
              for i in iterable]

    # step 2: remove duplicates
    packed = [l for n, l in enumerate(listed) if not l in listed[n+1:]]

    return packed

有任何优化建议吗?

Ps:欢迎提出线索标题的建议。

2014/09/26编辑:我刚刚发现了大熊猫非标准库,在这种情况下很有帮助。

以下自我回答的更多例子。

4 个答案:

答案 0 :(得分:2)

state_merged = {}
for s in states:
    state_merged.setdefault(s['state'], []).append(s['city'])

states = [{'state':k, 'cities':v} for k, v in state_merged.iteritems()]

如果您使用的是python 3.0,请使用state_merged.items()代替state_merged.iteritems()

答案 1 :(得分:2)

以下不需要预先排序的可迭代并且在O(n)时间内运行,但它假设状态与其他字典键之间存在不对称(给出您的示例似乎是正确的假设)。

import collections
def pack(iterable):
    out = collections.defaultdict(list) #or use defaultdict(set)
    for d in iterable:
        out[d['state']].append(d['city'])
    return out

it = [
    {'state': '1', 'city': 'a'},
    {'state': '1', 'city': 'b'},
    {'state': '2', 'city': 'c'},
    {'state': '2', 'city': 'd'},
    {'state': '3', 'city': 'e'}
]

pack(it) == {'1': ['a', 'b'],
             '2': ['c', 'd'],
             '3': ['e']}

如果您需要以与请求相同的格式返回可迭代,则可以将out转换为list

def convert(out):
    final = []
    for state, city in out.iteritems(): #Python 3.0+ use .items()
        final.append({'state': state, 'city': city})
    return final

convert(pack(it)) == [
    {'state': '1', 'city': ['a', 'b']},
    {'state': '2', 'city': ['c', 'd']},
    {'state': '3', 'city': ['e']}
]

如果输入中只有2个键,则需要进行以下更改:

it = [{'state': 'WA', 'city': 'Seattle', 'zipcode': 98101, 'city_population': 9426},
      {'state': 'OR', 'city': 'Portland', 'zipcode': 97225, 'city_population': 24749},
      {'state': 'WA', 'city': 'Spokane', 'zipcode': 99201, 'city_population': 12523}]


def citydata():
    return {'city': [], 'zipcode': [], 'state_population': 0} #or use a namedtuple('Location', 'city zipcode state_population')

def pack(iterable):
    out = defaultdict(citydata)
    for d in iterable:
        out[d['state']]['city'].append(d['city'])
        out[d['state']]['zipcode'].append(d['zipcode'])
        out[d['state']]['state_population'] += d['city_population']
    return out

pack(it) == {
   'WA':
       {'city': ['Seattle', 'Spokane'], 'zipcode': [98101, 99201], 'state_population': 21949},
   'OR':
       {'city': ['Portland'], 'zipcode': [97225], 'state_population': 24749}
}

convert函数需要相应调整。

convert(pack(it)) == [
       {'state': 'WA', 'city': ['Seattle', 'Spokane'], 'zipcode': [98101, 99201], 'state_population': 21949},
       {'state': 'OR', 'city': ['Portland'], 'zipcode': [97225], 'state_population': 24749}
]

要维护原始可迭代的顺序,请使用OrderedDefaultdict而不是defaultdict。

答案 2 :(得分:1)

这是一种功能更强大的方法,速度更快:

import itertools
def pack(original):
    return [
        {'state': state, 'cities': [element['city'] for element in group]} 
        for state, group 
        in itertools.groupby(original, lambda e: e['state'])
    ]

这假定您的每个州都将其所有成员连续列在原始列表中。

您当前的方法速度慢得多的原因是它必须遍历整个列表以查找找到的每个状态ID。这被称为O(n^2)方法。这种方法只需要遍历源列表一次,因此它是O(n)

答案 3 :(得分:0)

我刚刚发现pandas lib(非标准版)在安装到我的windows python 2.6.5(exe http://www.lfd.uci.edu/~gohlke/pythonlibs/#pandas)后出现问题。

网站:http://pandas.pydata.org/pandas-docs/stable/

一般介绍:

pandas是一个Python包,提供快速,灵活和富有表现力的数据结构,旨在使“关系”或“标记”数据的使用既简单又直观。它旨在成为用Python进行实际的真实数据分析的基础高级构建块。

对于已经使用numpy和R的人来说,Pandas会很熟悉。

以下是如何使用pandas解决我的问题:

>>> import pandas as pd

>>> raw = [{'state': '1', 'city': 'a'},
           {'state': '1', 'city': 'b'},
           {'state': '2', 'city': 'c'},
           {'state': '2', 'city': 'd'},
           {'state': '3', 'city': 'e'}]

>>> df = pd.DataFrame(raw) # magic !

>>> df
  city state
0    a     1
1    b     1
2    c     2
3    d     2
4    e     3

>>> grouped = df.groupby('state')['city']
>>> grouped
<pandas.core.groupby.SeriesGroupBy object at 0x05F22110>

>>> listed = grouped.apply(list)
>>> listed
state
1        [a, b]
2        [c, d]
3           [e]
Name: city, dtype: object

>>> listed.to_dict() # magic again !
{'1': ['a', 'b'], '3': ['e'], '2': ['c', 'd']}

更复杂的例子包括grouped.apply(custom_fct)

Pandas groupby: How to get a union of strings