javascriptarraysdatearray-reduce

How to group array of dates by year and subtract last value - first value?


I have the array of dates and I want to group dates by year and subtract the last value - the first value. for example, I have the following array:

const data = [
  { x: "2021-10-17T14:38:45.540Z", y: 2 },
  { x: "2021-10-17T13:38:45.540Z", y: 2 },
  { x: "2021-09-16T14:36:46.540Z", y: 1 },
  { x: "2021-01-04T14:35:46.540Z", y: 2 },
  { x: "2021-01-01T14:30:46.540Z", y: 1 },
  { x: "2020-02-01T06:28:47.520Z", y: 12 },
  { x: "2020-02-01T07:28:47.520Z", y: 12 },
  { x: "2019-04-13T10:19:20.034Z", y: 20 },
  { x: "2018-01-01T09:09:19.134Z", y: 4 },
  { x: "2017-01-01T12:09:19.034Z", y: 11 },
  { x: "2016-01-02T12:10:20.034Z", y: 24 },
  { x: "2016-01-02T11:10:20.034Z", y: 14 }
];

This is what I tried Group array of object by year and subtract the last value - the first value

I got this result which sum the values :

 [
  {
    "value": 8,
    "label": "2021"
  },
  {
    "value": 24,
    "label": "2020"
  },
  {
    "value": 20,
    "label": "2019"
  },
  {
    "value": 4,
    "label": "2018"
  },
  {
    "value": 11,
    "label": "2017"
  },
  {
    "value": 38,
    "label": "2016"
  }
]

Expected output for year (value = last value - first value):

 [
  {
    "value": 1,
    "label": "2021"
  },
  {
    "value": 0,
    "label": "2020"
  },
  {
    "value": 20,
    "label": "2019"
  },
  {
    "value": 4,
    "label": "2018"
  },
  {
    "value": 11,
    "label": "2017"
  },
  {
    "value": 10,
    "label": "2016"
  }
]

This is the group by year function :

function group_by_year(arr) {
  return Object.values(
    arr.reduce((a, { x: date_string, y: value }) => {
      const { year } = get_date_parts(date_string);
      if (a[year] === undefined) {
        a[year] = { value: 0, label: year };
      }
      a[year].value += value; // the problem (lastValue - firstValue)

      return a;
    }, {})
  );
}

The problem is how to change this line a[year].value += value; to a[year].value = lastValue - firstValue;


Solution

  • Inside the accumulator, you are just adding the values from array. This will generate total value against an year.

    I have used the below logic.

    From the Accumulated result obtained from Array.reduce, run Array.map to generate the output of your desire. It will be lastValue - firstValue. Use Math.abs if you are interested in positive difference only without sign.

    Working Fiddle

    const data = [{ x: "2021-10-17T14:38:45.540Z", y: 2 },{ x: "2021-10-17T13:38:45.540Z", y: 2 },{ x: "2021-09-16T14:36:46.540Z", y: 1 },{ x: "2021-01-04T14:35:46.540Z", y: 2 },{ x: "2021-01-01T14:30:46.540Z", y: 1 },{ x: "2020-02-01T06:28:47.520Z", y: 12 },{ x: "2020-02-01T07:28:47.520Z", y: 12 },{ x: "2019-04-13T10:19:20.034Z", y: 20 },{ x: "2018-01-01T09:09:19.134Z", y: 4 },{ x: "2017-01-01T12:09:19.034Z", y: 11 },{ x: "2016-01-02T12:10:20.034Z", y: 24 },{ x: "2016-01-02T11:10:20.034Z", y: 14 }];
    
    function get_date_parts(iso_string) {
      const [year, month, day, hr, min, sec] = iso_string.split(/\D/g);
      return { year, month, day, hr, min, sec };
    }
    
    function group_by_year(arr) {
      const grouppedYear = Object.values(
        arr.reduce((a, { x: date_string, y: value }) => {
          const { year } = get_date_parts(date_string);
          if (a[year] === undefined) {
            a[year] = {
              firstValue: value,
              lastValue: value,
              label: year,
              occurance: 1,
            };
          } else {
            a[year].lastValue = value;
            ++a[year].occurance;
          }
          return a;
        }, {})
      );
      const returnValue = grouppedYear.map(({firstValue, lastValue, label, occurance}) => ({value: occurance > 1 ? lastValue - firstValue : firstValue, label}))
      return returnValue;
    }
    const output = group_by_year(data);
    console.log(output)