【问题标题】:List of dictionaries from numpy array without for loop没有for循环的numpy数组中的字典列表
【发布时间】:2017-03-16 20:16:31
【问题描述】:

有没有办法对一个需要几个 numpy 数组并将它们放入字典列表的操作进行矢量化?

这是一个简化的示例。真实场景可能涉及更多的数组和更多的字典键。

import numpy as np
x = np.arange(10)
y = np.arange(10, 20)
z = np.arange(100, 110)

print [dict(x=x[ii], y=y[ii], z=z[ii]) for ii in xrange(10)]

我可能在xrange 调用中进行了数千或数十万次迭代。创建xyz 的所有操作都是矢量化的(我的示例不像上面那么简单)。所以,只剩下 1 个 for 循环需要摆脱,我预计这会带来巨大的速度提升。

我已经尝试使用 map 和一个函数来创建 dict 和各种其他变通方法。似乎 Python for 循环是缓慢的部分(像往常一样)。由于预先存在的 API 要求,我有点坚持使用字典。然而,没有字典和记录数组或其他东西的解决方案会很有趣,但最终我认为这不适用于现有的 API。

【问题讨论】:

  • z=z[ii],好收获!
  • [dict(x=x_, y=y_, z=z_) for x_, y_, z_ in zip(x, y, z)] 就纯 Python 而言,这是矢量化的。
  • 您是否尝试过使用列表和 dic 理解?是不是太慢了?
  • 我的意思是,问题可能不在于循环的存在与否,而是列表的构造导致随着内存的增长而重复分配内存。
  • @durden2.0 仅供参考,listcomps 中的 for 与 Python 的一般 for 关系不大。前者实际上有一个较低级别的实现,并且比后者更快。

标签: python performance numpy vectorization


【解决方案1】:

这是一种混合使用NumPyPandas 的方法-

# Stack into columns & create a pandas dataframe with appropriate col names
a = np.column_stack((x.ravel(),y.ravel(),z.ravel()))
df = pd.DataFrame(a,columns=[['x','y','z']])

# Convert to list of dicts
out = df.T.to_dict().values()

示例运行 -

In [52]: x
Out[52]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [53]: y
Out[53]: array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])

In [54]: z
Out[54]: array([100, 101, 102, 103, 104, 105, 106, 107, 108, 109])

In [55]: out
Out[55]: 
[{'x': 0, 'y': 10, 'z': 100},
 {'x': 1, 'y': 11, 'z': 101},
 {'x': 2, 'y': 12, 'z': 102},
 {'x': 3, 'y': 13, 'z': 103},
 {'x': 4, 'y': 14, 'z': 104},
 {'x': 5, 'y': 15, 'z': 105},
 {'x': 6, 'y': 16, 'z': 106},
 {'x': 7, 'y': 17, 'z': 107},
 {'x': 8, 'y': 18, 'z': 108},
 {'x': 9, 'y': 19, 'z': 109}]

