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.
I am trying to do a pretty standard group by in javascript using sports data. I have the following array of objects:
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.
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.
Thanks!
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));
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With