I would like to find the local minima and maxima simultanously in an array. I attempted to use Scipy's argrelextrema, but I don't find any documentation on the required comparator function. I attempted to write my own with no success. How could I do this?
I'd like to do this, but only calling argrelextrema
once instead of iterating over the data twice with argrelmin
and argrelmax
, as my data is pretty long, and I have to do this multiple times with it:
import numpy as np
import scipy.signal as sg
data = [[xes], [ys]] #a long 2d array
max_places = np.array(sg.argrelmax(data[1]))[0]
min_places = np.array(sg.argrelmin(data[1]))[0]
extrema_places = np.contantenate((max_places, min_places))
Your goal doesn't make sense.
Using the example in the docs:
In [199]: from scipy.signal import argrelextrema
In [200]: x = np.array([2, 1, 2, 3, 2, 0, 1, 0])
In [201]: argrelextrema(x, np.greater)
Out[201]: (array([3, 6], dtype=int32),)
this gets the local maxima. Change to less
to get the local minima.
In [202]: argrelextrema(x, np.less)
Out[202]: (array([1, 5], dtype=int32),)
A lambda
function can work instead of np.greater
:
In [204]: argrelextrema(x, lambda a,b: a>b)
Out[204]: (array([3, 6], dtype=int32),)
We could combine > and < with a or, but that just fetches all points (except the ends).
In [205]: argrelextrema(x, lambda a,b: (a>b) | (a<b))
Out[205]: (array([1, 2, 3, 4, 5, 6], dtype=int32),)
Change a value of x
so two adjacent values are the same (and thus the lambda test is false)
In [211]: x
Out[211]: array([2, 1, 2, 3, 2, 2, 1, 0])
In [212]: argrelextrema(x, lambda a,b: (a>b) | (a<b))
Out[212]: (array([1, 2, 3, 6], dtype=int32),)
But lets say you could give it desired function, what kind of result would you want? The maxima and minima strung together as one array, or as two, or as a 2d array?
np.array([1,3,5,6])
(np.array([3,6]), np.array([1,5]))
Just realized that the result is already a tuple, like where
, one array per dimension. In fact that's what it returns:
results = _boolrelextrema(data, comparator,
axis, order, mode)
return np.where(results)
The meat of the task is in that _boolrelextrama
function; which presumably returns an array like data
, but with True/False based on the comparator
.
Are you looking for a turning point, in either direction? You might want to play with order
.
In [217]: argrelextrema(x, lambda a,b: (a>b) | (a<b), order=2)
Out[217]: (array([1, 3, 6], dtype=int32),)
With that modified x
this order 2 version does return both extrema. But I doubt if it works if the runs between turns are longer.
See full code in sg._peak_finding.py
(see also the [source]
link in the documentation). _boolrelextrema
uses the comparator
to test data
against several shifted versions of data
. mode
is used by np.take
.
argrelmax
is doing:
In [262]: x = np.array([2, 1, 2, 3, 2, 0, 1, 0]) # data
In [263]: locs = np.arange(0,len(x)) # all indices
In [264]: plus=np.take(x,locs+1,mode='clip') # shift up, with 'clip'
In [265]: minus=np.take(x,locs-1,mode='clip') # shift down
In [266]: plus
Out[266]: array([1, 2, 3, 2, 0, 1, 0, 0])
In [267]: minus
Out[267]: array([2, 2, 1, 2, 3, 2, 0, 1])
In [268]: (x>plus) & (x>minus) # compare x with shifted arrays
Out[268]: array([False, False, False, True, False, False, True, False], dtype=bool)
In [269]: np.where(_)
Out[269]: (array([3, 6], dtype=int32),)
argrelmin
generates the same plus
and minus
, but then does:
In [270]: np.where((x<plus) & (x<minus))
Out[270]: (array([1, 5], dtype=int32),)
Doing the 'or' step before where
interweaves the indices:
In [271]: np.where(((x<plus) & (x<minus)) | (x>plus) & (x>minus))
Out[271]: (array([1, 3, 5, 6], dtype=int32),)
A combined min_max
could save on the 2 take
operations, but otherwise it has do the same 2 comparator steps.
Note that all these operations are in compiled code; there's no Python level iteration on the data array.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With