【问题标题】:Numpy: Joining structured arrays?Numpy:加入结构化数组?
【发布时间】:2011-03-18 17:33:54
【问题描述】:

输入

我有很多 numpy structured arrays 在像这个例子这样的列表中:

import numpy

a1 = numpy.array([(1, 2), (3, 4), (5, 6)], dtype=[('x', int), ('y', int)])

a2 = numpy.array([(7,10), (8,11), (9,12)], dtype=[('z', int), ('w', float)])

arrays = [a1, a2]

期望的输出

将它们全部连接在一起以创建如下统一结构化数组的正确方法是什么?

desired_result = numpy.array([(1, 2, 7, 10), (3, 4, 8, 11), (5, 6, 9, 12)],
                             dtype=[('x', int), ('y', int), ('z', int), ('w', float)])

当前方法

这是我目前正在使用的,但是速度很慢,所以我怀疑必须有更有效的方法。

from numpy.lib.recfunctions import append_fields

def join_struct_arrays(arrays):
    for array in arrays:
        try:
            result = append_fields(result, array.dtype.names, [array[name] for name in array.dtype.names], usemask=False)
        except NameError:
            result = array

    return result

【问题讨论】:

    标签: python numpy


    【解决方案1】:

    还可以使用numpy.lib.recfunctions的函数merge_arrays

    import numpy.lib.recfunctions as rfn
    rfn.merge_arrays(arrays, flatten = True, usemask = False)
    
    Out[52]: 
    array([(1, 2, 7, 10.0), (3, 4, 8, 11.0), (5, 6, 9, 12.0)], 
         dtype=[('x', '<i4'), ('y', '<i4'), ('z', '<i4'), ('w', '<f8')])
    

    【讨论】:

    • 这比我原来的解决方案更具可读性和快 1.32 倍。谢谢!
    【解决方案2】:

    这是一个应该更快的实现。它将所有内容转换为numpy.uint8 的数组,并且不使用任何临时对象。

    def join_struct_arrays(arrays):
        sizes = numpy.array([a.itemsize for a in arrays])
        offsets = numpy.r_[0, sizes.cumsum()]
        n = len(arrays[0])
        joint = numpy.empty((n, offsets[-1]), dtype=numpy.uint8)
        for a, size, offset in zip(arrays, sizes, offsets):
            joint[:,offset:offset+size] = a.view(numpy.uint8).reshape(n,size)
        dtype = sum((a.dtype.descr for a in arrays), [])
        return joint.ravel().view(dtype)
    

    编辑:简化代码并避免不必要的as_strided()

    【讨论】:

    • 这比我原来的解决方案快 166 倍。我会永远自己想出这个。谢谢!
    • @Jon-Eric:我稍微简化了代码(并抛弃了as_strided())。我希望这不会影响性能。也一定要看看joris' second answer
    • @Jon-Eric:你说它快 166 倍,而不是 1.66 倍?只是想确认一下。
    • @Hans 是的,快了 166 倍。 (当我最初测量时。)
    • @Hans 这是一种相当低级的方法,它基本上只是将旧数组复制为内存块,完全忽略它们的结构。很清楚为什么可以相当快地完成此操作,但要了解原始解决方案为何如此缓慢,您需要分析代码。可能有很多原因,包括你提到的那个。
    【解决方案3】:
    def join_struct_arrays(*arrs):
        dtype = [(name, d[0]) for arr in arrs for name, d in arr.dtype.fields.items()]
        r = np.empty(arrs[0].shape, dtype=dtype)
        for a in arrs:
           for name in a.dtype.names:
               r[name] = a[name]
        return r
    

    【讨论】:

    • 或许for循环可以改进,但目前最快
    【解决方案4】:

    还有另一种方式,我认为可读性更强,速度也更快:

    def join_struct_arrays(arrays):
        newdtype = []
        for a in arrays:
            descr = []
            for field in a.dtype.names:
                (typ, _) = a.dtype.fields[field]
                descr.append((field, typ))
            newdtype.extend(tuple(descr))
        newrecarray = np.zeros(len(arrays[0]), dtype = newdtype)
        for a in arrays:
            for name in a.dtype.names:
                newrecarray[name] = a[name]
        return newrecarray
    

    编辑:在 Sven 的建议下,它变成了(有点慢,但实际上非常易读):

    def join_struct_arrays2(arrays):
        newdtype = sum((a.dtype.descr for a in arrays), [])
        newrecarray = np.empty(len(arrays[0]), dtype = newdtype)
        for a in arrays:
            for name in a.dtype.names:
                newrecarray[name] = a[name]
        return newrecarray
    

    【讨论】:

    • 很好,+1!两个建议: 1. 使用numpy.empty() 而不是numpy.zeros()——不需要初始化数据。 2. 用我的代码的最后一行替换前七行。
    • 谢谢!这确实简化了代码。但另一方面,我在 IPython 中使用 %timeit 对其进行了测试,并且通过将这 7 行替换为最后一行,它慢了两倍。我还将它与您的解决方案进行了比较,它看起来比我的慢 5 倍左右。但是我猜想当数组列表中的元素数量增加时,你的解决方案会变得更好?
    • 要获得有意义的计时,您需要使用大数组。就性能而言,我希望您的解决方案至少与我的解决方案相当。请注意,使用 empty() 而不是 zeros() 应该会加快速度。
    • 我认为使用dtype.descr是错误的。根据documentation:> 警告:此属性专门为 PEP3118 合规而存在,不是与 np.dtype 兼容的数据类型描述。
    猜你喜欢
    • 2016-04-28
    • 2019-05-08
    • 1970-01-01
    • 1970-01-01
    • 2019-08-11
    • 2016-03-29
    • 2012-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多