简体   繁体   中英

lodash grouping an array by multiple keys

[Update]: I've removed most of the layout and lodash (failed code) from before because the JSON data format has changed.

I'm trying to group a data set in preparation for aggregating totals. Here is the incoming JSON layout. I need to group by country, then by brand:

  [
    {
      $id: "1",
      countryCode: "HT",
      brand: "CO",
      roomNights: 12,
      hotelSpend: 2000
     },
    {
     $id: "2",
     countryCode: "PK",
     brand: "HH",
     roomNights: 201,
     hotelSpend: 10000
    },
   {
     $id: "3",
     countryCode: "RO",
     brand: "CO",
     roomNights: 34,
     hotelSpend: 5000
    },
   { 
     $id: "4",
     countryCode: "US",
     brand: "ES",
     roomNights: 120,
     hotelSpend: 56000
   },
   {
     $id: "5",
     countryCode: "PK",
     brand: "HH",
     roomNights: 145,
     hotelSpend: 33000
   }
  ]

The data needs to be transformed into this format:

        ['Brand','HT'     , 'PK'        , 'US'     , 'RO', 'Avg Rm', 'Avg Spend']
        ['HH'   ,'0/0'    ,'201/10000', '0/0'       , '0/0'     , 201,   10000],
        ['CO'   ,'12/2000','0/0',     , '0/0'       , '34/5000', 23 ,    3500],
        ['ES'   , '0/0'    ,'0/0'    , '120/50000'  , '0/0'    , 120, 50000]

The roomNights and hotelSpend will be totalled per brand & country and the average of each will need to be calculated fields at the end.

Thanks!

Let's first define a mean function and add it to _ :

_.mixin({
  mean: function(ds) {
    return _(ds).foldr(function(a, b) { return a + b; }, 0) / ds.length;
  }
}); 

Let's define functions for selecting the rows and columns:

var row = function(d) { return d.brand; };
var col = function(d) { return d.countryCode; };

aggr function takes a sub-list of our data and aggregates its values into one value (which here is a string representation of a rational number):

var aggr = function(ds) {
  var val = _(ds).foldr(function(a, b) {
    return {
      roomNights: a.roomNights + b.roomNights,
      hotelSpend: a.hotelSpend + b.hotelSpend
    };
  }, {roomNights: 0, hotelSpend: 0});

  return val.roomNights + "/" + val.hotelSpend;
};

Our rows and columns labels:

rows = _.chain(data).map(row).unique().value();
columns = _.chain(data).map(col).unique().value();

The pivot:

[["Brand/Country"].concat(columns).concat(["Avg Rm", "Avg Spend"])] // header row
.concat(_(rows).map(function(r){

  // data in this row
  rdata = _(data).filter(function(d) { return row(d) == r; });

  return [r].concat(_(columns).map(function(c){
    return aggr(_(rdata).filter(function(d) {return col(d) == c; }));
  }))
  // the last two columns in each row
  .concat([
    _.chain(rdata).map(function(d) { return d.roomNights; }).mean().value(),
    _.chain(rdata).map(function(d) { return d.hotelSpend; }).mean().value()
  ]);
}));

You can control the order or filter the result by specific countryCode or brand by modifying the rows and columns arrays, similar to spreadsheets.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM