如何检测numpy数组中元素的符号变化

36
我有一个包含正数和负数的numpy数组。
a = array([1,1,-1,-2,-3,4,5])

我想创建另一个数组,其中包含在发生符号更改的每个索引处的值(例如,如果当前元素为正数且上一个元素为负数,反之亦然)。

对于上面的数组,我期望得到以下结果

array([0,0,1,0,0,1,0])

或者,将数组中符号变化发生的位置的列表或布尔值列表作为0和1的替代,都可以。

8个回答

37

类似以下内容

a = array([1,1,-1,-2,-3,4,5])
asign = np.sign(a)
signchange = ((np.roll(asign, 1) - asign) != 0).astype(int)
print signchange
array([0, 0, 1, 0, 0, 1, 0])

现在,numpy.roll执行循环移位,因此如果最后一个元素与第一个元素的符号不同,则signchange数组中的第一个元素将为1。如果不希望这样,当然可以进行简单的操作

signchange[0] = 0

同时,np.sign函数认为0具有自己的符号,不同于正数或负数。例如,在输入数组[-1, 0, 1]时,"signchange"数组的值将为[0, 1, 1],即使零线只被“穿过”一次。如果不希望发生这种情况,可以插入以下代码:

sz = asign == 0
while sz.any():
    asign[sz] = np.roll(asign, 1)[sz]
    sz = asign == 0

在第一个示例中的第2行和第3行之间。


3
请注意,如果最后一个元素与第一个元素符号不同,则第一个位置将显示符号更改。它还将认为0与正数或负数的符号不同。因此,[-1, 0, 1] 将给出 signchange = [1, 1, 1]。这可能是期望的行为,但我想指出这一点。 - tgray
@tgray 是的,我修改了我的答案,指出如何修复这些问题(如果需要)。 - janneb

26
(numpy.diff(numpy.sign(a)) != 0)*1

伟大的解决方案...现在,如何优雅地对每行进行符号变化总和? - pythlang
指定 diffsum 的轴。 - Christian Alis
*1是什么作用? - undefined
True转换为1,将False转换为0 - undefined

14

确定符号变化位置的三种方法

import numpy as np
a = np.array([1,1,-1,-2,-3,4,5])

方法1:将数组中相邻的数相乘并找出负数

idx1 = np.where(a[:-1] * a[1:] < 0 )[0] +1
idx1
Out[2]: array([2, 5], dtype=int64)

%timeit np.where(a[:-1] * a[1:] < 0 )[0] + 1
4.31 µs ± 15.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

方法2(最快):相邻符号不相等的情况

idx2 = np.where(np.sign(a[:-1]) != np.sign(a[1:]))[0] + 1
idx2
Out[4]: array([2, 5], dtype=int64)

%timeit np.where(np.sign(a[:-1]) != np.sign(a[1:]))[0] + 1
3.94 µs ± 20.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

方法三:由ianalis提出。这可能是最优雅的方法,但速度稍慢。

idx3 = np.where(np.diff(np.sign(a)) != 0)[0] + 1
idx3
Out[6]: array([2, 5], dtype=int64)

%timeit np.where(np.diff(np.sign(a)) != 0)[0] + 1
9.7 µs ± 36.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

编辑:

对于大型数组,方法1是最佳选择。

在此输入图片描述


1

关于将“严格”符号从正变为负、从负变为正(不包括零)的另一个想法:

a = np.array([0.4, 0.5, -0.2, -0.6, 5, 0, 0, 5, 0,-2])

# Get associated index
ind =np.arange(len(a))

# remove zero from array but keep original index
a2 =a[a!=0.]
ind2  =ind[a!=0.]

# Detect sign changes in reduced array
idx=np.where(np.diff(np.sign(a2)) != 0)[0] + 1

# Get sign changes index for original array
ind2[idx]

array([2, 4, 9])

1

怎么样?

[0 if x == 0 else 1 if numpy.sign(a[x-1]) != numpy.sign(y) else 0 for x, y in enumerate(a)]

numpy.sign会将0赋予它自己的符号,因此0将成为除了其他0之外的任何数的符号变化,这可能是您想要的。


1
以上的答案使用了列表推导和一些numpy魔法来获得您想要的结果。这里是一个非常直接,虽然有点复杂的方法来做同样的事情:
import numpy as np

arr = np.array([1,1,-1,-2,-3,4,5])

result = []
for i, v in enumerate(arr):
    if i == 0:
        change = False
    elif v < 0 and arr[i-1] > 0:
        change = True
    elif v > 0 and arr[i-1] < 0:
        change = True
    else:
        change = False

    result.append(change)

print result

它不会在 np.array([1,0,-1]) 中注册更改。 - Justin Peel

0

对于这个问题的直接解释,其中0不是它们自己的情况,使用greatersign可能更容易。以下是一个例子:

a = array([1, 1, -1, -2, -3, 0, 4, 0, 5, 6])

x = greater_equal(a, 0)
sign_change = x[:-1]-x[1:]

当使用 TF 输出时,可以表示不同数字之间的符号变化:

 1 F 1 T -1 F -2 F -3 T 0 F 4 F 0 F 5 F 6

当使用以下方式打印:

print `a[0]`+"".join([(" T" if sign_change[i] else " F")+" "+`a[i+1]` for i in range(len(sign_change))])

还要注意,这个数组比原来的短一个元素,这是有道理的,因为你要求改变符号。如果你想包括最后一个和第一个元素之间的变化,你可以使用roll,正如其他人建议的那样。


现在这段代码无法运行。它会报错:TypeError: numpy boolean subtract, the - operator, is deprecated, use the bitwise_xor, the ^ operator, or the logical_xor function instead. 请使用其他解决方案。 - shantanu pathak

0
如果您只对一种方向的变化感兴趣(从负值到正值(包括零)以下):
arr = np.array([1,1,-1,-2,-3,4,5])

np.where(np.diff((arr>=0)*1)==1)

返回符号改变前每个最后一个负值的索引


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