比较NumPy结构化数组

4

快速问题

我希望能够比较两个numpy结构化数组中特定的dtype字段,这些数组保证具有相同的dtype。我希望以一种允许每次调用函数时基于给定的输入比较不同字段的方式进行比较(即我无法轻松地为每个单独的字段硬编码比较)。

带有示例的详细问题

我试图比较具有相同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')])

现在假设我想检查并找到任何条目在a中,其a['pos']['x']字段大于b['pos']['x']字段,并将这些条目返回到一个新的numpy数组中,可以使用以下代码:

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

现在假设我们想保留 a 中仅当 x 字段和 y 字段都比 b 中对应的字段要大的项。这很简单,我们可以再次执行以下操作:

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

这将返回一个空数组,这是正确的答案。

现在,假设我们有一个非常复杂的dtype(比如包含34个字段——参见这里的dtype示例),我们想要能够比较它们中的任何一个,但可能不是全部(类似于上一个示例,但总体上具有更多的dtype字段和更多的需要比较的字段)。此外,如果我们想要比较的字段可以从一次运行到另一次运行发生变化(因此我们不能像上面那样硬编码),那么怎么办呢?这就是我试图找到解决方案的问题。

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

使用掩码数组

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

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中提取正确的条目。现在我想起来这可能是正确的方法。
我的长篇问题的结论。
尽管我认为我已经找到了解决这个问题的方法,但我仍然会发布这个问题,至少要看看是否有人有关于如何对结构化/掩码numpy数组进行逻辑比较的想法,因为我认为这将是一个有用的知识点,以及是否有比我想到的更好的方法。请注意,您可以通过逐行复制“具有示例的长问题”部分中的片段来轻松地形成MWE,我没有看到任何理由通过执行此操作来占用更多空间。
2个回答

3

要对一系列的列进行比较,您必须使用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替换为该列表。


谢谢。我也考虑过这个,不过当你有带有嵌套层级的数据类型时,比如 dtype=[('firstGroup',[('homework',int),('classwork',int)]),('roomsOfTheHouse',[('bathroom',[('sink',str),('tub',str)]),('kitchen',[('floor',str),('counter',str)])] ,我不确定它是否能够被处理。 - Andrew

2
我已经回答了许多结构化数组的问题和一些掩码数组的问题,但从未探索它们的组合。掩码是numpy中的一部分,并且结构化数组是较新的。不清楚开发人员是否曾经特别努力地开发过。我需要查看/usr/lib/python3/dist-packages/numpy/ma/core.py中的代码。
但很明显,跨字段的功能受到限制。
您可以“查看”字段的子集:
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()
< p > 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]])

我可以通过将原始的 a 转换为数字数组来进行相同的比较 - 通过使用 copyviewreshapecopy 将所需的数据元素放在一个连续的缓冲区中,view 更改 dtype(而不更改数据缓冲区)。
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)

我接受这个答案,因为它很好地深入考虑了这个主题(包括讨论unutbu的其他优秀答案),并且还解释了为什么事情是这样的。 - Andrew
多字段索引自我回答以来已经发生了变化。请阅读结构化数组文档以获取详细信息。 - hpaulj

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