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

后端 未结 7 683
臣服心动
臣服心动 2021-01-18 14:22

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 rig

相关标签:
7条回答
  • 2021-01-18 14:58

    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.

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

    0 讨论(0)
  • 2021-01-18 14:59

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

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

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

    0 讨论(0)
  • 2021-01-18 15:02

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

    0 讨论(0)
  • 2021-01-18 15:06

    You can do this by using reduce with Object.keys and Array.prototype.map as follows:-

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

    0 讨论(0)
  • 2021-01-18 15:06

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

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

    0 讨论(0)
  • 2021-01-18 15:14

    One way to do this is to use reduce and map in conjunction.

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

    0 讨论(0)
提交回复
热议问题