Finding singulars/sets of local maxima/minima in 1D-numpy array (once again)

  • A+

What I would like to have is a function that can detect where are the local maxima/minima in an array (even if there is a set of local maxima/minima). Example:

Given the array


I would like to have an output like:

set of 2 local minima => array[0]:array[1] set of 3 local minima => array[3]:array[5] local minima, i = 9 set of 2 local minima => array[11]:array[12] set of 2 local minima => array[15]:array[16] 

As you can see from the example, not only the singular values are detected but, also, sets of local maxima/minima.

I know in this question there are a lot of good answers and idea but none of them do the job described: some of them simply ignore the extreme points of the array and all ignore the sets of local minima/maxima.

Before asking the question, I wrote a function by myself (a newbie implementation, I am sorry) that does exactly what I described above (the function is at the end of this question).

I am also sure that is NOT the best way to work with python. Are there builtin function, APIs, libraries etc. that I can use? Any other function suggestion? A one-line instruction?

Any suggestion or code improvement is appreciated. Thank you all

def local_min(a):     candidate_min=0     for i in range(len(a)):         # controlling the first left element         if i==0 and len(a)>=1:             #if the first element is a singula local minima             if a[0]<a[1]:                 print("local minima, i = 0")             #if the element is a candidate to be part of a set of local minima             elif a[0]==a[1]:                 candidate_min=1         # controlling the last right element         if i == (len(a)-1) and len(a)>=1:             if candidate_min > 0:                 if a[len(a)-1]==a[len(a)-2]:                     print("set of " + str(candidate_min+1)+ " local minima => array["+str(i-candidate_min)+"]:array["+str(i)+"]")             if a[len(a)-1]<a[len(a)-2]:                 print("local minima, i = " + str(len(a)-1))         # controlling the other values in the middle of the array         if i>0 and i<len(a)-1 and len(a)>2:             #if a singular local minima             if (a[i]<a[i-1] and a[i]<a[i+1]):                 print("local minima, i = " + str(i))                 # print(str(a[i-1])+" > " + str(a[i]) + " < "+str(a[i+1])) #debug             #if it was found a set of candidate local minima             if candidate_min >0:                 #the candidate set IS a set of local minima                 if a[i] < a[i+1]:                     print("set of " + str(candidate_min+1)+ " local minima => array["+str(i-candidate_min)+"]:array["+str(i)+"]")                     candidate_min = 0                 #the candidate set IS NOT a set of local minima                 elif a[i] > a[i+1]:                     candidate_min = 0                 #the set of local minima is growing                 elif a[i] == a[i+1]:                     candidate_min = candidate_min + 1                 #it never should arrive in the last else                 else:                     print("Something strange happen")                     return -1             #if there is a set of candidate local minima (first value found)             if (a[i]<a[i-1] and a[i]==a[i+1]):                 candidate_min = candidate_min + 1 

Note: I tried to enrich the code with some comment to understand what I would like to do. I know that the function that I propose is not clean and just prints the results that can be stored and returned at the end. It was written to give an example. The algorithm I propose should be O(n).


You can find the extrema of an array easily using scipy.signal.argrelextrema, just like in this answer to the question you linked to. The only difference is that, to include non-strict minima and maxima in the results, you need to use np.less_equal and np.greater_equal instead of np.less and np.greater as the comparator:

import numpy as np from scipy.signal import argrelextrema  minima = argrelextrema(input, np.less_equal)[0] 

Applying this to your example input = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1]) yields the result:

minima = array([ 0,  1,  3,  4,  5,  9, 11, 12, 15, 16]) 

Of course, this is not exactly the output you're looking for, since the runs of consecutive extrema have not been grouped together, but if it's sufficient for your needs, this may be the simplest solution.

If you do need to have the runs of consecutive non-strict extrema grouped together, one way to do that is to first locate the gaps between the runs, and then use those to extract the left and right ends of the runs (which you can then optionally zip together, if you like):

gaps = np.diff(minima) > 1 gaps = np.concatenate([[True], gaps, [True]])  left_minima = minima[gaps[:-1]] right_minima = minima[gaps[1:]]  grouped_minima = zip(left_minima, right_minima) 

which, for your example input, produces:

grouped_minima = [(0, 1), (3, 5), (9, 9), (11, 12), (15, 16)] 


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: