Python – counting sign changes

Question:

I have a list of numbers I am reading left to right. Anytime I encounter a sign change when reading the sequence I want to count it.

X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1] 
X = [-, +, +, -, +, -, +, +, -, -,-,+]

So, in this list there are 8 sign changes.

When Item [0] (in this case -3) is negative it is considered a sign change. Also, any 0 in the list is considered [-].

Any help would be greatly appreciated.

Asked By: Captain Cretaceous

||

Answers:

You can use itertools.groupby to count the groups of positive and non-positive numbers:

>>> x = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1] 

>>> import itertools
>>> len(list(itertools.groupby(x, lambda x: x > 0)))

Result:

8

In your question you state that you want:

  • to count the changes, not the groups
  • to count an extra change if the first element is not positive.

You can do this either by testing the first element directly and adjusting the result:

>>> len(list(itertools.groupby(x, lambda x: x > 0))) - (x[0] > 0)

or by prepending a positive number to the input before doing the grouping then subtracting 1 from the result:

>>> len(list(itertools.groupby(itertools.chain([1], x), lambda x: x > 0))) - 1

Watch out if your input list could by empty – the former solution will raise an exception.

Answered By: Mark Byers
X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]

last_sign = 1
sign_changes = 0

for x in X:
    if x == 0:
        sign = -1
    else:
        sign = x / abs(x)

    if sign == -last_sign:
        sign_changes = sign_changes + 1
        last_sign = sign

print sign_changes
Answered By: Christian Neverdal
numbers = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
# could be replaced by     signs = [x > 0 for x in numbers]
# but this methods gives us nice minus and plus signs
signs = map(lambda x: "+" if x > 0 else "-", numbers)

# zip(…) creates the pairs, each pair that has different signs
# adds one to "count"
count = sum(1 for x,y in zip(signs[:-1], signs[1:]) if x != y)

-> 7

For your additional requirement, that a negative number at the start of the list should be considered another change, just add a positive number to your list.

If you’re dealing with huge lists, consider using generators. (izip, tee, …)

Answered By: Georg Schölly

Here is a solution using fold, have fun figuring it out:

def lolwut((x,c), y):
    return (y, c+(x^y))

print reduce( lolwut ,(x > 0 for x in X), (True,0)) # 8
print reduce( lolwut ,(x > 0 for x in X), (False,0)) # 7
Answered By: Jochen Ritzel

If you haven’t been convinced to read the itertools documentation yet:

def pairs(iterable):
    'iter -> (iter0, iter1), (iter1, iter2), (iter3, iter4), ...'
    from itertools import izip, tee
    first, second = tee(iterable)
    second.next()
    return izip(first, second)

def sign_changes(l):
    result = 0
    if l and l[0]<=0: result += 1
    result += sum(1 for a,b in pairs(l) if b*a<=0 and (a!=0 or b!=0))
    return result
Answered By: Peter Milley

For integers, (a^b) < 0 if signs of a and b are different.

def countSignChanges(seq):
    # make sure 0's are treated as negative
    seq = [-1 if not x else x for x in seq]

    # zip with leading 1, so that opening negative value is 
    # treated as sign change
    return sum((a^b)<0 for a,b in zip([1]+seq, seq))


X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
print countSignChanges(X)

gives the desired answer, 8.

Answered By: PaulMcG

Here is a way to do it without loops… It should be much faster for large data 😉 (however, because of the numpy etc, will not be as efficient for small lists- and it will be much better for numpy array than lists for obvious reasons – you can even drop the conversion…)

x = np.array([-3,2,7,-4,1,-1,1,6,-1,0,-2,1])
positive= x>0
count = np.logical_xor(positive[1:],positive[:-1]).sum()
count += not(positive[0])
print count

returns 8

Xor returns true if the two booleans are different ( from + to – ) and is pretty fast. There is one problem with the code: if something goes to exactly 0 from the positive sign, it will count as crossing. This is exactly what you ask in your question though since you represented 0 as ‘-‘.

Answered By: ntg
Input = [-1, 2, 3, -4, 5, -6, 7, 8, -9, 10, -11, 12] 

for i in range(len(Input)):
    if(Input[i]<1):
        Input[i]=0
    else:
        Input[i]=1

print(Input) #[0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]

count=0
check=Input[0]
for i in range(len(Input)-1):

    if check != Input[i + 1]:
        count+=1
        check=Input[i+1]


print(count) #9
Answered By: Atul Sharma

K = np.sum(np.abs(np.diff(x>0)))

Gives the result

Answered By: Filipe Pinto
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.