Numpy:如何连接结构化数组?

30

输入

我有一个类似于以下示例的列表,其中包含许多numpy结构化数组

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
4个回答

47

您还可以使用 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
这个比我的原始解决方案更易读,而且快了1.32倍。谢谢! - Jon-Eric

20

这里是一个应该更快的实现。它将所有内容转换为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()


2
这比我的原始解决方案快166倍。我自己永远想不出来。谢谢! - Jon-Eric
2
@Jon-Eric:我稍微简化了一下代码(并且抛弃了as_strided())。希望这不会影响性能。同时,一定要看看joris的第二个答案 - Sven Marnach
@Jon-Eric:你是说它比原来快166倍,而不是1.66倍?只是想确认一下。 - Hans
@Hans 是的,快了166倍(当我最初测量时)。 - Jon-Eric
1
@Hans 这是一种相当低级的方法,基本上只是将旧数组作为内存块进行复制,完全忽略它们的结构。很明显这样可以很快地完成,但要理解为什么原始解决方案如此缓慢,您需要对代码进行分析。可能有很多原因,包括您提到的原因。 - Sven Marnach
显示剩余2条评论

8

还有一种方式,更易读且速度更快:

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. 用我的代码倒数第二行替换前七行。 - Sven Marnach
谢谢!这真的简化了代码。但是另一方面,我在IPython中使用%timeit测试了它,并通过用您的倒数第二行替换这7行,速度慢了两倍。我还将其与您的解决方案进行了比较,似乎比我的慢了约5倍。但我猜当数组列表中的元素数量增加时,您的解决方案会变得更好吧? - joris
为了获得有意义的时间,您需要使用大数组。就性能而言,我希望您的解决方案至少与我的相当。请注意,使用empty()而不是zeros()应该会加快速度。 - Sven Marnach
我认为使用 dtype.descr 是错误的。根据文档
警告:此属性专门用于PEP3118兼容性,并且不是与np.dtype兼容的数据类型描述。
- moi

1
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 循环可以改进,但它目前是最快的。 - lfjbb

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