【讨论】:

  • 聪明的解决方案!我简化了我的设置,但实际上我正在创建一个嵌套字典,所以我会尝试稍微调整一下。例如,我返回的字典列表实际上就像[{{'connection': {'xy': x[ii], 'yy': y[ii]}}]
  • @durden2.0 您能否针对该要求编辑问题中的循环理解代码?
【解决方案2】:

这是一个(Num)?Pythonic 方式:

In [18]: names = np.array(['x', 'y', 'z'])
In [38]: map(dict, np.dstack((np.repeat(names[None, :], 10, axis=0), np.column_stack((x, y, z)))))
Out[38]: 
[{'x': '0', 'y': '10', 'z': '100'},
 {'x': '1', 'y': '11', 'z': '101'},
 {'x': '2', 'y': '12', 'z': '102'},
 {'x': '3', 'y': '13', 'z': '103'},
 {'x': '4', 'y': '14', 'z': '104'},
 {'x': '5', 'y': '15', 'z': '105'},
 {'x': '6', 'y': '16', 'z': '106'},
 {'x': '7', 'y': '17', 'z': '107'},
 {'x': '8', 'y': '18', 'z': '108'},
 {'x': '9', 'y': '19', 'z': '109'}]

另外,请注意,如果您不需要同时使用所有字典,则可以简单地创建一个生成器并按需访问每个项目。

(dict(x=x[ii], y=y[ii], z=z[ii]) for ii in xrange(10))

如果你想要一个嵌套字典,我建议使用列表理解:

In [88]: inner = np.dstack((np.repeat(names[None, :], 10, axis=0), np.column_stack((x, y))))

In [89]: [{'connection': d} for d in map(dict, inner)]
Out[89]: 
[{'connection': {'x': '0', 'y': '10'}},
 {'connection': {'x': '1', 'y': '11'}},
 {'connection': {'x': '2', 'y': '12'}},
 {'connection': {'x': '3', 'y': '13'}},
 {'connection': {'x': '4', 'y': '14'}},
 {'connection': {'x': '5', 'y': '15'}},
 {'connection': {'x': '6', 'y': '16'}},
 {'connection': {'x': '7', 'y': '17'}},
 {'connection': {'x': '8', 'y': '18'}},
 {'connection': {'x': '9', 'y': '19'}}]

【讨论】:

  • 不错!如果我需要创建嵌套字典怎么办。例如,{'connection': {'x': x[ii], 'y': y[ii]}}。我意识到我在我的场景中简化了我的问题。
  • @durden2.0 你的意思是,你想要一个包含所有项目的嵌套字典?没有z?还是可选的?
  • 是所有项目的嵌套字典,z 在我的解决方案中是必需的,但对于解决方案本身并没有太大的影响,因为键是嵌套字典而不执行 python for 循环。
  • @durden2.0 列表理解的 for 不像 pythons for 循环,因为它的迭代已经在 C 中实现。但是如果你想在 Numpy 中执行此操作,因为你想要一个python对象,你的代码需要与上层(python)交互,这意味着你不能写一个纯numpythonic代码。
  • @Kasramvd "List comprehension's for is not like pythons for loop, cause its iteration has been implemented in C",真的吗? I wonder why they are slow then?。另一个相关链接 - stackoverflow.com/questions/22108488/…
【解决方案3】:

对于你的小例子,我无法获得比列表和字典理解组合更快的任何东西

In [105]: timeit [{'x':i, 'y':j, 'z':k} for i,j,k in zip(x,y,z)]
100000 loops, best of 3: 15.5 µs per loop
In [106]: timeit [{'key':{'x':i, 'y':j, 'z':k}} for i,j,k in zip(x,y,z)]
10000 loops, best of 3: 37.3 µs per loop

在分区之前使用数组串联连接数组的替代方案速度较慢。

In [108]: timeit [{'x':x_, 'y':y_, 'z':z_} for x_, y_, z_ in np.column_stack((x,y,z))]
....
10000 loops, best of 3: 58.2 µs per loop

=========================

使用recfunctions 最简单的结构化数组:

In [109]: from numpy.lib import recfunctions
In [112]: M=recfunctions.merge_arrays((x,y,z))
In [113]: M.dtype.names=['x','y','z']
In [114]: M
Out[114]: 
array([(0, 10, 100), (1, 11, 101), (2, 12, 102), (3, 13, 103),
       (4, 14, 104), (5, 15, 105), (6, 16, 106), (7, 17, 107),
       (8, 18, 108), (9, 19, 109)], 
      dtype=[('x', '<i4'), ('y', '<i4'), ('z', '<i4')])
In [115]: M['x']
Out[115]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

时间慢得多,但如果您想一次访问所有 x 值,这比从所有字典中获取它们要好得多。

np.rec.fromarrays((x,y,z),names=['x','y','z'])

产生一个具有给定名称的recarray。速度差不多。

我还可以构造一个具有正确 dtype 和 shape 的空数组并将数组复制到其中。这可能和merge 一样快,但描述起来更复杂。

我建议优化使用/访问的数据结构,而不是构建速度。一般是构建一次,多次使用。

============

In [125]: dt=np.dtype([('x',x.dtype),('y',y.dtype),('z',z.dtype)])
In [126]: xyz=np.zeros(x.shape,dtype=dt)
In [127]: xyz['x']=x; xyz['y']=y; xyz['z']=z
# or for n,d in zip(xyz.dtype.names, (x,y,z)): xyz[n] = d
In [128]: xyz
Out[128]: 
array([(0, 10, 100), (1, 11, 101), (2, 12, 102), (3, 13, 103),
       (4, 14, 104), (5, 15, 105), (6, 16, 106), (7, 17, 107),
       (8, 18, 108), (9, 19, 109)], 
      dtype=[('x', '<i4'), ('y', '<i4'), ('z', '<i4')])

【讨论】:

  • 使用结构体数组是比较合适的做法,但没必要使用recfunctions
  • 我添加了非recfunctions版本。
猜你喜欢
  • 2016-07-31
  • 1970-01-01
  • 2012-11-28
  • 2017-04-05
  • 2017-05-05
  • 2022-11-17
  • 1970-01-01
  • 2018-08-28
  • 2020-10-11
相关资源
最近更新 更多