在Javascript数组中,按属性分组并计算平均值/均值

7

虽然我很有信心该解决方案必定存在,但我还是在其他stackoverflow帖子中苦苦寻找。如果真的有,请指引我正确的方向。

我正在尝试使用体育数据在Javascript中进行标准的分组操作。我的数组对象如下:

 const myData = [
    {team: "GSW", pts: 120, ast: 18, reb: 11},
    {team: "GSW", pts: 125, ast: 28, reb: 18},
    {team: "GSW", pts: 110, ast: 35, reb: 47},
    {team: "HOU", pts: 100, ast: 17, reb: 43},
    {team: "HOU", pts: 102, ast: 14, reb: 32},
    {team: "SAS", pts: 127, ast: 21, reb: 25},
    {team: "SAS", pts: 135, ast: 25, reb: 37},
    {team: "SAS", pts: 142, ast: 18, reb: 27}
 ]

我的数据中每一行都对应着一个具体的篮球比赛结果。简单来说,我想要按照数据进行分组,并对分组后的数据应用平均函数。我期望得到的结果是:

const groupedData = [
    {team: "GSW", pts: 118.3, ast: 27.0, reb: 25.3},
    {team: "HOU", pts: 101, ast: 15.5, reb: 37.5},
    {team: "SAS", pts: 134.7, ast: 21.3, reb: 29.7} 
] 

我希望在这里使用原生JavaScript中的reduce()...根据我对reduce的了解,这似乎是最好的方法。我目前正在处理这个问题,如果在其他人发布答案之前我能让它起作用,我会发布结果。

编辑:我的实际数据有大约30个键。我希望找到一个简单的解决方案,只需要让我指定要按团队列分组,然后假设其余的都是分组,或者(b)传递一个统计列的数组(pts,asts等),而不是为每个统计项创建一行。

谢谢!


3
为了能够计算点数和其他数据的平均值,你需要分步进行,因为你需要有所有的数据。 - A. Llorente
1
当你说所有数据需要“有序”时,你的意思是什么? - Canovice
2
所以,首先你需要将所有数据分组,然后再逐一计算平均值。 - A. Llorente
1
从最初发布的几个答案中,我现在明白你的意思了。 - Canovice
显示剩余2条评论
7个回答

12

一种解决方法是同时使用 reducemap

const myData = [
    {team: "GSW", pts: 120, ast: 18, reb: 11},
    {team: "GSW", pts: 125, ast: 28, reb: 18},
    {team: "GSW", pts: 110, ast: 35, reb: 47},
    {team: "HOU", pts: 100, ast: 17, reb: 43},
    {team: "HOU", pts: 102, ast: 14, reb: 32},
    {team: "SAS", pts: 127, ast: 21, reb: 25},
    {team: "SAS", pts: 135, ast: 25, reb: 37},
    {team: "SAS", pts: 142, ast: 18, reb: 27}
 ]
 
 // Calculate the sums and group data (while tracking count)
 const reduced = myData.reduce(function(m, d){
    if(!m[d.team]){
      m[d.team] = {...d, count: 1};
      return m;
    }
    m[d.team].pts += d.pts;
    m[d.team].ast += d.ast;
    m[d.team].reb += d.reb;
    m[d.team].count += 1;
    return m;
 },{});
 
 // Create new array from grouped data and compute the average
 const result = Object.keys(reduced).map(function(k){
     const item  = reduced[k];
     return {
         team: item.team,
         ast: item.ast/item.count,
         pts: item.pts/item.count,
         reb: item.reb/item.count
     }
 })
 
 console.log(JSON.stringify(result,null,4));

编辑:刚看到你对问题的更新。如果你可以编程地提供白名单(计算数组中的键)或黑名单(忽略数组中的键),那么你就可以放弃每个键的每一行。

const myData = [
    {team: "GSW", pts: 120, ast: 18, reb: 11},
    {team: "GSW", pts: 125, ast: 28, reb: 18},
    {team: "GSW", pts: 110, ast: 35, reb: 47},
    {team: "HOU", pts: 100, ast: 17, reb: 43},
    {team: "HOU", pts: 102, ast: 14, reb: 32},
    {team: "SAS", pts: 127, ast: 21, reb: 25},
    {team: "SAS", pts: 135, ast: 25, reb: 37},
    {team: "SAS", pts: 142, ast: 18, reb: 27}
 ]
 
/**
 * Function which accepts a data array and a list of whitelisted
 * keys to find the average of each key after grouping
 */
function getGroupedData(data, whitelist) {
  // Calculate the sums and group data (while tracking count)
  const reduced = data.reduce(function(m, d) {
    if (!m[d.team]) {
      m[d.team] = { ...d,
        count: 1
      };
      return m;
    }
    whitelist.forEach(function(key) {
      m[d.team][key] += d[key];
    });
    m[d.team].count += 1;
    return m;
  }, {});

  // Create new array from grouped data and compute the average
  return Object.keys(reduced).map(function(k) {
    const item = reduced[k];
    const itemAverage = whitelist.reduce(function(m, key) {
      m[key] = item[key] / item.count;
      return m;
    }, {})
    return {
      ...item, // Preserve any non white-listed keys
      ...itemAverage // Add computed averege for whitelisted keys
    }
  })
}


