JosephFTaylor
JosephFTaylor

Reputation: 99

Finding the peaks of an array using Javascript

I have an array of 640 values that contains the y-values of what is needed to draw a line.

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

Answers (3)

zfrisch
zfrisch

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

Nina Scholz
Nina Scholz

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

AKX
AKX

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

Related Questions