如何在numpy中更改掩码数组的值?

6
在我的代码中,有一个地方我试图修改掩蔽数组的值,但是Python似乎没有响应。我认为这与数组存储内存的方式有关,好像我修改的是值的副本而不是值本身,但我对此了解不够,无法解决它。
下面是我正在尝试做的一个简化版本:
    x = np.zeros((2,5)) # create 2D array of zeroes
    x[0][1:3] = 5       # replace some values along 1st dimension with 5

    mask = (x[0] > 0)   # create a mask to only deal with the non negative values

    x[0][mask][1] = 10  # change one of the values that is non negative 

    print x[0][mask][1] # value isn't changed in the original array

这个的输出结果是:

    5.0

当它应该是10时。

任何帮助都将不胜感激,理想情况下,这需要可扩展性(这意味着我不一定知道x的形状,或者值为非负数的位置,或者我需要修改哪个)。

我正在使用numpy 1.11.0,在Ubuntu 16.04.2上的python 2.7.12进行工作。

谢谢!


尽可能使用一组索引括号,而不是多个,例如 x[0, 1:3]x[0, mask]。但也要记住,使用布尔掩码进行索引会产生副本。 - hpaulj
3个回答

4
让我们将您的问题泛化一下:
In [164]: x=np.zeros((2,5))
In [165]: x[0, [1, 3]] = 5      # index with a list, not a slice
In [166]: x
Out[166]: 
array([[ 0.,  5.,  0.,  5.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])

当索引发生在等号的右边时,它是 __setitem__ 的一部分,并作用于原始数组。无论使用切片、列表还是布尔掩码进行索引,这都是正确的。
但是,使用列表或掩码进行选择会产生一份副本。进一步的索引赋值只影响该副本,而不是原始数组。
In [167]: x[0, [1, 3]]
Out[167]: array([ 5.,  5.])
In [168]: x[0, [1, 3]][1] = 6
In [169]: x
Out[169]: 
array([[ 0.,  5.,  0.,  5.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])

最好的方法是修改掩码本身:
In [170]: x[0, np.array([1,3])[1]] = 6
In [171]: x
Out[171]: 
array([[ 0.,  5.,  0.,  6.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])

如果mask是布尔值,您可能需要将其转换为索引数组。
In [174]: mask = x[0]>0
In [175]: mask
Out[175]: array([False,  True, False,  True, False], dtype=bool)
In [176]: idx = np.where(mask)[0]
In [177]: idx
Out[177]: array([1, 3], dtype=int32)
In [178]: x[0, idx[1]]
Out[178]: 6.0

或者你可以直接调整布尔值

In [179]: mask[1]=False
In [180]: x[0,mask]
Out[180]: array([ 6.])

在处理大问题时,您需要注意索引产生视图并且是一个副本的情况。您需要熟悉使用列表、数组和布尔值进行索引,并理解如何在它们之间切换。


这太完美了,谢谢!这个可行且可扩展,我只需要重新思考一下如何处理掩码和数组。我不知道多次索引会导致Python创建副本,但这正是我怀疑的。 - Jesse Rio

1
你所创建的不是真正的掩码数组:
x = np.zeros((2,5))
x[0][1:3] = 5
mask = (x[0] > 0)
mask
Out[14]: array([False,  True,  True, False, False], dtype=bool)

所以,这只是一个布尔数组。要创建掩码数组,您应该使用numpy.ma模块:

masked_x = np.ma.array(x[0], mask=~(x[0] > 0)) # let's mask first row as you did
masked_x
Out[15]: 
masked_array(data = [-- 5.0 5.0 -- --],
             mask = [ True False False  True  True],
       fill_value = 1e+20)

现在您可以更改掩码数组,相应地更改主数组:
masked_x[1] = 10.    
masked_x
Out[36]: 
masked_array(data = [-- 10.0 5.0 -- --],
             mask = [ True False False  True  True],
       fill_value = 1e+20)    
x
Out[37]: 
array([[  0.,  10.,   5.,   0.,   0.],
       [  0.,   0.,   0.,   0.,   0.]])

请注意,在掩码数组中,无效条目标记为True

他可以使用这个布尔数组作为掩码,而不需要使用np.ma.array步骤。他可能在宽泛地使用“掩码数组”这个术语。 - hpaulj
@hpaulj 可能吧。但我认为实现他想要的(通过掩码更改初始数组的值)最好的方法是实际上使用掩码数组。至少如果我正确理解问题的话。 - Vadim Shkaberda
理想情况下,我希望尽可能简单,意味着我有一个数组可供使用,然后通过掩码修改该数组,而不创建掩码数组的副本(就像numpy.ma模块所做的那样)。 @hpaulj的答案解决了我的问题,谢谢。 - Jesse Rio
@JesseRio 不用谢。但是掩码数组的好处在于它不会创建副本masked_x.data.base is x; Out[12]: True。它创建了一个视图,其内存与x共享。 - Vadim Shkaberda

1
为了理解发生了什么,我建议阅读http://scipy-cookbook.readthedocs.io/items/ViewsVsCopies.html
这归结于高级索引的误导性使用。 以下语句是相同的,正如您所看到的,它直接使用掩码将x的元素设置为10。
x[0][mask] = 10
x[0,mask] = 10
x.__setitem__((0, mask), 10)

你正在做的是以下内容
x[0][mask][1] = 10
x[0,mask][1] = 10
x[0,mask].__setitem__(1, 10)
x.__getitem__((0, mask)).__setitem__(1, 10)

通过使用__getitem__() 创建副本

总之,您需要重新考虑如何使用不同的掩码修改该单个数字__setitem()__


谢谢!这正是我怀疑的,你提供的链接非常有见地。我已经接受了@hpaulj的答案,因为他也提供了一个解决方案。 - Jesse Rio

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