console.log(JSON.stringify(getGroupedData(myData, ['pts', 'ast', 'reb']), null, 4));


如果更新不困难,可以假设两者都可用。 - Canovice
更新了我的回答...与我最初想象的差别不是很大(平均操作使得必须详细说明)。 - Chirag Ravindra

1

const myData = [
    {team: "GSW", pts: 120, ast: 18, reb: 11},
    {team: "GSW", pts: 125, ast: 28, reb: 18},
    {team: "GSW", pts: 110, ast: 35, reb: 47},
    {team: "HOU", pts: 100, ast: 17, reb: 43},
    {team: "HOU", pts: 102, ast: 14, reb: 32},
    {team: "SAS", pts: 127, ast: 21, reb: 25},
    {team: "SAS", pts: 135, ast: 25, reb: 37},
    {team: "SAS", pts: 142, ast: 18, reb: 27}
 ]

const groubElement = myData.reduce((obj, val) => {
    if (obj[val.team]) {
        obj[val.team].pts = obj[val.team].pts + val.pts;
        obj[val.team].ast = obj[val.team].pts + val.ast;
        obj[val.team].reb = obj[val.team].pts + val.reb;
        obj[val.team].counter = obj[val.team].counter + 1;
    } else {
        obj[val.team] = val;
        obj[val.team].counter = 1;
    }
    return obj;

}, {});



const groupElementWithMean = Object.values(groubElement).map(({
    counter,
    ...element
}) => {
    element.pts = (element.pts / counter).toFixed(1);
    element.ast = (element.ast / counter).toFixed(1);
    element.reb = (element.reb / counter).toFixed(1);
    return element;
});

console.log(groupElementWithMean);


1
你可以使用 reduceObject.keys 以及 Array.prototype.map 来实现这个功能:

const myData = [
    { team: "GSW", pts: 120, ast: 18, reb: 11 },
    { team: "GSW", pts: 125, ast: 28, reb: 18 },
    { team: "GSW", pts: 110, ast: 35, reb: 47 },
    { team: "HOU", pts: 100, ast: 17, reb: 43 },
    { team: "HOU", pts: 102, ast: 14, reb: 32 },
    { team: "SAS", pts: 127, ast: 21, reb: 25 },
    { team: "SAS", pts: 135, ast: 25, reb: 37 },
    { team: "SAS", pts: 142, ast: 18, reb: 27 }
]

let grpData = myData.reduce((acc, cv) => {
    if (!acc[cv.team]) {
        acc[cv.team] = {};
        acc[cv.team].team = cv.team;
        acc[cv.team].count = acc[cv.team].pts = acc[cv.team].ast = acc[cv.team].reb = 0
    }
    acc[cv.team].count++;
    acc[cv.team].pts += cv.pts;
    acc[cv.team].ast += cv.ast;
    acc[cv.team].reb += cv.reb;
    return acc;
}, {});
grpData = Object.keys(grpData).map(key => {
    let { team, reb, ast, pts, count } = grpData[key];
    return {
        team, reb: reb / count, ast: ast / count, pts: pts / count
    };
})
console.log(grpData);


1
使用statsFields数组,并循环遍历以创建总计,然后获取平均值。

const myData = [
    {team: "GSW", pts: 120, ast: 18, reb: 11},
    {team: "GSW", pts: 125, ast: 28, reb: 18},
    {team: "GSW", pts: 110, ast: 35, reb: 47},
    {team: "HOU", pts: 100, ast: 17, reb: 43},
    {team: "HOU", pts: 102, ast: 14, reb: 32},
    {team: "SAS", pts: 127, ast: 21, reb: 25},
    {team: "SAS", pts: 135, ast: 25, reb: 37},
    {team: "SAS", pts: 142, ast: 18, reb: 27}
 ]
 
 const statsFields = ['pts','ast','reb'];
 
 const teamsObject = myData.reduce((a,{team,...stats})=>{
   a[team] = a[team] || {team, games:0};
   a[team].games++
   statsFields.forEach(k=> a[team][k] = (a[team][k] || 0) + stats[k]);
   return a;
 },{});
 
 const res = Object.values(teamsObject).map(({games,...team})=>{
    // average for each field total/games
    statsFields.forEach(k=> team[k] = team[k]/games);    
    return team;
 })
 
 console.log(JSON.stringify(res))


1

可以按照以下方式简单完成。

注意:使用JSON.parse和stringify进行深浅拷贝数据。否则,原始数组将被修改。如果可以修改原始数组,则不需要此操作。

const data = [
    {team: "GSW", pts: 120, ast: 18, reb: 11},
    {team: "GSW", pts: 125, ast: 28, reb: 18},
    {team: "GSW", pts: 110, ast: 35, reb: 47},
    {team: "HOU", pts: 100, ast: 17, reb: 43},
    {team: "HOU", pts: 102, ast: 14, reb: 32},
    {team: "SAS", pts: 127, ast: 21, reb: 25},
    {team: "SAS", pts: 135, ast: 25, reb: 37},
    {team: "SAS", pts: 142, ast: 18, reb: 27}
 ];

