0

I have a array which updates every minute. When i want to show it over a day, I want to have the average of every hour that day.

The most recent minute is add the end of the array.

    //get the last elements from the array
    var hours= (this.today.getHours() + 1) * 60
    var data = Array.from(this.temps.data)
    let lastData = data.slice(Math.max(data.length - hours))

    let newData: any

    // calculate the average of every hour
    for (let i = 0; i < minutes; i++) {
      var cut = i * 60
      for (let i = cut; i < (cut + 60); i++) {
        newData = newData + lastData[i];
        let test = newData/60
        console.log(test);
      }
    }

I can't figure out how I make an array from every last 60 elements. My goal is to get an array like

avgHour[20,22,30,27,]

The array I have is updated every minute. So I need the average of every 60 elements to get a hour.

array looks like this

data[25,33,22,33]

It is every minute from a week so really long.

This Worked For me

    var arrays = [], size = 60;

    while (arr.length > 0){
        arrays.push(arr.splice(0, size));
    }

    for (let i = 0; i < (arrays.length - 1); i++) {
      var sum = 0
        for (let b = 0; b < 60; b++) {
          sum += arrays[i][b]
        }
        let avg = sum/60
        arr2.push(avg)            
    }

this just splits the array every 60 elements. Now I can calculate the average for every 60. duplicate of How to split a long array into smaller arrays, with JavaScript

Thanks for the help!

Bono495
  • 43
  • 11

4 Answers4

2

I am a big fan of the functional programming library Ramda. (Disclaimer: I'm one of its authors.) I tend to think in terms of simple, reusable functions.

When I think of how to solve this problem, I think of it through a Ramda viewpoint. And I would probably solve this problem like this:

const avgHour = pipe(
  splitEvery(60),
  map(mean),
)

// some random data
const data = range(0, 7 * 24 * 60).map(_ => Math.floor(Math.random() * 20 + 10))

console.log(avgHour(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {pipe, splitEvery, map, mean, range} = R</script>

I think that is fairly readable, at least once you understand that pipe creates a pipeline of functions, each handing its result to the next one.

Now, there is often not a reason to include a large library like Ramda to solve a fairly simple problem. But all the functions used in that version are easily reusable. So it might make sense to try to create your own versions of these functions and keep them available to the rest of your application. In fact, that's how libraries like Ramda actually get built.

So here is a version that has simple implementations of those functions, ones you might place in a utility library:

const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
const splitEvery = (n) => (xs) => {
  let i = 0, a = [] 
  while (i < xs.length) {a.push(xs.slice(i, i + n)); i += n}
  return a
}
const map = (fn) => (xs) => xs.map(x => fn(x))
const sum = (xs) => xs.reduce((a, b) => a + b, 0)
const mean = (xs) => sum(xs) / (xs.length || 1)

const avgHour = pipe(
  splitEvery(60),
  map(mean)
)

const range = (lo, hi) => [...Array(hi - lo)].map((_, i) => lo + i)

// some random data
const data = range(0, 7 * 24 * 60).map(_ => Math.floor(Math.random() * 20 + 10))

console.log(avgHour(data))
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
1

You can reduce the data and group by hour, then simply map to get each hour's average. I'm using moment to parse the dates below, you can do that with whatever lib/js you prefer...

const arr = Array.from({length: 100}, () => ({time: moment().subtract(Math.floor(Math.random() * 10), 'hours'), value: Math.floor(Math.random() * 100)}));

const grouped = [...arr.reduce((a, b) => {
    let o = a.get(b.time.get('hour')) || {value: 0, qty: 0};
    a.set(b.time.get('hour'), {value: o.value + b.value, qty: o.qty + 1});
    return a;
}, new Map)].map(([k, v]) => ({
    [k]: v.value / v.qty
}));




console.log(grouped)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"></script>
baao
  • 71,625
  • 17
  • 143
  • 203
0

To measure the average I needed to split the array every 60 elements. This is the solution I found

//Calculate the average of every 60 elements to get the average of an hour
var arr2: number[] = []
var arr: number[] = []
arr = Array.from(this.temps.data)
var arrays = [], size = 60;

while (arr.length > 0){
    arrays.push(arr.splice(0, size));
}

for (let i = 0; i < (arrays.length - 1); i++) {
  var sum = 0
    for (let b = 0; b < 60; b++) {
      sum += arrays[i][b]
    }
    let avg = sum/60
    arr2.push(avg)            
}

After all I think its stupid to get the last elements of the array, Because this is a better solution. But thanks for the help!

Bono495
  • 43
  • 11
  • It may not matter at all for you, but this version is destructive toward your original array. You might want to do a shallow clone first, `var newArr = arr.slice(0)`, and then do you splicing on that one. – Scott Sauyet Feb 11 '19 at 16:27
  • Nope it didn't matter as I already copy it from the original object to an array. I updated my code. But it was good to look at for a sec cause it could have broke my code. – Bono495 Feb 12 '19 at 09:05
0

By grouping and then reducing you can do this like following.

function groupBy(list, keyGetter) {
  const map = {};
  list.forEach((item) => {
    const key = keyGetter(item);
    if (!map[key]) {
      map[key] = [item];
    } else {
      map[key].push(item);
    }
  });
  return map;
}

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];

const now = (new Date()).getTime();

const stepSize = 60000*1;

const withTime = data.reverse().map((x, i) => { return { time: new Date(now - stepSize * i), temp: x } });

const grouped = groupBy(withTime, x => new Date(x.time.getFullYear(), x.time.getMonth(), x.time.getDate(), x.time.getHours()).valueOf());


const avg = Object.entries(grouped).map((x) => {
  return {
    time: new Date(Number(x[0])),
    temp: x[1].map(y => y.temp).reduce((acc, val) => acc + val) * (1.0 / x[1].length)
  }
});

console.log(avg);
AntiHeadshot
  • 1,130
  • 9
  • 24