比较 numpy 结构化数组

comparing numpy structured arrays

快速问题

我希望能够比较来自保证具有相同数据类型的两个 numpy 结构化数组的特定数据类型字段。我想以一种允许我们比较的字段每次根据给定输入调用函数时不同的方式来做到这一点(即我不能轻易地对每个单独字段的比较进行硬编码)

例子很长

我正在尝试比较来自两个具有相同 dtype 的 numpy 结构化数组的特定字段。例如,假设我们有

import numpy as np
from io import BytesIO

a = np.genfromtxt(BytesIO('12 23 0|23.2|17.9|0\n12 23 1|13.4|16.9|0'.encode()),dtype=[('id','U7'),('pos',[('x',float),('y',float)]),('flag','U1')],delimiter='|')

b = np.genfromtxt(BytesIO(' |23.0|17.91|0'.encode()),dtype=[('id','U7'),('pos',[('x',float),('y',float)]),('flag','U1')],delimiter='|')

这给出了

In[156]: a
Out[154]: 
array([('12 23 0', (23.2, 17.9), '0'), ('12 23 1', (13.4, 16.9), '0')], 
      dtype=[('id', '<U7'), ('pos', [('x', '<f8'), ('y', '<f8')]), ('flag', '<U1')])

In[153]: b
Out[151]: 
array([('', (23.0, 17.91), '0')], 
      dtype=[('id', '<U7'), ('pos', [('x', '<f8'), ('y', '<f8')]), ('flag', '<U1')])

现在假设我想检查并找到 aa['pos']['x'] 字段大于 b['pos']['x'] 字段的任何条目,并且 return 这些条目到新的 numpy 数组,像这样的东西可以工作

newArr = a[a["pos"]["x"]>b["pos"]["x"]]

现在假设我们只想保留 a 中的条目,其中 xy 字段都大于它们在 b 中的对应项。这很简单,因为我们可以再做一次

