我想拥有一个可以检测局部最大值/最小值在数组中的位置的函数(即使存在一组局部最大值/最小值)。示例:
给出数组
test03 = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1])
我希望输出如下:
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]
从示例中可以看到,不仅检测到奇异值,还检测到局部最大值/最小值集。
我在this question中知道有很多好的答案和想法,但是都没有完成所描述的工作:其中一些只是忽略了数组的极点,而全部都忽略了局部极小值/最大值。
在问问题之前,我自己编写了一个函数,该函数的功能与我上面所述的完全相同(该函数位于问题的结尾:local_min(a)
。经过我的测试,它可以正常工作)。< / p>
问题:但是,我也确信这不是使用Python的最佳方法。我可以使用内置的函数,API,库等吗?还有其他功能建议吗?单行指令?完整的矢量解决方案?
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 singular 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
注意:我试图通过添加一些注释来丰富代码,以了解我想做什么。我知道我建议的功能是 不干净,只是打印可以存储和返回的结果 在末尾。它被写为一个例子。我建议的算法应该是O(n)。
更新:
有人建议导入from scipy.signal import argrelextrema
并使用如下功能:
def local_min_scipy(a):
minima = argrelextrema(a, np.less_equal)[0]
return minima
def local_max_scipy(a):
minima = argrelextrema(a, np.greater_equal)[0]
return minima
拥有这样的东西是我真正想要的。但是,当局部最小值/最大值集具有两个以上的值时,它将无法正常工作。例如:
test03 = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1])
print(local_max_scipy(test03))
输出为:
[ 0 2 4 8 10 13 14 16]
在test03[4]
中,我当然有一个最小值,而不是最大值。如何解决此问题? (我不知道这是另一个问题还是在哪里问这个问题。)
答案 0 :(得分:8)
完整的矢量解决方案:
test03 = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1]) # Size 17
extended = np.empty(len(test03)+2) # Rooms to manage edges, size 19
extended[1:-1] = test03
extended[0] = extended[-1] = np.inf
flag_left = extended[:-1] <= extended[1:] # Less than successor, size 18
flag_right = extended[1:] <= extended[:-1] # Less than predecessor, size 18
flagmini = flag_left[1:] & flag_right[:-1] # Local minimum, size 17
mini = np.where(flagmini)[0] # Indices of minimums
spl = np.where(np.diff(mini)>1)[0]+1 # Places to split
result = np.split(mini, spl)
result
:
[0, 1] [3, 4, 5] [9] [11, 12] [15, 16]
编辑
不幸的是,由于它们被视为平坦的局部极小值,因此一旦它们的大小至少为3,它也会检测到最大值。这种方式的numpy补丁会很丑。
为解决此问题,我提出了另外两个解决方案,分别是numpy和numba。
使用np.diff
的numpy:
import numpy as np
test03=np.array([12,13,12,4,4,4,5,6,7,2,6,5,5,7,7,17,17])
extended=np.full(len(test03)+2,np.inf)
extended[1:-1]=test03
slope = np.sign(np.diff(extended)) # 1 if ascending,0 if flat, -1 if descending
not_flat,= slope.nonzero() # Indices where data is not flat.
local_min_inds, = np.where(np.diff(slope[not_flat])==2)
#local_min_inds contains indices in not_flat of beginning of local mins.
#Indices of End of local mins are shift by +1:
start = not_flat[local_min_inds]
stop = not_flat[local_min_inds+1]-1
print(*zip(start,stop))
#(0, 1) (3, 5) (9, 9) (11, 12) (15, 16)
与numba加速兼容的直接解决方案:
#@numba.njit
def localmins(a):
begin= np.empty(a.size//2+1,np.int32)
end = np.empty(a.size//2+1,np.int32)
i=k=0
begin[k]=0
search_end=True
while i<a.size-1:
if a[i]>a[i+1]:
begin[k]=i+1
search_end=True
if search_end and a[i]<a[i+1]:
end[k]=i
k+=1
search_end=False
i+=1
if search_end and i>0 : # Final plate if exists
end[k]=i
k+=1
return begin[:k],end[:k]
print(*zip(*localmins(test03)))
#(0, 1) (3, 5) (9, 9) (11, 12) (15, 16)
答案 1 :(得分:5)
我认为scipy.signal
中的另一个功能会很有趣。
from scipy.signal import find_peaks
test03 = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1])
find_peaks(test03)
Out[]: (array([ 2, 8, 10, 13], dtype=int64), {})
find_peaks
有很多选项,可能非常有用,尤其是对于嘈杂的信号。
该功能非常强大且用途广泛。您可以为峰的最小最小宽度,高度,彼此之间的距离等设置多个参数。例如:
test04 = np.array([1,1,5,5,5,5,5,5,5,5,1,1,1,1,1,5,5,5,1,5,1,5,1])
find_peaks(test04, width=1)
Out[]:
(array([ 5, 16, 19, 21], dtype=int64),
{'prominences': array([4., 4., 4., 4.]),
'left_bases': array([ 1, 14, 18, 20], dtype=int64),
'right_bases': array([10, 18, 20, 22], dtype=int64),
'widths': array([8., 3., 1., 1.]),
'width_heights': array([3., 3., 3., 3.]),
'left_ips': array([ 1.5, 14.5, 18.5, 20.5]),
'right_ips': array([ 9.5, 17.5, 19.5, 21.5])})
有关更多示例,请参见documentation。
答案 2 :(得分:1)
有多种方法可以解决此问题。这里列出了一种方法。 您可以创建一个自定义函数,并在查找mimima时使用最大值处理边缘情况。
import numpy as np
a = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1])
def local_min(a):
temp_list = list(a)
maxval = max(a) #use max while finding minima
temp_list = temp_list + [maxval] #handles last value edge case.
prev = maxval #prev stores last value seen
loc = 0 #used to store starting index of minima
count = 0 #use to count repeated values
#match_start = False
matches = []
for i in range(0, len(temp_list)): #need to check all values including the padded value
if prev == temp_list[i]:
if count > 0: #only increment for minima candidates
count += 1
elif prev > temp_list[i]:
count = 1
loc = i
# match_start = True
else: #prev < temp_list[i]
if count > 0:
matches.append((loc, count))
count = 0
loc = i
prev = temp_list[i]
return matches
result = local_min(a)
for match in result:
print ("{} minima found starting at location {} and ending at location {}".format(
match[1],
match[0],
match[0] + match[1] -1))
让我知道这是否对您有用。这个想法很简单,您想要遍历列表一次,并在看到它们时继续存储最小值。通过在任一端都填充最大值来处理边缘。 (或通过填充末尾,并使用最大值进行初始比较)
答案 3 :(得分:1)
这是基于将数组重新分配到可迭代的Windows中的答案:
import numpy as np
from numpy.lib.stride_tricks import as_strided
def windowstride(a, window):
return as_strided(a, shape=(a.size - window + 1, window), strides=2*a.strides)
def local_min(a, maxwindow=None, doends=True):
if doends: a = np.pad(a.astype(float), 1, 'constant', constant_values=np.inf)
if maxwindow is None: maxwindow = a.size - 1
mins = []
for i in range(3, maxwindow + 1):
for j,w in enumerate(windowstride(a, i)):
if (w[0] > w[1]) and (w[-2] < w[-1]):
if (w[1:-1]==w[1]).all():
mins.append((j, j + i - 2))
mins.sort()
return mins
对其进行测试:
test03=np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1])
local_min(test03)
输出:
[(0, 2), (3, 6), (9, 10), (11, 13), (15, 17)]
这不是最有效的算法,但至少很短。我非常确定它是O(n^2)
,因为大约有1/2*(n^2 + n)
个窗口可以迭代。这只是部分矢量化,因此可能有一种改进方法。
为澄清起见,输出是包含局部最小值游程的切片的索引。他们故意在运行结束时走过这一事实(有人试图在编辑中“修复”该错误)。您可以使用输出迭代输入数组中的最小值切片,如下所示:
for s in local_mins(test03):
print(test03[slice(*s)])
输出:
[2 2]
[4 4 4]
[2]
[5 5]
[1 1]
答案 4 :(得分:1)
一个纯粹的numpy解决方案(修订版):
import numpy as np
y = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1])
x = np.r_[y[0]+1, y, y[-1]+1] # pad edges, gives possibility for minima
ups, = np.where(x[:-1] < x[1:])
downs, = np.where(x[:-1] > x[1:])
minend = ups[np.unique(np.searchsorted(ups, downs))]
minbeg = downs[::-1][np.unique(np.searchsorted(-downs[::-1], -ups[::-1]))][::-1]
minlen = minend - minbeg
for line in zip(minlen, minbeg, minend-1): print("set of %d minima %d - %d" % line)
这给
set of 2 minima 0 - 1
set of 3 minima 3 - 5
set of 1 minima 9 - 9
set of 2 minima 11 - 12
set of 2 minima 15 - 16
np.searchsorted(ups, downs)
在每次下跌之后找到第一次上涨。这是最小值的“真实”结尾。
对于最小值的开始,我们执行类似的操作,但是现在是相反的顺序。
它适用于示例,但尚未完全测试。但我会说是一个很好的起点。
答案 5 :(得分:0)
一种非常简单,快速的方法是使用秩过滤器作为腐蚀或扩张。 为了找到最小值,请首先腐蚀您的阵列。然后,找到它与原始数组相等的位置。
import numpy as np
from scipy.ndimage import rank_filter, grey_erosion, grey_dilation
test03 = np.array([2,2,10,4,4,4,5,6,7,2,6,5,5,7,7,1,1])
test_erode = rank_filter(test03, 0, size=3)
print(test_erode == test03)
输出:
[ True True False True True True False False False True False True
True False False True True]
可以使用相同的过程来找到最大值。随着最大值,您正在执行扩张。只需将等级更改为-1
:
test_dilate = rank_filter(test03, -1, size=3)
print(test_dilate == test03)
输出:
[ True False True False True False False False True False True False
False True True False True]
我意识到rank_filter
可能比其他操作要慢。您还可以使用SciPy的grey_erosion
来查找最小值,或使用grey_dilation
来查找最大值:
test_erode = grey_erosion(test03, size=3)
test_min = test_erode == test03
test_dil = grey_dilation(test03, size=3)
test_max = test_dil == test03
编辑2
要转换为一组(信用给用户B. M。)...
mini = np.where(test_min)[0] # Indices of minimums
spl = np.where(np.diff(mini)>1)[0]+1 # Places to split
result = np.split(mini, spl)
print(result)
输出:
[array([0, 1]), array([3, 4, 5]), array([9]), array([11, 12]), array([15, 16])]
答案 6 :(得分:0)
只要没有多个连续的相等元素,就可以使用argrelmax,因此首先需要对数组进行长度编码,然后使用argrelmax(或argrelmin):
import numpy as np
from scipy.signal import argrelmax
from itertools import groupby
def local_max_scipy(a):
start = 0
result = [[a[0] - 1, 0, 0]] # this is to guarantee the left edge is included
for k, g in groupby(a):
length = sum(1 for _ in g)
result.append([k, start, length])
start += length
result.append([a[-1] - 1, 0, 0]) # this is to guarantee the right edge is included
arr = np.array(result)
maxima, = argrelmax(arr[:, 0])
return arr[maxima]
test03 = np.array([2, 2, 10, 4, 4, 4, 5, 6, 7, 2, 6, 5, 5, 7, 7, 1, 1])
output = local_max_scipy(test03)
for val, start, length in output:
print(f'set of {length} maxima start:{start} end:{start + length}')
输出
set of 1 maxima start:2 end:3
set of 1 maxima start:8 end:9
set of 1 maxima start:10 end:11
set of 2 maxima start:13 end:15