JSilva JSilva - 3 months ago 8x
Javascript Question

Best way to summarize data for an array of objects using JavaScript and lodash

I have an array of objects, and I looking for the best most efficient way to group and summarize the data. Currently I have a couple of hundred lines of code and lots of 'for each' statements involved, and I know there is a lot easier way to do this, but I just can't get it quite working.

Here is a small sample of my data. For any given data set I might have several hundred products. Each product has five stars and the star can be assigned one of three values (Earned, Not Earned, or In Progress). My goal is to see a summary of how many of each value is assigned to each star.

Product: "A"
star1: "Not Earned"
star2: "In Progress"
star3: "Earned"
star4: "Not Earned"
star5: "In Progress"
Product: "B"
star1: "In Progress"
star2: "Not Earned"
star3: "In Progress"
star4: "Earned"
star5: "Earned"

In the end I would see a result something like this: I realize my formatting of objects and arrays are off a bit which is one more part of the reason why I'm asking for help. I'm also using JavaScript and lodash.

Results= [{
In Progress: 50,
Not Earned: 32,
Earned: 1
In Progress: 10
Not Earned: 14,
Earned: 11
In Progress: 45,
Not Earned: 25,
Earned: 19

How can I do this?


Here is one way to do it, probably not the fastest, but it works and doesn't produce any temp variables. It's not hard-coded to the data other than a single array var name (r), doesn't need any library to run (except in dino browsers), and it's "simple":

var r=[{
  Product: "A",
  star1: "Not Earned",
  star2: "In Progress",
  star3: "Earned",
  star4: "Not Earned",
  star5: "Earned"
  Product: "B",
  star1: "In Progress",
  star2: "Not Earned",
  star3: "In Progress",
  star4: "Earned",
  star5: "Earned"
  Product: "C",
  star1: "In Progress",
  star2: "Not Earned",
  star3: "Not Earned",
  star4: "Earned",
  star5: "In Progress"

var sums = {}; // A count holder

Object.keys(r[0]).forEach(function(k){ // For each key in the data of a single data object
     this[k]=r.map(function(o){ return o[k] }) // Pluck values
             if(this[w]){this[w]++;}else{this[w]=1;} // Count values using an object
             return this;
         },{}).pop();  // Take just one of the count object copies (poor-man's reduce with this)
}, sums);

// View result:
JSON.stringify(sums, null, "\t")
/* == {
    "Product": {
        "A": 1,
        "B": 1,
        "C": 1
    "star1": {
        "Not Earned": 1,
        "In Progress": 2
    "star2": {
        "In Progress": 1,
        "Not Earned": 2
    "star3": {
        "Earned": 1,
        "In Progress": 1,
        "Not Earned": 1
    "star4": {
        "Not Earned": 1,
        "Earned": 2
    "star5": {
        "Earned": 2,
        "In Progress": 1

The basic idea is to pluck all the values under each key, then count the values. This two-pass solution might be a little slower than a hard-coded single-purpose routine, but it eliminates the need for a hand-coded reduce function.

You can default in zeros if needed, I wasn't sure if the two example objects contained all the possible values your set contains...