newArr = a[np.array([np.array([a['pos']['x']>b['pos']['x']),a['pos']['y']>b['pos']['y'])).all(axis=0)]

其中 return 一个空数组是正确答案。

但是现在,假设我们有一个非常复杂的数据类型用于这些数组(比如有 34 个字段——请参阅 以获取我正在使用的数据类型的示例)并且我们希望能够比较它们中的任何一个,但可能不是全部(类似于前面的示例,但总体上有更多的 dtype 字段,我们想要比较更多的字段。此外,如果我们想要比较的字段可以从 运行 运行(所以我们不能像上面那样硬编码)。这就是我试图找到解决方案的问题。

我目前(未完成)的解决方案尝试

使用掩码数组

我解决这个问题的第一个想法是使用掩码数组 select 我们要比较的数据类型字段。像这样(假设我们可以使所有比较相同):

mask = np.ones(z.shape,dtype=[('id',bool),('pos',[('x',bool),('y',bool)]),('flag',bool)])
# unmask the x and y fields so we can compare them 
mask['pos']['x']=0
mask['pos']['y']=0

maskedA = np.ma.masked_array(a, mask=mask)
# We need to do this or the masked array gets angry (at least in python 3)
b.shape = (1,)

maskedB = np.ma.masked_array(b, mask=mask)

现在我想做类似

的事情
test = (maskedA>maskedB).any(axis=1)

但这不起作用,因为您可以像这样比较结构化数组 --

TypeError: unorderable types: MaskedArray() > MaskedArray()

我也试过压缩屏蔽数组

test = (maskedA.compressed()>maskedB.compressed()).any(axis=1)

这会导致不同的错误

TypeError: ufunc 'logical_not' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

现在,我意识到上述错误很可能是因为我不完全理解结构化数组和掩码数组的工作原理,但这也是我问这个问题的部分原因。有什么方法可以使用掩码数组来做这样的事情吗?

我刚才想到的解决方案可能会奏效,而且总体上可能更好...

所以我在写这篇文章时想到的另一个选择是在我将解析用户输入以形成数组 b 时进行比较。它实际上只是向解析器中的每个条件添加几行以进行比较并将结果添加到一个 numpy 布尔数组中,然后我可以使用它从 a 中提取正确的条目。现在我想这可能是要走的路。

我漫长而杂乱无章的问题的结论。

尽管我认为我找到了解决这个问题的方法,但我仍然会 post 这个问题,至少要稍微看看是否 (a) 有人对如何做有任何想法与 structured/masked numpy 数组进行逻辑比较,因为我认为了解它是一件有用的事情,并且 (b) 看看是否有人比我想出的想法更好。请注意,您可以通过逐行复制“示例的长期问题”部分中的片段来非常轻松地形成 MWE,我认为没有任何理由通过这样做占用更多 space。

要对一系列列进行比较,您必须使用 Python 循环。循环可以以列表理解的形式出现,例如:

In [87]: np.all([a['pos'][key] > b['pos'][key] for key in a['pos'].dtype.names], axis=0)
Out[87]: array([False, False], dtype=bool)

这将为 a['pos'] 中的每个字段计算 a['pos'][key] > b['pos'][key],然后使用 np.all 沿 0 轴减少数组。

如果您希望将比较应用于某些字段列表,您当然可以 将 a['pos'].dtype.names 替换为该列表。

我已经回答了很多结构化数组的问题,还有一些屏蔽数组的问题,但从未探索过它们的组合。长期以来,屏蔽一直是 numpy 的一部分。结构化数组较新。目前尚不清楚开发人员是否曾为开发付出过特别的努力。我必须查看 /usr/lib/python3/dist-packages/numpy/ma/core.py.

中的代码

但显然跨字段的功能是有限的。

您可以 'view' 字段的子集:

In [116]: a['pos'][['y','x']]
Out[116]: 
array([(17.9, 23.2), (16.9, 13.4)], 
      dtype=[('y', '<f8'), ('x', '<f8')])

但您不能同时设置多个字段:

In [117]: a['pos'][['y','x']]=0
...
IndexError: unsupported iterator index

未实现与这些列视图的比较(可能还有其他操作)。

In [123]: a['pos'][['y','x']]>b['pos'][['y','x']]
...
TypeError: unorderable types: numpy.ndarray() > numpy.ndarray()

unutbu 已经建议迭代方法:

In [127]: [a['pos'][name]>b['pos'][name] for name in ['x','y']]
Out[127]: [array([ True, False], dtype=bool), array([False, False], dtype=bool)]

迭代 dtype 的名称在处理结构化数组时很常见。 recarray 复制数组的函数,逐字段复制(如果需要则递归)。 genfromtxt 将您的平面输入列表转换为与 dtype 匹配的嵌套元组集时,可能会进行某种名称迭代。

将深层嵌套级别转换为数组可能会有所帮助。例如,我可以将 ('x','y') 转换为 (2,) 数组:

In [141]: a1=np.array([('12 23 0', (23.2, 17.9), '0'), ('12 23 1', (13.4, 16.9), '0')], 
      dtype=[('id', '<U7'), ('pos', '<f8',(2,)), ('flag', '<U1')])
In [142]: b1=np.array([('', (23.0, 17.91), '0')], dtype=a1.dtype)
In [143]: a1['pos']>b1['pos']
Out[143]: 
array([[ True, False],
       [False, False]], dtype=bool)
In [145]: a1['pos']
Out[145]: 
array([[ 23.2,  17.9],
       [ 13.4,  16.9]])

我可以通过使用 copyviewreshape 将其转换为数字数组来与原始 a 进行相同的比较。 copy 将所需的数据元素放在一个连续的缓冲区中,view 更改数据类型(不更改数据缓冲区)。

In [150]: a['pos'].copy().view(float)
Out[150]: array([ 23.2,  17.9,  13.4,  16.9])

In [153]: a['pos'].copy().view(float).reshape(-1,2)>b['pos'].copy().view(float)
Out[153]: 
array([[ True, False],
       [False, False]], dtype=bool)