Collapse objects in array based on id and calculate new properties in javascript

Viewed 65

Here's a sample of a very particular dataset I am working on. I have an array of objects in the following format:

const input = [
  {year: "2010", country_id: "01", country: "India", region: "North", category: "a", value: "10", population: "100"},
  {year: "2010", country_id: "01", country: "India", region: "South", category: "a", value: "10", population: "100"},
  {year: "2010", country_id: "01", country: "India", region: "North", category: "b", value: "10", population: "100"},
  {year: "2010", country_id: "01", country: "India", region: "North", category: "c", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "South", category: "a", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "South", category: "a", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "North", category: "b", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "North", category: "c", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "East", category: "c", value: "10", population: "100"},
  {year: "2015", country_id: "02", country: "Turkey", region: "North", category: "a", value: "15", population: "25"},
  {year: "2015", country_id: "02", country: "Turkey", region: "North", category: "b", value: "5", population: "25"}
]

How can I collapse the objects in the array based on year and id and create a, b, c and total_population new properties in output based on the data in category, value and population from input?

One thing to note is that a, b and c are cumulative sums and total_population is a cumulative sum based on region. This means that population values are duplicated i some cases (i.e.: there are three entries in country "India" and region "North" that should total 100 and not 300.)

const output = [
  {year: "2010", id: "01", country: "India", a: 20, b: 10, c: 10, total_population: 200},
  {year: "2015", id: "01", country: "India", a: 10, b: 10, c: 20, population: 300},
  {year: "2015", id: "02", country: "Turkey", a: 15, b: 5, population: 25}
]

My best attempt follows. Unfortunately I am constantly getting NaN when trying to execute the cumulative sum. Also, it seems that this solution does not perform well when testing it in a larger dataset.

const years = [...new Set(input.map((d) => d.year))].sort()

const country_ids = [...new Set(input.map((d) => d.country_id))].sort()

const combined_ids = {
  let array = [];

  country_ids.forEach((y, i) => {
    years.forEach((m) => {
      let obj = {};
      obj["year"] = m;
      obj["country_id"] = y;
      obj["combined_id"] = m + "-" + y;
      array.push(obj);
    });
  });

  return array;
}

const output = combined_ids.map((y, i) => {
  input.map((m) => {
    if (y.combined_id === m.year + "-" + m.country_id) {
      y.country_id = m.country_id;

      y[m.category] += !m.category
       ? 0
       : m.value === NaN
       ? 0
       : m.value === null
       ? 0
       : m.value === undefined
       ? 0
       : m.category === undefined
       ? 0
       : +m.value;

      y["total_population"] += +m.population;
      }
  });
  return y;
});

1 Answers

You can try below solution using array.reduce. First you need to create a dictionary to store unique population values. Then you can use another reduce to group objects and combine them together. delete will be used to make sure population for the same key is only used once:

const input = [
  {year: "2010", country_id: "01", country: "India", region: "North", category: "a", value: "10", population: "100"},
  {year: "2010", country_id: "01", country: "India", region: "South", category: "a", value: "10", population: "100"},
  {year: "2010", country_id: "01", country: "India", region: "North", category: "b", value: "10", population: "100"},
  {year: "2010", country_id: "01", country: "India", region: "North", category: "c", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "South", category: "a", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "South", category: "a", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "North", category: "b", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "North", category: "c", value: "10", population: "100"},
  {year: "2015", country_id: "01", country: "India", region: "East", category: "c", value: "10", population: "100"},
  {year: "2015", country_id: "02", country: "Turkey", region: "North", category: "a", value: "15", population: "25"},
  {year: "2015", country_id: "02", country: "Turkey", region: "North", category: "b", value: "5", population: "25"}
]

let getKey = obj => `${obj.year}-${obj.country}-${obj.region}`;

let populationDict = input.reduce((acc, cur) => {
    acc[getKey(cur)] = +cur.population;
    return acc;
}, {})


let output = input.reduce((acc, cur) => {
    let { year, country_id, country, category, value } = cur;
    let val = +value;
    let prev = acc.find(x => x.year === year && x.id === country_id && x.country === country);
    let population = populationDict[getKey(cur)];
    delete populationDict[getKey(cur)];
    
    if(!prev){
        acc.push({year,id: country_id,country, [category]: val, total_population: population});       
    } else {
        prev[category] = prev[category] ? prev[category] + val : val;
        if(population) {
          prev.total_population += population;
        }        
    }

    return acc;

}, []);

console.log(output);

Related