javascript 分组并计算Javascript数组中属性的平均值/平均值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/51040651/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 09:23:59  来源:igfitidea点击:

Group by and calculate mean / average of properties in a Javascript array

javascriptgroup-by

提问by Canovice

I struggled finding the solution I was looking for in other stackoverflow posts, even though I strongly feel as if it must exist. If it does, please do forward me in the right direction.

我努力在其他 stackoverflow 帖子中找到我正在寻找的解决方案,尽管我强烈觉得它一定存在。如果是这样,请不要让我朝着正确的方向前进。

I am trying to do a pretty standard group by in javascript using sports data. I have the following array of objects:

我正在尝试使用体育数据在 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}
 ]

Each row in my data corresponds to the results of a specific basketball game. Simply put, I would like to group by the data, and apply a mean/average function to the grouped data. The results I would expect are:

我的数据中的每一行都对应于特定篮球比赛的结果。简单地说,我想按数据分组,并对分组数据应用均值/平均函数。我期望的结果是:

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} 
] 

I would prefer to use vanilla javascript with reduce() here... given what I know about reduce, it seems like the best way. I am currently working on this and will post if i can get it to work before someone else posts the answer.

我更喜欢在这里使用带有 reduce() 的 vanilla javascript...鉴于我对 reduce 的了解,这似乎是最好的方法。我目前正在研究这个,如果我能在其他人发布答案之前让它工作,我会发布。

EDIT: My actual data has ~30 keys. I am hoping to find a solution that simply asks me to either (a) specify only the team column to be grouped by, and assume it groups the rest, or (b) pass an array of stat columns (pts, asts, etc.) rather than creating a line for each stat.

编辑:我的实际数据有 ~30 个键。我希望找到一个解决方案,它只要求我 (a) 仅指定要分组的团队列,并假设它将其余列分组,或者 (b) 传递一组 stat 列(pts、asts 等)。 ) 而不是为每个统计数据创建一行。

Thanks!

谢谢!

回答by Chirag Ravindra

One way to do this is to use reduceand mapin conjunction.

一种方法是使用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));

EDIT: Just saw your update to the question. You can do away with each line for each key if you can either whitelist (provide an array of keys to compute) or blacklist (provide an array of keys to ignore) keys to do that programmatically.

编辑:刚刚看到您对问题的更新。如果您可以白名单(提供要计算的键数组)或黑名单(提供要忽略的键数组)键以编程方式执行此操作,则可以取消每个键的每一行。

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));

回答by Nishant Dixit

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);

回答by vibhor1997a

You can do this by using reducewith Object.keysand Array.prototype.mapas follows:-

你可以通过使用reducewithObject.keysArray.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);

回答by Nina Scholz

You could take a dynamic approach by using a Mapand generate all items after collecting the unknown keys.

您可以通过使用 a 采取动态方法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; }

With rest properties (babel: true).

具有其余属性 ( 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; }

回答by charlietfl

Using array of statsFieldsand loop over those to create totals and later to get averages

使用数组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))

回答by Vignesh Raja

It can be simply done as follows.

它可以简单地完成如下。

Note: Used JSON.parse and stringify to deep shallow copy the data. Or else the original array gets modified. Its not needed if the original array can be modified.

注意:使用 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']));