Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Summation of only consecutive values in a python array

I am new on python (and even programing!), so I will try to be as clear as I can to explain my question. For you guys it could be easy, but I have not found a satisfactory result on this yet.

Here is the problem:

I have an array with both negative and positive values, say:

x = numpy.array([1, 4, 2, 3, -1, -6, -6, 5, 6, 7, 3, 1, -5, 4, 9, -5, -2, -1, -4])

I would like to sum ONLY the negative values that are continuous, i.e. only sum(-1, -6, -6), sum(-5, -2, -1, -4) and so on. I have tried using numpy.where, as well as numpy.split based on the condition.

For example:

 for i in range(len(x)):
     if x[i] < 0.:
         y[i] = sum(x[i])

However, as you can expect, I just got the summation of all negative values in the array instead. In this case sum(-1, -6, -6, -5, -5, -2, -1, -4) Could guys share with me an aesthetic and efficient way to solve this problem? I will appreciate any response on this.

Thank you very much

like image 844
hurrdrought Avatar asked Sep 24 '15 20:09

hurrdrought


2 Answers

You can use itertools module, here with using groupby you can grouping your items based on those sign then check if it meet the condition in key function so it is contains negative numbers then yield the sum else yield it and at last you can use chain.from_iterable function to chain the result :

>>> from itertools import groupby,tee,chain
>>> def summ_neg(li):
...     for k,g in groupby(li,key=lambda i:i<0) :
...           if k:
...              yield [sum(g)]
...           yield g
... 
>>> list(chain.from_iterable(summ_neg(x)))
[1, 4, 2, 3, -13, 5, 6, 7, 3, 1, -5, 4, 9, -12]

Or as a more pythonic way use a list comprehension :

 list(chain.from_iterable([[sum(g)] if k else list(g) for k,g in groupby(x,key=lambda i:i<0)]))
[1, 4, 2, 3, -13, 5, 6, 7, 3, 1, -5, 4, 9, -12]
like image 157
Mazdak Avatar answered Oct 12 '22 08:10

Mazdak


Here's a vectorized NumPythonic solution -

# Mask of negative numbers
mask = x<0

# Differentiation between Consecutive mask elements. We would look for 
# 1s and -1s to detect rising and falling edges in the mask corresponding 
# to the islands of negative numbers.
diffs = np.diff(mask.astype(int))

# Mask with 1s at start of negative islands
start_mask = np.append(True,diffs==1) 

# Mask of negative numbers with islands of one isolated negative numbers removed
mask1 = mask & ~(start_mask & np.append(diffs==-1,True))

# ID array for IDing islands of negative numbers
id = (start_mask & mask1).cumsum()

# Finally use bincount to sum elements within their own IDs
out = np.bincount(id[mask1]-1,x[mask1])

You can also use np.convolve to get mask1, like so -

mask1 = np.convolve(mask.astype(int),np.ones(3),'same')>1

You can also get the count of negative numbers in each "island" with a little tweak to existing code -

counts = np.bincount(id[mask1]-1)

Sample run -

In [395]: x
Out[395]: 
array([ 1,  4,  2,  3, -1, -6, -6,  5,  6,  7,  3,  1, -5,  4,  9, -5, -2,
       -1, -4])

In [396]: out
Out[396]: array([-13., -12.])

In [397]: counts
Out[397]: array([3, 4])
like image 39
Divakar Avatar answered Oct 12 '22 07:10

Divakar