从字典创建NumPy数组的最佳方法是什么?

8

我刚开始学习NumPy,可能会缺少一些核心概念...

从值为列表的字典创建NumPy数组的最佳方法是什么?

类似于这样:

d = { 1: [10,20,30] , 2: [50,60], 3: [100,200,300,400,500] }

应该转化为类似以下内容:

data = [
  [10,20,30,?,?],
  [50,60,?,?,?],
  [100,200,300,400,500]
]

我将对每一行进行基本的统计分析,例如:

deviations = numpy.std(data, axis=1)

问题:
  • 从字典创建numpy.array的最佳/最有效方法是什么?字典很大,有几百万个键,每个键具有约20个项目。

  • 每个'行'的值数量不同。如果我正确理解numpy需要统一大小,那么我应该填写缺失项以使std()函数正常工作?

更新:我忘了提到的一件事-虽然Python技术是合理的(例如遍历几百万项很快),但它受限于单个CPU。Numpy操作可以很好地扩展到硬件并且可利用所有CPU,因此它们很有吸引力。
3个回答

8
你不需要创建numpy数组就可以调用numpy.std()。你可以在循环遍历字典的所有值时调用numpy.std()。列表将会即时转换为numpy数组以计算标准差。
这种方法的缺点是主循环将在Python中而不是在C中。但我想这应该足够快:你仍然可以以C速度计算std,并且你将节省大量内存,因为你不必在变量大小数组中存储0值。
  • 如果您想进一步优化此代码,可以将值存储在numpy数组的列表中,这样您只需要进行一次python列表-> numpy数组转换。
  • 如果您发现速度仍然太慢,请尝试使用psycho来优化python循环。
  • 如果速度仍然太慢,请尝试使用Cython和numpy模块一起使用。这个Tutorial声称在图像处理方面有令人印象深刻的速度提升。或者直接在Cython中编写整个std函数(请参见this以获取sum函数的基准测试和示例)。
  • 与Cython相比,另一种选择是使用SWIGnumpy.i
  • 如果您想仅使用numpy并在C级别上计算所有内容,请将同一大小的所有记录分组到不同的数组中,并在每个数组上调用numpy.std()。它应该看起来像以下示例。

具有O(N)复杂度的示例:

import numpy
list_size_1 = []
list_size_2 = []
for row in data.itervalues():
    if len(row) == 1:
      list_size_1.append(row)
    elif len(row) == 2:
      list_size_2.append(row)
list_size_1 = numpy.array(list_size_1)
list_size_2 = numpy.array(list_size_2)
std_1 = numpy.std(list_size_1, axis = 1)
std_2 = numpy.std(list_size_2, axis = 1)

我现在正在循环中使用numpy.std,你说得对,内存节省很重要。不过我至少想与numpy版本进行速度比较。 - Parand
问题在于numpy.std()只接受固定大小的数组。所以我唯一能想到的方法是将所有相同大小的记录分组在一起,并对每个组调用numpy.std()函数来进行测试。 - Mapad
分组相同大小的记录,简单而有效。我喜欢它。 - Parand

2

虽然这里已经有一些相当合理的想法了,但我认为以下内容也值得一提。

用任何默认值填补缺失的数据都会破坏统计特征(std等)。显然这就是Mapad提出同样大小的记录分组的好方法。问题在于它需要比直接解决方案更多的计算(假设没有关于记录长度的先验数据):

  1. 至少需要 O(N*logN) 次 'len' 调用和比较来排序(使用有效的算法)
  2. O(N) 次检查,在第二次遍历列表时获取组(它们在“垂直”轴上的起始和结束索引)

使用Psyco是个好主意(它非常容易使用,所以一定要试试)。

似乎最佳方式是采用Mapad在项目1中描述的策略,但进行修改-不生成整个列表,而是通过迭代字典将每行转换为numpy.array并执行所需的计算。像这样:

for row in data.itervalues():
    np_row = numpy.array(row)    
    this_row_std = numpy.std(np_row)
    # compute any other statistic descriptors needed and then save to some list

无论如何,在Python中执行几百万次循环不会像人们想象的那样花费太长时间。此外,这似乎不是例行计算,因此如果偶尔运行一次甚至只运行一次需要额外的一秒钟/一分钟又有什么关系。
Mapad所建议的泛化变体:
from numpy import array, mean, std

def get_statistical_descriptors(a):
    if ax = len(shape(a))-1
    functions = [mean, std]
    return f(a, axis = ax) for f in functions


def process_long_list_stats(data):
    import numpy

    groups = {}

    for key, row in data.iteritems():
        size = len(row)
        try:
            groups[size].append(key)
        except KeyError:
            groups[size] = ([key])

    results = []

    for gr_keys in groups.itervalues():             
        gr_rows = numpy.array([data[k] for k in gr_keys])       
        stats = get_statistical_descriptors(gr_rows)                
        results.extend( zip(gr_keys, zip(*stats)) )

    return dict(results)

谢谢Maleev,这基本上就是我最终所做的。有一件事我忘了提到——虽然在Python中循环速度很快,但我相信我只使用了单个CPU来执行此方法。矩阵操作可以利用所有CPU,因此它们更具吸引力。 - Parand
为什么需要在按长度分组向量之前对行进行排序?只需要分组即可。此外,我会小心使用大O符号:这里N约为1000000,但Python和C程序之间的速度可能慢100倍左右。因此,N-> 1000并不真正趋近于无穷大。 - Mapad
2 Parand:你说得对,考虑到多线程确实是有意义的。2 Mapad:如果我没有非常错误的话,分组本质上等同于排序。那么你建议如何进行分组呢? - Maleev
Python代码,无论是仅循环遍历行还是分组,都会在任何情况下执行。因此,仅谈论Python代码的渐近复杂度,我们得到了pO(NlogN) - pO(N) = pO(NlogN)的差异。此外,C代码在组内循环遍历行会增加cO(N)的复杂度。 - Maleev
你说 c << p。当然。但这仍然留下了p*O(NlogN)的差异。除非你证明你可以在平均和最坏情况下以O(N)的速度进行分组。 - Maleev
我同意你的简化,只是想提醒一下它们假设N >> p(p是Python循环引入的计算时间与C循环处理记录相比)。由于这里的所有过程都需要O(N)(请参见我的示例),因此我不会在检查复杂性时忽略p的大O符号。 - Mapad

0

numpy字典

您可以使用结构化数组来保留通过键访问numpy对象的能力,就像使用字典一样。

import numpy as np


dd = {'a':1,'b':2,'c':3}
dtype = eval('[' + ','.join(["('%s', float)" % key for key in dd.keys()]) + ']')
values = [tuple(dd.values())]
numpy_dict = np.array(values, dtype=dtype)

numpy_dict['c']

现在将输出

array([ 3.])

然而,生成的数组具有嵌套元组,因此某些操作可能会变慢。 - Davoud Taghawi-Nejad

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