【问题标题】:Group by and calculate mean / average of properties in a Javascript array分组并计算 Javascript 数组中属性的平均值/平均值
【发布时间】:2018-06-26 10:29:06
【问题描述】:

我很难找到我在其他 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} 
] 

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

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

谢谢!

【问题讨论】:

  • 您需要分步进行,因为您需要拥有所有数据才能平均点数和其余数据
  • 你说所有数据都需要“有序”是什么意思
  • 为了“为了煮水,你需要先把它加热”。所以首先你需要对所有数据进行分组,然后你需要通过它来计算平均值。
  • 从发布的前几个答案中,我明白你现在的意思了

标签: javascript group-by


【解决方案1】:

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

【讨论】:

  • 如果不难更新,请随意假设两者都可用
  • 更新了我的答案.. 并没有我最初想象的那么大(平均操作使得有必要详细地进行操作)
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    您可以通过将reduceObject.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);

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        可以简单地做如下。

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

        【讨论】:

        • @Canovic 希望这对您有所帮助。
        【解决方案6】:

        您可以通过使用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; }

        带有其他属性 (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; }

        【讨论】:

        • 返回的是总和而不是平均值
        【解决方案7】:

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

        【讨论】:

        • 请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助、质量更好,并且更有可能吸引投票
        猜你喜欢
        • 1970-01-01
        • 2013-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-30
        相关资源
        最近更新 更多