Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smoothing out values of an array

If I had an array of numbers such as [3, 5, 0, 8, 4, 2, 6], is there a way to “smooth out” the values so they’re closer to each other and display less variance?

I’ve looked into windowing the data using something called the Gaussian function for a 1-dimensional case, which is my array, but am having trouble implementing it. This thread seems to solve exactly what I need but I don’t understand how user naschilling (second post) came up with the Gaussian matrix values.

Context: I’m working on a music waveform generator (borrowing from SoundCloud’s design) that maps the amplitude of the song at time t to a corresponding bar height. Unfortunately there’s a lot of noise, and it looks particularly ugly when the program maps a tiny amplitude which results in a sudden decrease in height. I basically want to smooth out the bar heights so they aren’t so varied.

The language I'm using is Javascript.

EDIT: Sorry, let me be more specific about "smoothing out" the values. According to the thread linked above, a user took an array

[10.00, 13.00, 7.00, 11.00, 12.00, 9.00, 6.00, 5.00]

and used a Gaussian function to map it to

[ 8.35,  9.35, 8.59,  8.98,  9.63, 7.94, 5.78, 7.32]

Notice how the numbers are much closer to each other.

EDIT 2: It worked! Thanks to user Awal Garg's algorithm, here are the results:

No smoothing Some smoothing Maximum smoothing

EDIT 3: Here's my final code in JS. I tweaked it so that the first and last elements of the array were able to find its neighbors by wrapping around the array, rather than calling itself.

var array = [10, 13, 7, 11, 12, 9, 6, 5];

function smooth(values, alpha) {
    var weighted = average(values) * alpha;
    var smoothed = [];
    for (var i in values) {
        var curr = values[i];
        var prev = smoothed[i - 1] || values[values.length - 1];
        var next = curr || values[0];
        var improved = Number(this.average([weighted, prev, curr, next]).toFixed(2));
        smoothed.push(improved);
    }
    return smoothed;
}

function average(data) {
    var sum = data.reduce(function(sum, value) {
        return sum + value;
    }, 0);
    var avg = sum / data.length;
    return avg;
}

smooth(array, 0.85);
like image 906
robinnnnn Avatar asked Sep 25 '15 18:09

robinnnnn


People also ask

How do you remove values from an array?

Pass the value of the element you wish to remove from your array into the indexOf() method to return the index of the element that matches that value in the array. Then make use of the splice() method to remove the element at the returned index.

How do you smooth data?

Data smoothing can be defined as a statistical approach of eliminating outliers from datasets to make the patterns more noticeable. The random method, simple moving average, random walk, simple exponential, and exponential moving average are some of the methods used for data smoothing.

What is a smooth array?

In smoothing, the data points of a signal are modified so individual points (presumably because of noise) are reduced, and points that are lower than the adjacent points are increased leading to a smoother signal.

How do you smooth data in Python?

Another method for smoothing is a moving average. There are various forms of this, but the idea is to take a window of points in your dataset, compute an average of the points, then shift the window over by one point and repeat. This will generate a bunch of points which will result in the smoothed data.


1 Answers

Interesting question!

The algorithm to smooth out the values obviously could vary a lot, but here is my take:

"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];

function avg (v) {
  return v.reduce((a,b) => a+b, 0)/v.length;
}

function smoothOut (vector, variance) {
  var t_avg = avg(vector)*variance;
  var ret = Array(vector.length);
  for (var i = 0; i < vector.length; i++) {
    (function () {
      var prev = i>0 ? ret[i-1] : vector[i];
      var next = i<vector.length ? vector[i] : vector[i-1];
      ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
    })();
  }
  return ret;
}

function display (x, y) {
  console.clear();
  console.assert(x.length === y.length);
  x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}

display(array, smoothOut(array, 0.85));

NOTE: It uses some ES6 features like fat-arrow functions and template strings. Firefox 35+ and Chrome 45+ should work fine. Please use the babel repl otherwise.

My method basically computes the average of all the elements in the array in advance, and uses that as a major factor to compute the new value along with the current element value, the one prior to it, and the one after it. I am also using the prior value as the one newly computed and not the one from the original array. Feel free to experiment and modify according to your needs. You can also pass in a "variance" parameter to control the difference between the elements. Lowering it will bring the elements much closer to each other since it decreases the value of the average.

A slight variation to loosen out the smoothing would be this:

"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];

function avg (v) {
  return v.reduce((a,b) => a+b, 0)/v.length;
}

function smoothOut (vector, variance) {
  var t_avg = avg(vector)*variance;
  var ret = Array(vector.length);
  for (var i = 0; i < vector.length; i++) {
    (function () {
      var prev = i>0 ? ret[i-1] : vector[i];
      var next = i<vector.length ? vector[i] : vector[i-1];
      ret[i] = avg([t_avg, prev, vector[i], next]);
    })();
  }
  return ret;
}

function display (x, y) {
  console.clear();
  console.assert(x.length === y.length);
  x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}

display(array, smoothOut(array, 0.85));

which doesn't take the averaged value as a major factor.

Feel free to experiment, hope that helps!

like image 114
user3459110 Avatar answered Nov 01 '22 14:11

user3459110