Well here's some java source code that will do the locating.
Basically, it compares each element in the List, with the Y value
before and next, to find all local peaks.
It then filters out the small ones, by testing if the peak is below
the average for the entire sample, and then generates a local average
(ie the average of the 3 samples before and next) and checks if the
peak is signficantly higher (in terms of a percentage of the total
average) than the local average (this should eliminate shoulders).
You will need to tweak peakPercentage to say how sharp a peak needs
(18% is the highest value that will get both peaks, but 0% will also
get both peaks as the others are below the total average) to be and
searchDepth to say how wide a shoulder is acceptable.
I havn't done any searching for mathmatical formulas or more advanced
methods of such peak searching, rather stuck with a rather simple
method that I put together while thinking about the question.
import java.util.ArrayList;
import java.util.List;
/**
* @author James McGuigan
*/
public class XYMapping {
public List X;
public List Y;
public List PeakX = new ArrayList();
public List PeakY = new ArrayList();
public double YAverage = 0;
public double peakPercentage = 0.10;
public int searchDepth = 2;
/**
* @param X = List of doubles for X coords - assumes they are evenly spaced
* @param Y = List of doubles for Y coords
* @param peakPercentage = how high (as percentage of total average)
* @param searchDepth = how far to search for the local average
*/
public XYMapping(List X, List Y, double peakPercentage, int searchDepth) {
super();
this.X = X;
this.Y = Y;
this.peakPercentage = peakPercentage;
this.searchDepth = searchDepth;
this.FindPeaks();
}
public void FindPeaks() {
double Ytotal = 0;
for(int i=0;i<this.Y.size();i++) {
Ytotal += ((Double)this.Y.get(i)).doubleValue();
} this.YAverage = Ytotal / this.Y.size();
double peak = 0.0;
for(int i=0;i<this.X.size();i++) {
double current = ((Double)this.Y.get(i)).doubleValue();
double last = 0;
double next = 0;
if(i-1 > 0) last = ((Double)this.Y.get(i-1)).doubleValue();
if(i+1 < this.X.size()) next = ((Double)this.Y.get(i+1)).doubleValue();
// are we at a local peak
if(current > last && current > next)
{
// test height above average to weed out shoulders
// use an absolute height value to weed out small hills
double total = current;
int squares = 1;
for(int d=1;d<=searchDepth;d++) {
if(i-d > 0) { // test for out of bounds
total += ((Double)this.Y.get(i-d)).doubleValue();
squares++;
}
if(i+d < this.X.size()) {
total += ((Double)this.Y.get(i+d)).doubleValue();
squares++;
}
}
double average = total / squares;
double minPeakSize = average + (this.YAverage * this.peakPercentage);
// test if the peak is higher than mid point of graph
// and significantly higher than sourounding points
if(current > this.YAverage
&& (current - average) > minPeakSize) { //this.heightAboveAverage) {
this.PeakX.add(this.X.get(i));
this.PeakY.add(this.Y.get(i));
}
}
}
for(int i=0;i<this.PeakX.size();i++) {
System.out.print("Peak " + i + " = X:");
System.out.print(this.PeakX.get(i));
System.out.print("\tY:");
System.out.print(this.PeakY.get(i));
System.out.print("\n");
}
}
public static void main(String[] args) {
int[] xarray = {1,2,3,4,5,6,7, 8, 9,10,11,12,13,14,15,16,17,18,19,20};
int[] yarray = {2,3,4,3,2,5,10,20,13,10, 5, 4, 6, 4, 3, 2, 7,15,28,12};
List X = new ArrayList();
List Y = new ArrayList();
for(int i=0;i<xarray.length;i++) { X.add(new Double(xarray[i]));
Y.add(new Double(yarray[i])); };
double peakPercentage = 0.18; // 18% - highest value that still find
both peaks in sample data
int searchDepth = 3;
XYMapping mapper = new XYMapping(X,Y,peakPercentage,searchDepth);
}
}
Code Output:
Peak 0 = X:8.0 Y:20.0
Peak 1 = X:19.0 Y:28.0 |