3

I am trying to create some code that returns the positions and the values of the "peaks" (or local maxima) of a numeric array.

For example, the list arr = [0, 1, 2, 5, 1, 0] has a peak at position 3 with a value of 5 (since arr[3] equals 5).

The first and last elements of the array will not be considered as peaks (in the context of a mathematical function, you don't know what is after and before and therefore, you don't know if it is a peak or not).

def pick_peaks(arr):
    print(arr)
    posPeaks = {
        "pos": [],
        "peaks": [],
    }
    startFound = False
    n = 0
    while startFound == False:
        if arr[n] == arr[n+1]:
            n += 1
        else:
            startFound = True

    endFound = False
    m = len(arr) - 1
    while endFound == False:
        if arr[m] == arr[m-1]:
            m -= 1
        else:
            endFound = True

    for i in range(n+1, m):
        if arr[i] == arr[i-1]:
            None
        elif arr[i] >= arr[i-1] and arr[i] >= arr[i+1]:
            posPeaks["pos"].append(i)
            posPeaks["peaks"].append(arr[i])

    return posPeaks

My issue is with plateaus. [1, 2, 2, 2, 1] has a peak while [1, 2, 2, 2, 3] does not. When a plateau is a peak, the first position of the plateau is recorded.

Any help is appreciated.

Harry Day
  • 378
  • 4
  • 13
  • In your first example of a plateau, the value of the plateau is 2, but what is its position? – quamrana Dec 24 '18 at 14:19
  • @quamrana ah yes I forgot to add this, When a plateau is a peak, the first position of the plateau is used. – Harry Day Dec 24 '18 at 14:21
  • 1
    some points 1. the [] creates lists, not arrays. 2. use list comprehension, maps... instead of for/while loops as much as possible 3. look for enumerat and argmax functions – Foad S. Farimani Dec 24 '18 at 15:01

6 Answers6

2

I suggest you use groupby to group contiguous equal values, then for each group store the first position, example for [1, 2, 2, 2, 1] it creates the following list following list of tuples [(1, 0), (2, 1), (1, 4)], putting all together:

from itertools import groupby


def peaks(data):
    start = 0
    sequence = []
    for key, group in groupby(data):
        sequence.append((key, start))
        start += sum(1 for _ in group)

    for (b, bi), (m, mi), (a, ai) in zip(sequence, sequence[1:], sequence[2:]):
        if b < m and a < m:
            yield m, mi


print(list(peaks([0, 1, 2, 5, 1, 0])))
print(list(peaks([1, 2, 2, 2, 1])))
print(list(peaks([1, 2, 2, 2, 3])))

Output

[(5, 3)]
[(2, 1)]
[]
Dani Mesejo
  • 61,499
  • 6
  • 49
  • 76
2

I know I may be a little late for the party, but I'd like to share my solution using NumPy arrays:

def get_level_peaks(v):
    peaks = []

    i = 1
    while i < v.size-1:
        pos_left = i
        pos_right = i

        while v[pos_left] == v[i] and pos_left > 0:
            pos_left -= 1

        while v[pos_right] == v[i] and pos_right < v.size-1:
            pos_right += 1

        is_lower_peak = v[pos_left] > v[i] and v[i] < v[pos_right]
        is_upper_peak = v[pos_left] < v[i] and v[i] > v[pos_right]

        if is_upper_peak or is_lower_peak:
            peaks.append(i)

        i = pos_right

    peaks = np.array(peaks)

    """
    # uncomment this part of the code
    # to include first and last positions

    first_pos, last_pos = 0, v.size-1
    peaks = np.append([first_pos], peaks)
    peaks = np.append(peaks, [last_pos])
    """

    return peaks
v = np.array([7, 2, 0, 4, 4, 6, 6, 9, 5, 5])
p = get_peaks(v)
print(v)        # [7 2 0 4 4 6 6 9 5 5]
print(p)        # [0 2 7 9] (peak indexes)
print(v[p])     # [7 0 9 5] (peak elements)
v = np.array([8, 2, 1, 0, 1, 2, 2, 5, 9, 3])
p = get_peaks(v)
print(v)        # [8 2 1 0 1 2 2 5 9 3]
print(p)        # [0 3 8 9] (peak indexes)
print(v[p])     # [8 0 9 3] (peak elements)
v = np.array([9, 8, 8, 8, 0, 8, 9, 9, 9, 6])
p = get_peaks(v)
print(v)        # [9 8 8 8 0 8 9 9 9 6]
print(p)        # [0 4 6 9] (peak indexes)
print(v[p])     # [9 0 9 6] (peak elements)

