将NumPy数组中所有大于某个值的元素替换掉。

275
我有一个2D的NumPy数组。如何将其中大于阈值T = 255的所有值替换为x = 255?一种慢速的基于循环的方法是:
# arr = arr.copy()  # Optionally, do not modify original arr.

for i in range(arr.shape[0]):
    for j in range(arr.shape[1]):
        if arr[i, j] > 255:
            arr[i, j] = x

1
有关更多信息,请查看这个索引入门 - askewchan
8个回答

460

我认为这个问题最快也最简洁的解决方法是使用NumPy内置的高级索引。如果你有一个名为arrndarray数组,你可以按如下方式用一个值x替换所有大于>255的元素:

arr[arr > 255] = x

我在我的电脑上运行了一个500 x 500的随机矩阵,将所有大于0.5的值替换成5,并且平均花费了7.59毫秒。

In [1]: import numpy as np
In [2]: A = np.random.rand(500, 500)
In [3]: timeit A[A > 0.5] = 5
100 loops, best of 3: 7.59 ms per loop

4
请注意,这会修改现有的数组arr,而不是像原始帖子中创建一个result数组。 - askewchan
2
有没有一种方法可以不修改 A 而创建一个新数组来实现这个? - sodiumnitrate
1
如果我们想要更改索引为给定 n 的倍数的值,比如a[2]、a[4]、a[6]、a[8]......(其中n=2),我们该怎么做呢? - lavee_singh
6
请注意,如果数据存储在Python列表中,则以下方法无法奏效,数据必须存储在NumPy数组中(np.array([1,2,3])。 - mjp
2
能否使用此索引更新每个值而无需条件?我想这样做:array[ ? ] = x,将每个值设置为x。其次,是否可以执行多个条件,例如: array[ ? ] = 255 if array[i] > 127 else 0我想优化我的代码,目前正在使用列表推导式,但比这种高级索引慢得多。 - AgentM
显示剩余6条评论

62
如果你想要一个新的数组 result,其中包含了 arr 的副本,只有当 arr < 255 时,否则为 255
result = np.minimum(arr, 255)

更一般地说,对于一个下界和/或上界:
result = np.clip(arr, 0, 255)

如果你只想访问超过255的值,或者需要更复杂的操作,@mtitan8的回答更通用,但是np.clip和np.minimum(或np.maximum)对于你的情况来说更好且速度更快。
In [292]: timeit np.minimum(a, 255)
100000 loops, best of 3: 19.6 µs per loop

In [293]: %%timeit
   .....: c = np.copy(a)
   .....: c[a>255] = 255
   .....: 
10000 loops, best of 3: 86.6 µs per loop

如果你想在原地进行操作(即修改`arr`而不是创建`result`),你可以使用`np.minimum`的`out`参数。
np.minimum(arr, 255, out=arr)

或者

np.clip(arr, 0, 255, arr)

out=名称是可选的,因为参数的顺序与函数的定义相同。)
对于原地修改,布尔索引速度提高了很多(无需单独创建和修改副本),但仍然不如minimum快。
In [328]: %%timeit
   .....: a = np.random.randint(0, 300, (100,100))
   .....: np.minimum(a, 255, a)
   .....: 
100000 loops, best of 3: 303 µs per loop

In [329]: %%timeit
   .....: a = np.random.randint(0, 300, (100,100))
   .....: a[a>255] = 255
   .....: 
100000 loops, best of 3: 356 µs per loop

假设你想要限制数值的最小和最大值,如果没有使用 clip 函数,你需要重复两次操作,类似于下面的代码:

np.minimum(a, 255, a)
np.maximum(a, 0, a)

或者,
a[a>255] = 255
a[a<0] = 0

1
非常感谢您的完整评论,但在这种情况下,np.clip和np.minimum似乎不是我所需要的。在OP中,您可以看到阈值T和替换值(255)不一定是相同的数字。尽管如此,我仍然给了您一个赞,因为您的回答非常详细。再次感谢。 - NLi10Me
如果我们想要改变索引为给定 n 的倍数的值,比如 a[2]、a[4]、a[6]、a[8]......,我们该怎么做呢? - lavee_singh
@lavee_singh,要做到这一点,您可以使用切片的第三部分,通常被忽略:a[start:stop:step]会给您返回从startstop的数组元素,但是它只会取每个step(如果省略,默认为1)的元素。因此,要将所有偶数设置为零,您可以执行a[::2] = 0 - askewchan
谢谢,我需要这样的东西,尽管我知道它适用于简单列表,但我不知道它是否适用于numpy.array。 - lavee_singh
在我的调查中,令人惊讶的是 a = np.maximum(a,0)np.maximum(a,0,out=a) 更快。 - Muhammad Yasirroni

22

我认为您可以通过使用 where 函数最快实现这一点:

例如,在numpy数组中查找大于0.2的项目并将它们替换为0:

import numpy as np

nums = np.random.rand(4,3)

print np.where(nums > 0.2, 0, nums)

16

另一种方法是使用np.place,它进行就地替换,并适用于多维数组:

import numpy as np

# create 2x3 array with numbers 0..5
arr = np.arange(6).reshape(2, 3)

# replace 0 with -10
np.place(arr, arr == 0, -10)

这是我使用的解决方案,因为它是我遇到的第一个。我想知道这个和上面选择的答案之间是否有很大的区别。你认为呢? - jonathanking
在我的非常有限的测试中,我使用np.place的上述代码比被接受的答案的直接索引方法运行得慢2倍。这很令人惊讶,因为我本以为np.place会更加优化,但我猜他们可能在直接索引上投入了更多的工作。 - Shital Shah
1
在我的情况下,与内置方法相比,np.place 的速度也较慢,尽管在这个评论中声称相反。 - riyansh.legend

15

您可以考虑使用numpy.putmask

np.putmask(arr, arr>=T, 255.0)

这里是与Numpy内置索引的性能比较:
In [1]: import numpy as np
In [2]: A = np.random.rand(500, 500)

In [3]: timeit np.putmask(A, A>0.5, 5)
1000 loops, best of 3: 1.34 ms per loop

In [4]: timeit A[A > 0.5] = 5
1000 loops, best of 3: 1.82 ms per loop

1
我已经测试了代码,当使用上限为0.5而不是5时,使用索引比np.putmask快大约两倍。 - Ali_Sh

9

您还可以使用&|(和/或)来获取更多的灵活性:

5到10之间的值:A[(A>5)&(A<10)]

大于10或小于5的值:A[(A<5)|(A>10)]


6

np.where()非常好用!

np.where(arr > 255, 255, arr)

例子:

FF = np.array([[0, 0],
              [1, 0],
              [0, 1],
              [1, 1]])
np.where(FF == 1, '+', '-')
Out[]: 
array([['-', '-'],
       ['+', '-'],
       ['-', '+'],
       ['+', '+']], dtype='<U1')

np.where是一个很好的解决方案,它不会改变涉及到的数组,并且它也直接兼容pandas系列对象。真的帮了我很多。 - AndrewJaeyoung

4
假设您有一个包含从0到20的值的 numpy 数组,您希望用0替换大于10的数字。
import numpy as np

my_arr = np.arange(0,21) # creates an array
my_arr[my_arr > 10] = 0 # modifies the value

请注意,这将修改原始数组以避免覆盖原始数组,请尝试使用arr.copy()创建原始数组的新分离副本,然后修改该副本。
import numpy as np

my_arr = np.arange(0,21)
my_arr_copy = my_arr.copy() # creates copy of the orignal array

my_arr_copy[my_arr_copy > 10] = 0 

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