Home > Enterprise >  In an array of objects, calculating the average of values based on two other fields - using reduce o
In an array of objects, calculating the average of values based on two other fields - using reduce o

Time:12-08

I have a data array that looks like this:

data = [
{year: 2000, week: 7, score: 261},
{year: 2000, week: 8, score: 2960},
{year: 2000, week: 8, score: 3145},
{year: 2000, week: 9, score: 335},
{year: 2000, week: 9, score: 33},
{year: 2000, week: 10, score: 346},
{year: 2000, week: 10, score: 32},
{year: 2000, week: 11, score: 343},
{year: 2000, week: 11, score: 385},
{year: 2000, week: 12, score: 4944},
{year: 2000, week: 13, score: 630},
{year: 2000, week: 13, score: 705},
{year: 2000, week: 13, score: 747},
{year: 2001, week: 7, score: 372},
{year: 2001, week: 8, score: 756},
{year: 2001, week: 8, score: 273},
{year: 2001, week: 9, score: 298},
{year: 2001, week: 9, score: 3273},
{year: 2001, week: 10, score: 3688},
{year: 2001, week: 10, score: 3811},
{year: 2001, week: 11, score: 413},
{year: 2001, week: 11, score: 460},
{year: 2001, week: 12, score: 529},
{year: 2001, week: 12, score: 597},
{year: 2001, week: 13, score: 685},
{year: 2001, week: 13, score: 77},
{year: 2002, week: 7, score: 711},
{year: 2002, week: 8, score: 646},
{year: 2002, week: 8, score: 461},
{year: 2002, week: 9, score: 4020},
{year: 2002, week: 9, score: 355},
{year: 2002, week: 10, score: 308},
{year: 2002, week: 10, score: 262},
{year: 2002, week: 11, score: 257},
{year: 2002, week: 11, score: 2675},
{year: 2002, week: 12, score: 28},
]

I would like to group them so that I get the average score for scores in the same year and week. Ex:

output = [
{year: 2000, week: 7, avgScore: 261},
{year: 2000, week: 8, avgScore: 3052.5},
{year: 2000, week: 9, avgScore: 184},
{year: 2000, week: 10, avgScore: 189},
{year: 2000, week: 11, avgScore: 364},
{year: 2000, week: 12, avgScore: 2787},
{year: 2000, week: 13, avgScore: 726},
{year: 2001, week: 7, avgScore: 372},
{year: 2001, week: 8, avgScore: 514.5},
]

I figured out how to sum the numbers for the same year and week together using reduce but not sure how to get the average from that (finding the length and dividing the sum/total score)

output = data.reduce((acc, currentValue) => {
  if (currentValue.score === null) return acc;

  for (const key of acc) {
    if (key.year === currentValue.year && key.week === currentValue.week) {
      key.totalScore  = currentValue.score;
      return acc;
    }
  }
  const key = {
    year: currentValue.year,
    week: currentValue.week,
    totalScore: currentValue.score
  };
  return acc.concat([key]);
}, [])

Perhaps reduce is not the way to go to find average?

CodePudding user response:

A simple way to do this would be:

const avgs = {};

// first group your data into year and weeks
for(const row of data) {
  const { year, week, score } = row;
  if(avgs[year]) {
    if(avgs[year][week]) {
      avgs[year][week].total  = score;
      avgs[year][week].count  ;
    } else {
      avgs[year][week] = { total: score, count: 1 };
    }
  } else {
    avgs[year][week] = { total: score, count: 1 };
  }
}

// now calculate averages
for(const year of Object.keys(avgs)) {
  for(const week of Object.keys(avgs[year])) {
    const { total, count } = avgs[week][year];
    const avg = total/count;
    // you can now store this in the grouped object
    avgs[week][year].average = avg;

    // if you don't need total and count, you can delete them here
    delete avgs[year][week].total;
    delete avgs[year][week].count;
  }
}    

The above code should generate an object like:

avgs = {
  "2000": {
    "1": {
       "count": 2, // if count and total are not deleted
       "total": 200,
       "average": 100
     }
  }
}

CodePudding user response:

In my approach I use a .reduce() loop to create an object with individual score arrays. In a subsequent .map() over the object's entries I then translate each key-value combination (each values is an array of scores) into an object containing the year, week and (calculated) avg properties:

