3

I need to find the index of the first occurrence of three consecutive negative numbers. In the normal Python way I would do it like this:

a = [1,-1,1,-1,1,-1,1,-1,-1,-1,1,-1,1]
b=0
for i,v in enumerate(a):
    if v<0:
        b+=1
    else:
        b=0
    if b==3:
        break
indx = i-2

Anyone has an idea how to do it in a smarter NumPy way?

Ohm
  • 2,312
  • 4
  • 36
  • 75
  • You could try to take a look at this link. It seems like it would be able to help you. [Searching a sequence in a numpy array](https://stackoverflow.com/questions/36522220/searching-a-sequence-in-a-numpy-array?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa) – TMK Jun 14 '18 at 09:12
  • *index of the first occurrence of **three consecutive negative numbers*** - so you expect it to be `7`? or `[7,8,9]` ? – RomanPerekhrest Jun 14 '18 at 09:48
  • Did either of the posted solutions work for you? – Divakar Jun 15 '18 at 06:27
  • Yes, the first_consecutive_negative_island is doing exactly what I had to do..thanks – Ohm Jun 15 '18 at 12:51
  • @RomanPerekhrest I expect it to be `7` – Ohm Jun 15 '18 at 12:51

3 Answers3

3

Here's a vectorized solution with help from convolution -

def first_consecutive_negative_island(a, N=3):
    mask = np.convolve(np.less(a,0),np.ones(N,dtype=int))>=N
    if mask.any():
        return mask.argmax() - N + 1
    else:
        return None

Sample run -

In [168]: a = [1,-1,1,-1,1,-1,1,-1,-1,-1,1,-1,1]

In [169]: first_consecutive_negative_island(a, N=3)
Out[169]: 7

Works irrespective of where the group exists -

In [175]: a
Out[175]: [-1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1]

In [176]: first_consecutive_negative_island(a, N=3)
Out[176]: 0

With no negative numbers, it gracefully returns None -

In [183]: a
Out[183]: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

In [184]: first_consecutive_negative_island(a, N=3)

For exactly three consecutive negative numbers search, we can use slicing, like so -

def first_consecutive_negative_island_v2(a):
    m =  np.less(a,0)
    mask = m[:-2] & m[1:-1] & m[2:]
    if mask.any():
        return mask.argmax()
    else:
        return None

Timings -

In [270]: a = np.random.randint(-1,2,(1000000)).tolist()

In [271]: %timeit first_consecutive_negative_island(a, N=3)
10 loops, best of 3: 44.5 ms per loop

In [272]: %timeit first_consecutive_negative_island_v2(a)
10 loops, best of 3: 38.7 ms per loop
Divakar
  • 218,885
  • 19
  • 262
  • 358
0
import numpy as np
a = np.array([1,-1,1,-1,-1,-1,1,-1,-1,-1,1,-1,1])
item_index = np.where(a < 0)
for a,b in zip( item_index[0],item_index[0][2:]):
    if b-a == 2: 
        break
index_ = a
ken
  • 35
  • 1
  • 7
  • This is not a `numpy`-style answer. It uses `zip` AND a for loop. This is neither pythonic nor efficient numpy style. – JE_Muc Jun 14 '18 at 09:44
0

There is no looping needed at all. Also no zipping. Just do the following:

a = np.array([1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, 1])
idx = np.where(a < 0)[0][:3]

np.where returns a tuple with the indices of the chosen condition. [0] indexes this tuple's first dimension (for a 1D-input array this is the only dimension) and [:3] slices these indices to only use the first three indices.

If the occurrence of the first three consecutive negative numbers is needed, you can do the following:

idx = np.where(np.diff(np.where(a < 0), n=2) == 0)[1][0] + 2

This will give you the index of the first negative number of three consecutive negative numbers. If you want the indices of all three first negative consecutive numbers, do this:

idx_start = np.where(np.diff(np.where(a < 0), n=2) == 0)[1][0] + 2
idx = np.arange(idx_start, idx_start + 3)

This will work as long as the first two numbers in the array are not part of three consecutive negative numbers.

JE_Muc
  • 5,403
  • 2
  • 26
  • 41