function groupData(mydata,keys)
{
    var accresult = mydata.reduce(function(acc, value){
      var arr = acc.filter(function(obj){return obj.team==value.team});
      arr.length ? (item=arr[0] , keys.forEach(function(key){ item[key]+=value[key]; })) : acc.push(value);
      return acc;
  },[]);

  var result = accresult.map(function(val){
      var l = mydata.filter(function(obj){return obj.team==val.team}).length;
      keys.forEach(function(key){ val[key]=(val[key]/l).toFixed(2); })
      return val;
  });
  return result;
}

console.log(groupData(JSON.parse(JSON.stringify(data.slice(0))),['pts','ast']));
console.log(groupData(JSON.parse(JSON.stringify(data.slice(0))),['pts','ast','reb']));
console.log(groupData(JSON.parse(JSON.stringify(data.slice(0))),['pts']));


@Canovice 希望这能帮到你。 - Vignesh Raja

0

您可以采用动态方法,使用Map来收集未知键并生成所有项。

function groupBy(array, key) {
    return Array.from(
        array.reduce((m, o) => {
            var temp = m.get(o[key]);
            if (!temp) {
                m.set(o[key], temp = {});
            }
            Object.entries(o).forEach(([k, v]) => {
                if (k === key) {
                    return;
                }
                temp[k] = temp[k]  || { sum: 0, count: 0 };
                temp[k].sum += v;
                temp[k].count++;
            });
            return m;
        }, new Map),
        ([k, v]) => Object.assign({ [key]: k }, ...Object.entries(v).map(([l, { sum, count }]) => ({ [l]: +(sum / count).toFixed(1) })))
    );
}

const myData = [{ team: "GSW", pts: 120, ast: 18, reb: 11 }, { team: "GSW", pts: 125, ast: 28, reb: 18 }, { team: "GSW", pts: 110, ast: 35, reb: 47 }, { team: "HOU", pts: 100, ast: 17, reb: 43 }, { team: "HOU", pts: 102, ast: 14, reb: 32 }, { team: "SAS", pts: 127, ast: 21, reb: 25 }, { team: "SAS", pts: 135, ast: 25, reb: 37 }, { team: "SAS", pts: 142, ast: 18, reb: 27 }];

console.log(groupBy(myData, 'team'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

使用 rest 属性(babel: true)。

function groupBy(array, key) {
    return Array.from(
        array.reduce((m, { [key]: k, ...rest}) => {
            var temp = m.get(k);
            if (!temp) {
                m.set(k, temp = {});
            }
            Object.entries(rest).forEach(([l, v]) => {
                temp[l] = temp[l]  || { sum: 0, count: 0 };
                temp[l].sum += v;
                temp[l].count++;
            });
            return m;
        }, new Map),
        ([k, v]) => Object.assign({ [key]: k }, ...Object.entries(v).map(([l, { sum, count }]) => ({ [l]: +(sum / count).toFixed(1) })))
    );
}

const myData = [{ team: "GSW", pts: 120, ast: 18, reb: 11 }, { team: "GSW", pts: 125, ast: 28, reb: 18 }, { team: "GSW", pts: 110, ast: 35, reb: 47 }, { team: "HOU", pts: 100, ast: 17, reb: 43 }, { team: "HOU", pts: 102, ast: 14, reb: 32 }, { team: "SAS", pts: 127, ast: 21, reb: 25 }, { team: "SAS", pts: 135, ast: 25, reb: 37 }, { team: "SAS", pts: 142, ast: 18, reb: 27 }];

console.log(groupBy(myData, 'team'));
.as-console-wrapper { max-height: 100% !important; top: 0; }


返回总数而不是平均数。 - charlietfl

0

const myData = [
  { team: "GSW", pts: 120 },
  { team: "HOU", pts: 100 },
  { team: "GSW", pts: 110 },
  { team: "SAS", pts: 135 },
  { team: "HOU", pts: 102 },
  { team: "SAS", pts: 127 },
  { team: "SAS", pts: 142 },
  { team: "GSW", pts: 125 }
];

var result = myData.reduce(function (a, b) {
  var exist = -1;
  //some breaks the loop once it gets the true
  a.some((x, y) => {
    if (x.team == b.team) {
      //assigning index of existing object in array
      exist = y;
      return true;
    } else {
      return false;
    }
  });
  if (exist == -1) {
    a.push({ team: b.team, pts: b.pts, count: 1 });
  } else {
    a[exist].count += 1;
    a[exist].pts += b.pts;
  }
  return a;
}, []).map(t => {return {team: t.team, avg: t.pts/t.count, count:t.count}});


console.log(result);


2
请不要仅仅发布代码作为答案,还要提供解释您的代码是如何解决问题的。带有解释的答案通常更有帮助和更高质量,并且更有可能吸引赞同。 - Boken

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接