const data = [{year: 2000, week: 7, score: 261},{year: 2000, week: 8, score: 2960},{year: 2000, week: 8, score: 3145},{year: 2000, week: 9, score: 335},{year: 2000, week: 9, score: 33},{year: 2000, week: 10, score: 346},{year: 2000, week: 10, score: 32},{year: 2000, week: 11, score: 343},{year: 2000, week: 11, score: 385},{year: 2000, week: 12, score: 4944},{year: 2000, week: 13, score: 630},{year: 2000, week: 13, score: 705},{year: 2000, week: 13, score: 747},{year: 2001, week: 7, score: 372},{year: 2001, week: 8, score: 756},{year: 2001, week: 8, score: 273},{year: 2001, week: 9, score: 298},{year: 2001, week: 9, score: 3273},{year: 2001, week: 10, score: 3688},{year: 2001, week: 10, score: 3811},{year: 2001, week: 11, score: 413},{year: 2001, week: 11, score: 460},{year: 2001, week: 12, score: 529},{year: 2001, week: 12, score: 597},{year: 2001, week: 13, score: 685},{year: 2001, week: 13, score: 77},{year: 2002, week: 7, score: 711},{year: 2002, week: 8, score: 646},{year: 2002, week: 8, score: 461},{year: 2002, week: 9, score: 4020},{year: 2002, week: 9, score: 355},{year: 2002, week: 10, score: 308},{year: 2002, week: 10, score: 262},{year: 2002, week: 11, score: 257},{year: 2002, week: 11, score: 2675},{year: 2002, week: 12, score: 28},];

const res = Object.entries(data.reduce((a, c) => {
  (a[`${c.year}|${c.week}`] ??= []).push(c.score);
  return a; // collect score-arrays for each year|week combination
}, {})).map(([k, arr]) => {
  let [year, week] = k.split("|"); // return an object for each year|week element and calculate the average:
  return {year, week, avg: arr.reduce((a, c) => a   c) / arr.length};
})

console.log(JSON.stringify(res));

CodePudding user response:

You can use Array.reduce() to group the data by year and week, combining to form a key

We create a list of scores for each year and week, and compute the average each time a score is added.

We can then use Object.values() to turn our mapping object to an array as required.

const data = [ {year: 2000, week: 7, score: 261}, {year: 2000, week: 8, score: 2960}, {year: 2000, week: 8, score: 3145}, {year: 2000, week: 9, score: 335}, {year: 2000, week: 9, score: 33}, {year: 2000, week: 10, score: 346}, {year: 2000, week: 10, score: 32}, {year: 2000, week: 11, score: 343}, {year: 2000, week: 11, score: 385}, {year: 2000, week: 12, score: 4944}, {year: 2000, week: 13, score: 630}, {year: 2000, week: 13, score: 705}, {year: 2000, week: 13, score: 747}, {year: 2001, week: 7, score: 372}, {year: 2001, week: 8, score: 756}, {year: 2001, week: 8, score: 273}, {year: 2001, week: 9, score: 298}, {year: 2001, week: 9, score: 3273}, {year: 2001, week: 10, score: 3688}, {year: 2001, week: 10, score: 3811}, {year: 2001, week: 11, score: 413}, {year: 2001, week: 11, score: 460}, {year: 2001, week: 12, score: 529}, {year: 2001, week: 12, score: 597}, {year: 2001, week: 13, score: 685}, {year: 2001, week: 13, score: 77}, {year: 2002, week: 7, score: 711}, {year: 2002, week: 8, score: 646}, {year: 2002, week: 8, score: 461}, {year: 2002, week: 9, score: 4020}, {year: 2002, week: 9, score: 355}, {year: 2002, week: 10, score: 308}, {year: 2002, week: 10, score: 262}, {year: 2002, week: 11, score: 257}, {year: 2002, week: 11, score: 2675}, {year: 2002, week: 12, score: 28}, ]

const result = Object.values(data.reduce((acc, { year, week, score}) => { 
    const key = [year, week].join('-');
    acc[key] = acc[key] || { year, week, scores: [] };
    acc[key].scores.push(score);
    acc[key].avgScore = acc[key].scores.reduce((avg, score) => avg   score/acc[key].scores.length, 0);
    return acc;
}, {})).map(({ scores, ...obj }) => obj)

console.log('Result:', result)
.as-console-wrapper { max-height: 100% !important; }

  • Related