2
$\begingroup$

FindPeaks only works on a single dimension list and returns the position as an index, however, there are cases when it can return non-integer value as a position, which makes it not very useful if the data is specified in {{xi,yi}} format. So the question is, how to best return the coordinates of peaks in data? Say let the data be:

d = {{1, 1}, {3, 0}, {4, 1}, {5, 3}, {8, 3.5}, {10, 5}, {11, 5}, {13, 
    5}, {13.1, 5}, {13.5, 4}, {14, 3}, {17, 3}, {17.5, 3.5}, {18, 
    1.5}, {19, 1}, {25, 0.5}};
ListPlot[d, Joined -> True]

enter image description here

then

FindPeaks@d[[All, 2]]
{{1, 1}, {15/2, 5}}

you can immediately see the problem. I could probably use Ceiling in the index to get an integer and then Part but it seems a bit complicated. Isn't there a simpler way? Anyway, the function is doing interpolation probably by a Gaussian so why I find it a little embarrassing that it cannot work with x values. (Also I am not sure the solution with forcefully using the index will always work)

$\endgroup$
  • $\begingroup$ @bills There are no positions in my data, there are only x and corresponding y values. My goal is to find peaks in those data or local maxima of y as a function of x. $\endgroup$ – leosenko Aug 28 '17 at 3:11
1
$\begingroup$

One possibility is to interpolate the data. Then resample the data regularly so that FindPeaks can do its job.

dInt = Interpolation[d, InterpolationOrder -> 1];
dResampled = Table[dInt[t], {t, 1, 25}];
FindPeaks[dResampled]
{{1, 1.}, {23/2, 5.}}

If you want to detect the peak near 17, you can sample more frequently.

$\endgroup$
1
$\begingroup$

It does seem like there's a missing function there. I couldn't find a natural way of doing it. Your idea of using Ceiling (or Floor or IntegerPart) is probably the simplest:

fp = FindPeaks@d[[All, 2]];
d[[Ceiling[#[[1]]]]] & /@ fp

(* {{1, 1}, {13, 5}} *)

But that seems weird to me -- not only is it missing the peak at 17.5 (which is intentional behaviour and can be altered by messing with the optional arguments), but it's also just taking one point from the "elongated peak" between 10 and 13.1.

Here's a way to get all the peaks:

coordinateFindPeaks[data_] :=
 With[{daug = #},
    First@Last@Reap@Do[
        If[daug[[i, 2]] >= Max[daug[[i - 1, 2]], daug[[i + 1, 2]]], 
         Sow[daug[[i]]]
         ],
        {i, 2, Length[daug] - 1}]] &@
  ArrayPad[data, {{1}, {0}}, -Infinity]

Then

coordinateFindPeaks[d]

(* {{1, 1}, {10, 5}, {11, 5}, {13, 5}, {13.1, 5}, {17.5, 3.5}} *)

And on a randomly constructed dataset:

d2 = SortBy[RandomReal[1, {20, 2}], First]
peaks = coordinateFindPeaks[d2]
ListPlot[{d2, peaks}, Joined -> {True, False}, Filling -> {2 -> 0}]

enter image description here

$\endgroup$
  • $\begingroup$ "…it's also just taking one point from the "elongated peak" between 10 and 13.1." - what do you propose should be done about plateaus, then? Take the midpoint? The left or right endpoint? Or something else altogether? $\endgroup$ – J. M. will be back soon Aug 30 '17 at 1:03
  • $\begingroup$ @J.M. I suppose it depends on what the intended use of the output is. I've included all the actual data points which reach the peak in coordinateFindPeaks, which seemed like the most straightforward option. I guess I would have expected something like that (but with list positions) as the default behaviour in FindPeaks, but there are potential pitfalls with any approach. My approach would not differentiate between the OP's data and the same data with a trough at x = 12, but FindPeaks would. $\endgroup$ – aardvark2012 Aug 30 '17 at 1:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.