Javascript按键计算对象数组的百分比总数

时间:2017-06-22 18:00:55

标签: javascript d3.js

假设我在Javascript中有以下数据结构:

 const data = [
       {year: 2017, kind: "mammal", animal: "dog", total: 5},
       {year: 2017, kind: "mammal", animal: "dog", total: 3},
       {year: 2016, kind: "mammal", animal: "dog", total: 5},
       {year: 2016, kind: "bird", animal: "chicken", total: 5},
       {year: 2016, kind: "bird", animal: "chicken", total: 90}
 ];

我希望能够指定一个或多个密钥,例如year来对数据进行分组。因此,如果我只是将year指定为组,我希望按如下方式计算总计:

 [{year: 2017, kind: "mammal", animal: "dog", count: 8, pct: 1},
  {year: 2016, kind: "mammal", animal: "dog", count: 5, pct: 0.05},
  {year: 2016, kind: "bird", animal: "chicken", count: 95, pct: 0.95}]

或者如果我按yearkind指定小组总数,我会想要以下内容:

 [{year: 2017, kind: "mammal", animal: "dog", count: 8, pct: 1},
  {year: 2016, kind: "mammal", animal: "dog", count: 5, pct: 1},
  {year: 2016, kind: "bird", animal: "chicken", count: 95, pct: 1}]

我怎样才能在Javascript中实现这一点?我已经看过使用d3的嵌套函数,但到目前为止还没有成功地解决这个问题。

2 个答案:

答案 0 :(得分:1)

我认为在您尝试实施之前,您需要清楚地了解您的要求。根据您的描述,我的理解是,对于结果,年份+种类+动物是结果项目的关键。您指定的组仅用于计算百分比。

首先根据年份+种类+动物

对项目进行分组
function groupItems(data){
   var resultMap = {};
   for(var index in data){
      var item = data[index];
      var key = item['year']+'###'+item['kind']+'###'+item['animal'];
      if(!resultMap[key]){
         //copy the item to a new object to make sure it does not update the origin data
         resultMap[key] = Object.assign({}, item); 
      }else{
         // sum them up
         resultMap[key]['total'] = resultMap[key]['total'] +item['total'];
      }
   }
   return Object.values(resultMap);
}

现在我们有了这些项目,您需要的是根据指定的组计算百分比。

function computePercentage(groupedItems, withGroups){
    //compute the total items per specified group
    var totalItemsPerGroup = {};
    for(var index in groupedItems){
        var item = groupedItems[index];
        var keyValues = getValuesFromObjectWithKeys(item, withGroups);
        var strKey = keyValues.join('###');
        if(totalItemsPerGroup[strKey] != undefined){
            totalItemsPerGroup[strKey] += item['total'];
        }else{
            totalItemsPerGroup[strKey] = item['total'];
        }
    }
    // compute percentage based on the total items per specified group calculated
    var result = [];
    for(var index in groupedItems){
        var item = groupedItems[index];
        var keyValues = getValuesFromObjectWithKeys(item, withGroups);
        var strKey = keyValues.join('###');
        var totalCount = totalItemsPerGroup[strKey];
        var percentage = totalCount == 0 ? 0 : item['total'] / totalCount;
        var resultItem = {};
        resultItem['year'] = item['year'];
        resultItem['kind'] = item['kind'];
        resultItem['animal'] = item['animal'];
        resultItem['count'] = item['total'];
        resultItem['pct'] = percentage;
        result.push(resultItem);
    }  
    return result;
}

// a helper function to get values based on specified keys
function getValuesFromObjectWithKeys(obj, keys){
   var result = [];
   for(var i in keys){
       if(obj[keys[i]]){
           result.push(obj[keys[i]]);
       }
   }
   return result;
}

所以要使用这些功能:

var grouppedItems = groupItems(data);
var yearPercentage = computePercentage(grouppedItems , ['year']);
console.log(JSON.stringify(yearPercentage));
var yearKindPercentage = computePercentage(grouppedItems , ['year', 'kind']);
console.log(JSON.stringify(yearKindPercentage ));
var kindAnimalPercentage = computePercentage(grouppedItems , ['kind', 'animal']);
console.log(JSON.stringify(kindAnimalPercentage ));

答案 1 :(得分:1)

我确信有更简洁的方法可以做到这一点,特别是如果您使用的是lodash-fpramda,但这样做(至少对于第一个结果集):

const computeBy = (primaryKey, secondary, data) => {

  const groupBy = (key, data) => data.reduce((acc, record) => {
    const value = record[key];
    acc[value] = acc[value] || [];
    acc[value].push(record);
    return acc;
  }, {});

  const groupedByPrimary = groupBy(primaryKey, data);

  const sum = records => records.reduce((acc, v) => acc + v.total, 0);

  return Object.keys(groupedByPrimary).reduce((acc, key) => {
    const group = groupedByPrimary[key];
    const groupTotalCount = sum(group);
    const groupedBySecondary = groupBy(secondary, group);
    const final = Object.values(groupedBySecondary).map(secondaryGroup => {
      const count = sum(secondaryGroup);
      const pct = count / groupTotalCount;
      const recordWithPercentage = Object.assign({}, secondaryGroup[0], { pct, count });
      delete recordWithPercentage.total;
      return recordWithPercentage;
    });
    return acc.concat(final);
  }, []);
}

const result = computeBy('year', 'kind', data).sort((a, b) => a.year < b.year);

工作小提琴:https://jsfiddle.net/frb6bowj/2/