2

I am attempting to write a simple set of instructions to return the index of a peak in a set of data. In my actual data set I have multiple peaks (so simply finding the max or min values is not sufficient). For my purposes, I can define a peak as any value that is less than the value before in and also less than the value following in (I am looking for negative peaks).

I have a sort of working example but it fails if there are two identical numbers in the list.

mylist = [10, 8, 5, 3, 1, 3, 4]
min =[]
for i in mylist[1:-1]: # the pre and post comparison means I have to start from item 2 to second last item
    pre = mylist.index(i)-1
    post = mylist.index(i)+1
    print('current i', i)
    print('index', pre, post)
    print('values pre post', mylist[pre], mylist[post])
    if i < mylist[pre] and i< mylist[post]:
       print('true')
       min.append(mylist.index(i))
    print('min=', min)

This seems to work until it gets to the second '3' (at position 5) in the list in which case it evaluates it against the values either side of the first '3'

....
current i 1
index 3 5
values pre post 3 3
true
current i 3
index 2 4
values pre post 5 1
min= [4]

As you can see it correctly finds that the value after '1' is '3' but I think its basically saying the value at index 5 = 3 so what values are either side of 3 and then reading the first 3. In my head this seems like it should be trivial, but am flummoxed. My searches and the suggested questions when writing this didn't flag any duplicates, but I would be amazed if I'm the first person this has happened to...

(also for explanation, I was using scipy find_peaks but it doesn't work for my purposes. If the final data points are rising it identifies the last data point as a peak. e.g. [... 11, 12, 13, 14, 15] it would identify '15' as a peak, which it may not be).

its_broke_again
  • 319
  • 4
  • 12
  • Basically, you are using `index` wrong. It is not "the" index (which it is in `mylist[i]`) but "an" index – the "index of the first occurrence of *x* in *s*" (https://docs.python.org/3/library/stdtypes.html#index-19). – Jongware Dec 05 '18 at 15:52
  • thanks, that makes sense in terms of why the output was for the first of the repeated digits. – its_broke_again Dec 05 '18 at 16:23

2 Answers2

4

I faced the same issue when I tried to iterate through mylist[1:-1]

Since you need the indices for preceding element in the first iteration and succeeding element in the last iteration, the code will miss locating some values and push 'IndexError: list index out of range error as well. Also, you are writing a lot of code to correlate index, value at each iteration, that is inbuilt in the enumerate(list) function

Below code works okay and gets you what you need:

mylist = [10, 8, 5, 3, 1, 3, 4]
peak = []     # min is a python keyword, so it is advised that you don't define it as a variable, we'll use peak to list all min values

# While iterating through indices and values, use enumerate
for i,x in enumerate(mylist):       # iterate through all indices
    if i == 0 or i == (len(mylist)-1):  # pass the first and last index
        pass
    else:
        pre = mylist[i-1]               # use the index variable i to locate pre 
        post = mylist [i+1]             # and post
        print ("Selected list >> Pre: {}, Index: {}, Post: {}".format(pre,x,post)) # updated the print line to make it more readable
        if pre > x < post:              # check if current element is between pre and post
            print ("True")
            peak.append(x)              # append to peak list if true
print (peak)

[Out]: 
Selected list >> Pre: 10, Index: 8, Post: 5
Selected list >> Pre: 8, Index: 5, Post: 3
Selected list >> Pre: 5, Index: 3, Post: 1
Selected list >> Pre: 3, Index: 1, Post: 3    # Peak found
True                                          # Print true
Selected list >> Pre: 1, Index: 3, Post: 4
[1]                                           # only 1 peak in the list

Let me know if you're happy with the code.

ParvBanks
  • 1,316
  • 1
  • 9
  • 15
  • 1
    Thanks this works perfectly. You are exactly right, I initially tried `[i-1]` but kept getting the index out of range error. I figured out why, but my in retrospect, my fix was to find 'an index' rather than 'the index' as pointed out in the comments. – its_broke_again Dec 05 '18 at 16:26
  • Glad it worked. You should learn about the enumerate function and apply it more in cases where you need both index and value. Happy learning! :) – ParvBanks Dec 05 '18 at 16:30
0

If you're willing to use numpy and scipy, you can use scipy.argrelextrema on a numpy array to get a local minimum (after https://stackoverflow.com/a/13491866/3651127):

import numpy as np
from scipy.signal import argrelextrema

mylist = [10, 8, 5, 3, 1, 3, 4]
x = np.array(mylist)

# for local minima
argrelextrema(x, np.less)

which returns:

(array([4], dtype=int64),)
dagrha
  • 2,449
  • 1
  • 20
  • 21
  • Thanks, I haven't seen this function before. Im not sure it will find multiple peaks, but it may well fix a different problem I've been having! – its_broke_again Dec 05 '18 at 16:29