【问题标题】:Odd behavior of using += with numpy.array and numpy.ma.array将 += 与 numpy.array 和 numpy.ma.array 一起使用的奇怪行为
【发布时间】:2020-12-11 06:59:13
【问题描述】:

谁能向我解释以下结果? 我知道它不像通常那样做这个操作,但我发现这个结果很奇怪。

import numpy as np

a = np.ma.masked_where(np.arange(20)>10,np.arange(20))
b = np.ma.masked_where(np.arange(20)>-1,np.arange(20))
c = np.zeros(a.shape)
d = np.zeros(a.shape)

c[~a.mask] += b[~a.mask]

print(b[~a.mask])
#masked_array(data=[--, --, --, --, --, --, --, --,--, --, --],
#             mask=[ True,  True,  True,  True,  True,  True,  True,  True, True,  True,  True],
#       fill_value=999999,
#            dtype=int64)

print(c)
#[ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10.  0.  0.  0.  0. 0.  0.  0.  0.  0.]

d[~a.mask] = d[~a.mask] + b[~a.mask]

print(d)
#[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

我预计c 不会改变,但我想这里有一些与内存中的对象相关的事情。此外,+= 保留原始对象,而=+ 创建一个新的d

我只是不太明白添加到c 的数据来自哪里。

【问题讨论】:

  • 添加到c 的数据来自b 而不是a(如果你给他们不同的值可以看出),但是有一些奇怪的东西np.ndarray.__iadd__ 会忽略另一个数组的掩码,而np.ndarray.__add__ 将考虑掩码。不过,还有一个比c[~a.mask] += b[~a.mask] 更简单的例子。只需c += b。布尔切片不是这种行为的基础。
  • 如果c 本身是一个掩码数组,您会得到预期的结果。但由于它是一个普通数组,+= 是用b.data 执行的。一般来说,不要混合屏蔽和未屏蔽的操作和数组。如果要保留遮罩的效果,请使用遮罩的方法和函数。
  • 谢谢。是的,使用 b.data 是有道理的。正如我所提到的,我确实在我的真实(更复杂)脚本中使用了一个掩码数组作为占位符(问题中的 c)。

标签: python arrays numpy numpy-ndarray masked-array


【解决方案1】:

我将从一个更简单的例子开始,以便更好地理解:

b = np.ma.masked_where(np.arange(20)>-1,np.arange(20))
#b: [-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --]
#b.data: [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
c = np.zeros(b.shape)
#c: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
d = np.zeros(b.shape)
#d: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

c += b
#c: [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.]

d = d + b
#d: [-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --]
#d.data: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

第一个操作c += b就地操作。也就是说,相当于c = type(c).__iadd__(c, b)根据c的类型做加法,不是掩码数组,所以b的数据作为未掩码使用。

另一方面,d = d + b 等价于 d = np.MaskedArray.__add__(d, b)(更具体地说,由于掩码数组是 ndarray 的子类,因此它使用 __radd__)并且是 NOT 就地 分配。这意味着它创建一个新对象并在添加时使用等式右侧的更宽类型,因此将 d (这是一个未屏蔽的数组)转换为一个屏蔽数组(因为b 是一个屏蔽数组),因此加法仅使用有效值(在这种情况下没有有效值,因为b 的所有元素都被屏蔽且无效)。这将产生一个掩码数组d,其掩码与b 相同,而d 的数据保持不变。

这种行为差异不是 Numpy 特有的,也适用于 python 本身。 OP 在问题中提到的情况具有类似的行为,并且正如 @alaniwi 在 cmets 中提到的那样,带有掩码 a 的布尔索引并不是该行为的基础。使用a 屏蔽bcd 的元素只是将分配限制为a(而不是数组的所有元素)对屏蔽元素的赋值,仅此而已。

为了让事情变得更有趣,实际上更清晰,让我们将右侧的bd 的位置互换:

e = np.zeros(b.shape)
#e: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

e = b + e
#e: [-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --]
#e.data: [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.]

请注意,类似于d = d + b,右侧使用掩码数组__add__ 函数,因此输出是掩码数组,但由于您将e 添加到b(又名e = np.MaskedArray.__add__(b, e)) ,则返回b 的掩码数据,而在d = d + b 中,将b 添加到d 并返回d 的数据。

【讨论】:

  • 一件事仍然不完全清楚:如果您使用我的原始代码,您知道为什么 d 仍然是 numpy.ndarray 而不是 numpy.ma.core.MaskedArray 吗? (Python 3.7.3,numpy 1.16.2)
  • @pythonewbie 好点。当您分配元素(而不是整个对象本身)时,您在技术上将数组的项目设置为新对象(而不是整个对象本身)。因此,当您使用掩码进行索引时,数组类型不会改变,但仍会使用 MaskedArray.__add__。换句话说,d[i]=d[i]+b[i] 等于 d.__setitem__(np.MaskedArray.__radd__(d.__getitem__(i), b.__getitem__(i))),因此,无效值不会添加到 d 的值中。
  • 再次感谢!我的问题在几个小时内得到了完全回答。很棒的东西。
猜你喜欢
  • 1970-01-01
  • 2015-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 2012-01-23
相关资源
最近更新 更多