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:array set of 3 local minima => array:array local minima, i = 9 set of 2 local minima => array:array set of 2 local minima => array:array
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<a: print("local minima, i = 0") #if the element is a candidate to be part of a set of local minima elif a==a: 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.greater_equal instead of
np.greater as the comparator:
import numpy as np from scipy.signal import argrelextrema minima = argrelextrema(input, np.less_equal)
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)]