假设我在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}]
或者如果我按year
和kind
指定小组总数,我会想要以下内容:
[{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的嵌套函数,但到目前为止还没有成功地解决这个问题。
答案 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-fp
或ramda
,但这样做(至少对于第一个结果集):
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);