In example 3, we have a flatten upper peak that goes from index 6 to index 8. In this case, the index will always indicate the leftmost position of the plateau. If you want to indicate the middle position or the rightmost position, just change this part of the code:

        ...

        if is_upper_peak or is_lower_peak:
            peaks.append(i)

        ...

to this:

        ...

        # middle position
        if is_upper_peak or is_lower_peak:
            peaks.append((pos_left + pos_right) // 2)

        ...
        ...

        # rightmost position
        if is_upper_peak or is_lower_peak:
            peaks.append(pos_right)

        ...
1

Here is a fairly simple generator function. Just loop and maintain the necessary state: i (last index of of "growth"), up (true if last value change was "growth")

def peaks(ar):
    i, up = 0, False
    for j in range(1, len(ar)):
        prev, val = ar[j-1], ar[j]
        if up and val < prev:
            yield prev, i
            up = False
        if val > prev:
            i, up = j, True

>>> list(peaks([0,1,2,5,1,0]))
[(5, 3)]
>>> list(peaks([0,1,2,5,1,2,0]))
[(5, 3), (2, 5)]
>>> list(peaks([0,1,2,5,1,2,0,3]))
[(5, 3), (2, 5)]
>>> list(peaks([1,2,2,2,1]))
[(2, 1)]
>>> list(peaks([1,2,2,2,3]))
[]
user2390182
  • 72,016
  • 6
  • 67
  • 89
1

This code takes a window number and gives the peak within that window size

l=[1,2,3,4,5,4,3,2,1,2,3,4,3,2,4,2,1,2]
n=int(input("The size of window on either side "))
for i in range(n,len(l)-n):
    if max(l[i-n:i]+l[i+1:i+n+1])<l[i]:
        print(l[i],' at index = ',i)
0

You can use the same algorithm with the plateaus as well if you can preprocess the data to remove the repeating numbers and keep only 1 unique number. Thus, you can convert the example [1, 2, 2, 2, 1] to [1, 2, 1] and apply the same algorithm.

Edit: The Code:

from itertools import groupby

def process_data(data):
    return [list(val for num in group) for val, group in groupby(data)]


def peaks(arr):
    #print(arr)
    posPeaks = {
    "pos": [],
    "peaks": [],
    }
    startFound = False
    n = 0
    while startFound == False:
        if arr[n][0] == arr[n+1][0]:
            n += 1
        else:
            startFound = True

    endFound = False
    m = len(arr) - 1
    while endFound == False:
        if arr[m][0] == arr[m-1][0]:
            m -= 1
        else:
            endFound = True

    for i in range(n+1, m):
        if arr[i][0] == arr[i-1][0]:
            None
        elif arr[i][0] >= arr[i-1][0] and arr[i][0] >= arr[i+1][0]:
            pos = sum([len(arr[idx]) for idx in range(i)])
            posPeaks["pos"].append(pos) #.append(i)
            posPeaks["peaks"].append(arr[i][0])
    return posPeaks



print(peaks(process_data([0, 1, 2, 5, 1, 0])))
print(peaks(process_data([1, 2, 2, 2, 1])))
print(peaks(process_data([1, 2, 2, 2, 3])))

Output:

{'pos': [3], 'peaks': [5]}
{'pos': [1], 'peaks': [2]}
{'pos': [], 'peaks': []}
Rish
  • 804
  • 8
  • 15
  • 1
    Won't this mess up finding the position of the later peaks? – Harry Day Dec 24 '18 at 14:28
  • Yes, it will. In that case you can create a list of list of numbers. For example: `[1, 2, 2, 2, 1]` as `[[1], [2, 2, 2], [1]]`. Modify your code to check only the first element of each list in the list. So in this case your loop will iterate over 1,2 and 1. While returning the position you can return the index position by using the length of each list. – Rish Dec 24 '18 at 14:40
  • See the edited post for preserving the index using the same code with little modifications. – Rish Dec 24 '18 at 15:18
0

A shorter script could be:

data_array = [1, 2, 5, 4, 6, 9]
# Delete the first and the last element of the data array. 
reduced_array = [ data_array[i] for i in range(1, len(data_array)-1) ]
# Find the maximum value of the modified array 
peak_value = max(reduced_array)
# Print out the maximum value and its index in the data array. 
print 'The peak value is: ' + str(peak_value)
print 'And its position is: ' + str(data_array.index(peak_value))

Output:

The peak value is: 6
And its position is: 4
Daedalus
  • 295
  • 2
  • 17