Reputation: 99
I have an array of 640 values that contains the y-values of what is needed to draw a line.
I need to determine array positions of the tops of each three peaks.
I have looked at similar questions on here relating to local maxima/local minima, but running the code I have found picks up the small peaks. I only need the three very distinct peaks.
I am trying to do this in Javascript. The data from the array is as follows:
126,126,126,126,126,126,126,126,126,126,126,126,126,126,124,123,122,122,120,119,119,118,118,118,119,119,119,120,121,121,122,124,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,124,125,124,125,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,123,124,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,122,123,122,123,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,119,119,119,119,116,115,114,113,113,113,113,113,115,117,118,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,119,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,118,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,108,107,106,118,108,108,103,103,103,103,103,103,104,105,107,108,109,109,109,109,109,109,109,109,109,109,109,109,109,109,109
I ran this bit of code on the array:
function pickPeaks(arr){
return arr.reduce( (res, val, i, self) => {
if(
// a peak when the value is greater than the previous and greater than the next
val > self[i - 1] && val > self[i + 1]
||
// a plateau when the value is greater than the previuos and equal to the next and from there the next different value is less
val > self[i - 1] && val === self[i + 1] && self.slice(i).find( item => item !== val ) < val
){
res.pos.push(i);
res.peaks.push(val);
}
return res;
}, { pos:[],peaks:[] } );
}
console.log(pickPeaks(first_black_array))
and it outputted the following:
peaks: (9) [362, 356, 356, 357, 358, 358, 367, 374, 377]
pos: (9) [21, 67, 69, 112, 179, 181, 313, 608, 612]
I would like the results to be something like:
pos: [24, 316, 616]
(the tops of each of the peaks)
Many thanks
Upvotes: 2
Views: 3902
Reputation: 8660
This function will go through your data and determine the largest transitions in data. It will then coordinate them so that the same peak will not be output to you when grabbing n
records.
function getPeaks(data, n = 3, peakList = []) {
return data.reduce((a, v, i, _a,
end_index = _a.length - 1,
change = v - _a[i - 1] || 0,
mate = i + Math.sign(change),
_ = (change) && a.peaks.push({change,i,mate})
) => (i === end_index) ?
(a.peaks.forEach(_ =>
(!peakList.includes(_.i) && (mate = a.peaks.find(__ =>
__.i === _.mate))) && (a.samePeak.push({
highpoint: data[_.i] > data[mate.i] ? _.i : mate.i,
steepness: mate.change > _.change ? mate.change : _.change
})),
peakList.push(_.i, mate.i)),
a.samePeak.sort((_, __) =>
__.steepness - _.steepness)
.map(_ => _.highpoint)
.slice(0, n)) : a, {
peaks: [],
samePeak: []
})
}
let data = [126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 124, 123, 122, 122, 120, 119, 119, 118, 118, 118, 119, 119, 119, 120, 121, 121, 122, 124, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 124, 125, 124, 125, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 123, 124, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 122, 123, 122, 123, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 119, 119, 119, 119, 116, 115, 114, 113, 113, 113, 113, 113, 115, 117, 118, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 108, 107, 106, 118, 108, 108, 103, 103, 103, 103, 103, 103, 104, 105, 107, 108, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109]
function getPeaks(data, n = 3, peakList = []) {
return data.reduce((a, v, i, _a,
end_index = _a.length - 1,
change = v - _a[i - 1] || 0,
mate = i + Math.sign(change),
_ = (change) && a.peaks.push({
change,
i,
mate
})
) => (i === end_index) ?
(a.peaks.forEach(_ =>
(!peakList.includes(_.i) && (mate = a.peaks.find(__ =>
__.i === _.mate))) && (a.samePeak.push({
highpoint: data[_.i] > data[mate.i] ? _.i : mate.i,
steepness: mate.change > _.change ? mate.change : _.change
})),
peakList.push(_.i, mate.i)),
a.samePeak.sort((_, __) =>
__.steepness - _.steepness)
.map(_ => _.highpoint)
.slice(0, n)) : a, {
peaks: [],
samePeak: []
})
}
//getpeaks(data, number of peaks to get)
console.log( getPeaks(data, 3) );
Admittedly, it became a little obfuscated as I went through - :) Here's a clearer version:
function getPeaks(data, n = 3, peakList = [] ) {
return data.reduce( ( a, v, i, _a, end_index = _a.length - 1 ) => {
change = v - _a[ i - 1 ] || 0,
mate = i + Math.sign( change ),
payload = {
change,
i,
mate
};
if ( change ) a.peaks.push( payload );
return ( i === end_index ) ? ( a.peaks.forEach( _ => {
if ( peakList.includes( _.i ) ) return;
if ( mate = a.peaks.find( __ => __.i === _.mate ) ) {
a.samePeak.push( {
highpoint: data[ _.i ] > data[ mate.i ] ? _.i : mate.i,
steepness: mate.change > _.change ? mate.change : _.change
} )
peakList.push( _.i, mate.i );
}
} ), a ) : a;
}, {
peaks: [],
samePeak: []
} ).samePeak.sort( ( _, __ ) => __.steepness - _.steepness )
.map(({highpoint})=>highpoint)
.slice( 0, n );
}
function getPeaks(data, n = 3, peakList = [] ) {
return data.reduce( ( a, v, i, _a, end_index = _a.length - 1 ) => {
change = v - _a[ i - 1 ] || 0,
mate = i + Math.sign( change ),
payload = {
change,
i,
mate
};
if ( change ) a.peaks.push( payload );
return ( i === end_index ) ? ( a.peaks.forEach( _ => {
if ( peakList.includes( _.i ) ) return;
if ( mate = a.peaks.find( __ => __.i === _.mate ) ) {
a.samePeak.push( {
highpoint: data[ _.i ] > data[ mate.i ] ? _.i : mate.i,
steepness: mate.change > _.change ? mate.change : _.change
} )
peakList.push( _.i, mate.i );
}
} ), a ) : a;
}, {
peaks: [],
samePeak: []
} ).samePeak.sort( ( _, __ ) => __.steepness - _.steepness )
.map(({highpoint})=>highpoint)
.slice( 0, n );
}
console.log( getPeaks(data, 3) );
Upvotes: 0
Reputation: 386578
You could look for smaller values, in comparison to the previous value and by updating the index of the last smallest value and if the values are increasing, then push the new minima.
Actually this approach returns a minima at index 608 where is should no, according to the question. Maybe you need to filter too small deltas of minima.
var data = [126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 124, 123, 122, 122, 120, 119, 119, 118, 118, 118, 119, 119, 119, 120, 121, 121, 122, 124, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 124, 125, 124, 125, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 124, 123, 124, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 122, 123, 122, 123, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 119, 119, 119, 119, 116, 115, 114, 113, 113, 113, 113, 113, 115, 117, 118, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 115, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 114, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 110, 108, 107, 106, 118, 108, 108, 103, 103, 103, 103, 103, 103, 104, 105, 107, 108, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109],
push = true,
last = 0,
result = data.reduce((r, v, i, a) => {
if (a[r[last]] === v) { // take latest index of series
r[last] = i;
return r;
}
if (a[i - 1] < v) { // look for new series
push = true;
return r;
}
if (a[i - 1] > v) {
if (push && a[i - 1] > a[r[last]]) { // prevent continuing minima
last = r.push(i) - 1;
push = false;
} else {
r[last] = i;
}
}
return r;
}, []);
console.log('values ', result.map(i => data[i].toString().padStart(3)).join(' '));
console.log('indices', result.map(v => v.toString().padStart(3)).join(' '));
Upvotes: 1
Reputation: 168967
To expand on my comment, something like
function detectPeaks(data, windowWidth, threshold) {
const peaks = [];
for (let i = 0; i < data.length; i++) {
const start = Math.max(0, i - windowWidth);
const end = Math.min(data.length, i + windowWidth);
let deltaAcc = 0;
for (let a = start; a < end; a++) {
deltaAcc += Math.abs(data[a - 1] - data[a]);
}
if (deltaAcc > threshold) {
peaks.push(i);
}
}
return peaks;
}
does reasonably well to find the "peaks" (or rather, areas where there are more change in values than elsewhere).
You can find an interactive example at https://codepen.io/akx/pen/QowEQq where you can also tweak the window width and threshold values.
Upvotes: 5