Number of islands of negative and positive numbers in a NumPy array
I have an array containing chunks of negative and chunks of positive elements. A much simplified example of this would be an array a
like:array([-3, -2, -1, 1, 2, 3, 4, 5, 6, -5, -4])
(a<0).sum()
and (a>0).sum()
give me the total of negative and positive elements, but how can I count them in order? By this I mean I want to know that my array contains the first 3 negative elements, 6 positive and 2 negative ones.
It sounds like a topic that was addressed somewhere and there might be a duplicate, but I can't seem to find it.
The method is to numpy.roll(a,1)
loop through the entire array and count the number of elements of a given sign appearing in, for example, the first element of the array when it rolls, but it doesn't look much numpyic (or pythonic) and is not very efficient to me.
source to share
Here's one vector approach -
def pos_neg_counts(a):
mask = a>0
idx = np.flatnonzero(mask[1:] != mask[:-1])
count = np.concatenate(( [idx[0]+1], idx[1:] - idx[:-1], [a.size-1-idx[-1]] ))
if a[0]<0:
return count[1::2], count[::2] # pos, neg counts
else:
return count[::2], count[1::2] # pos, neg counts
Run examples -
In [155]: a
Out[155]: array([-3, -2, -1, 1, 2, 3, 4, 5, 6, -5, -4])
In [156]: pos_neg_counts(a)
Out[156]: (array([6]), array([3, 2]))
In [157]: a[0] = 3
In [158]: a
Out[158]: array([ 3, -2, -1, 1, 2, 3, 4, 5, 6, -5, -4])
In [159]: pos_neg_counts(a)
Out[159]: (array([1, 6]), array([2, 2]))
In [160]: a[-1] = 7
In [161]: a
Out[161]: array([ 3, -2, -1, 1, 2, 3, 4, 5, 6, -5, 7])
In [162]: pos_neg_counts(a)
Out[162]: (array([1, 6, 1]), array([2, 1]))
Runtime test
Another approach is
# @Franz soln
def split_app(my_array):
negative_index = my_array<0
splits = np.split(negative_index, np.where(np.diff(negative_index))[0]+1)
len_list = [len(i) for i in splits]
return len_list
The timeline for a larger dataset is
In [20]: # Setup input array
...: reps = np.random.randint(3,10,(100000))
...: signs = np.ones(len(reps),dtype=int)
...: signs[::2] = -1
...: a = np.repeat(signs, reps)*np.random.randint(1,9,reps.sum())
...:
In [21]: %timeit split_app(a)
10 loops, best of 3: 90.4 ms per loop
In [22]: %timeit pos_neg_counts(a)
100 loops, best of 3: 2.21 ms per loop
source to share
Just use
my_array = np.array([-3, -2, -1, 1, 2, 3, 4, 5, 6, -5, -4])
negative_index = my_array<0
and you will get the indices of negative values. After that, you can split this array:
splits = np.split(negative_index, np.where(np.diff(negative_index))[0]+1)
and, in addition, calculate the size of the internal arrays:
len_list = [len(i) for i in splits]
print(len_list)
And you get what you are looking for:
Out[1]: [3, 6, 2]
You just need to specify what your first element is. Negative by definition in my code.
So just do:
my_array = np.array([-3, -2, -1, 1, 2, 3, 4, 5, 6, -5, -4]) negative_index = my_array<0 splits = np.split(negative_index, np.where(np.diff(negative_index))[0]+1) len_list = [len(i) for i in splits] print(len_list)
source to share
My (rather simple and probably ineffective) solution:
import numpy as np
arr = np.array([-3, -2, -1, 1, 2, 3, 4, 5, 6, -5, -4])
sgn = np.sign(arr[0])
res = []
cntr = 1 # counting the first one
for i in range(1, len(arr)):
if np.sign(arr[i]) != sgn:
res.append(cntr)
cntr = 0
sgn *= -1
cntr += 1
res.append(cntr)
print res
source to share