Numpy中的结构化数组没有二元运算符?

8

好的,经过学习numpy结构化数组的教程后,我能够创建一些简单的示例:

from numpy import array, ones
names=['scalar', '1d-array', '2d-array']
formats=['float64', '(3,)float64', '(2,2)float64']
my_dtype = dict(names=names, formats=formats)
struct_array1 = ones(1, dtype=my_dtype)
struct_array2 = array([(42., [0., 1., 2.], [[5., 6.],[4., 3.]])], dtype=my_dtype)

我的预期用例��有超过三个条目,并且将使用非常长的一维数组。直到我们尝试执行一些基本的数学运算,所有事情都很顺利。我会得到以下所有内容的错误:

struct_array1 + struct_array2
struct_array1 * struct_array2
1.0 + struct_array1
2.0 * struct_array2

显然,即使是最简单的结构化数组也不支持简单的运算符(+、-、*、/)。我有什么遗漏吗?我应该看其他的包吗(不要说Pandas,因为这对它来说太过杀伤力了)?这似乎是一个显而易见的功能,所以我有点困惑。但很难在网络上找到任何关于此的讨论。这是否严重限制了结构化数组的实用性?为什么有人会使用结构化数组而不是打包成字典的数组?是否有技术原因导致这可能是棘手的?或者,如果正确的解决方案是进行繁琐的重载工作,那么如何在保持操作快速的同时完成呢?
3个回答

4
在numpy结构化数组的文档页面上,大多数示例涉及混合数据类型,如浮点数、整数和字符串。在SO上,大多数结构化数组问题都与从CSV文件加载混合数据有关。另一方面,在您的示例中,结构的主要目的似乎是为“列”命名。
您可以对命名列进行数学运算,例如:
struct_array1['scalar']+struct_array2['scalar']
struct_array1['2d-array']+struct_array2['2d-array']

您可以对字段进行“迭代”:
for n in my_dtype['names']:
    print a1[n]+a2[n]

是的,为了达到这个目的,将那些数组值放在字典中,或者作为对象的属性,同样可以起到同样的效果。

然而,考虑到CSV情况,有时我们想要谈论CSV或结构化数组的特定“行”,例如struct_array[0]。这样的“行”是一组值的元组。

无论如何,在numpy中主要的数据结构是由数字值组成的多维数组,大部分代码都围绕着数字数据类型-浮点数、整数等展开。结构化数组是对此的一般化,使用的元素本质上只是一组固定的字节。这些字节的解释由dtype确定。

想一想MATLAB是如何发展的——矩阵先出现,然后是单元格(像Python列表),然后是结构体,最后是类和对象。Python已经有了列表、字典和对象。numpy添加了数组。它不需要重新发明通用的Python结构。

我倾向于定义一个像这样的类:

class Foo(object):
    def __init__(self):
        self.scalar = 1
        self._1d_array = np.arange(10)
        self._2d_array = np.array([[1,2],[3,4]])

仅实现应用程序所需的二进制操作。


感谢您的回复。这有助于解释情况。在问题的核心,似乎numpy开发人员针对结构化数组有一个用例,并且我的用例不兼容。这就是生活。 - user2789194

3

另一种操作整个数组的方法是使用文档中描述的“union” dtype。在您的示例中,您可以通过添加一个“union”字段并指定重叠的“offsets”来扩展您的dtype:

from numpy import array, ones, zeros

names=['scalar', '1d-array', '2d-array', 'union']
formats=['float64', '(3,)float64', '(2,2)float64', '(8,)float64']
offsets=[0, 8, 32, 0]
my_dtype = dict(names=names, formats=formats, offsets=offsets)
struct_array3=zeros((4,), dtype=my_dtype)

['union'] 现在提供了一个 (n,8) 数组的所有数据访问权限。

struct_array3['union'] # == struct_array3.view('(8,)f8')
struct_array3['union'].shape  # (4,8)

您可以对“union”或任何其他字段进行操作:

struct_array3['union'] += 2
struct_array3['scalar']= 1

'union'字段可以是另一个兼容的形状,比如'(2,4)float64'。这样的数组的“行”可能长这样:
array([ (3.0, [0.0, 0.0, 0.0], [[2.0, 2.0], [0.0, 0.0]], 
      [[3.0, 0.0, 0.0, 0.0], [2.0, 2.0, 0.0, 0.0]])], 
      dtype={'names':['scalar','1d-array','2d-array','union'], 
             'formats':['<f8',('<f8', (3,)),('<f8', (2, 2)),('<f8', (2, 4))], 
             'offsets':[0,8,32,0], 
             'itemsize':64})

0

好的,在进一步研究后,我找到了答案。(不是 hpaulj 的错 - 问题没有被很好地提出。)但我想发布这篇文章,以便其他人有类似的沮丧。

答案来自于 numpy 关于 ndarray.view 的文档。他们特别提供了一个例子,其中他们“[创建]在结构化数组上的视图,使其可以用于计算”。

所以,我感到沮丧的是,我无法对我的结构化数组进行操作。毕竟,我把我的结构化数组看作是一组浮点数!最终,我所需要的就是使用“view”告诉 numpy 这个抽象概念。可以通过以下方式避免问题中的错误:

( struct_array1.view(dtype='float64') + struct_array2.view(dtype='float64') ).view(dtype=my_dtype)
( struct_array1.view(dtype='float64') + struct_array2.view(dtype='float64') ).view(dtype=my_dtype)
( 1.0 + struct_array2.view(dtype='float64') ).view(dtype=my_dtype)
( 2.0 * struct_array2.view(dtype='float64') ).view(dtype=my_dtype)

这并不像人们想要的那样优雅,但至少numpy具备了这种能力。


可能可以将这种替代视图构建到原始数据类型中。查看将数据视为整数和字节集的示例。我会在回到带有numpy的计算机时尝试这个想法。 - hpaulj
如果您想保留数组的形状,请使用类似于 struct_array1.view('(8,)f8') 的东西,其中 (8,) 是您的 dtype 中浮点数的数量,即 struct_array1.dtype.itemsize/8 - hpaulj
我添加了一个答案,使用重叠字段作为“视图”的(兼容的)替代方案。 - hpaulj

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