I am trying to group data by multiple properties and sum their values.
Here is what I tried as per this question
I had a follow up to this question:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
const result = [...arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
return r.set(key, item);
}, new Map).values()];
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I wanted to make this more reusable with the numerical values. In this for example, I want the
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
part especially to be reusable.
I got the numerical value keys in an array: let gee = ['used', 'instances'];
I am not sure how to use it with Object.assign. I tried to do this:
const result = [...arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
// console.log(o);
const item = gee.forEach(v => o[v] += o[v]);
// const item = r.get(key) || Object.assign({}, o, {
// used: 0,
// instances: 0
// });
// item.used += o.used;
// item.instances += o.instances;
return r.set(key, item);
}, new Map).values()];
But this is not working. How can I use an array for this bit of code:
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
If the Map object has the key, loop through the totalKeys and increment the object in the accumulator with current object's data. If it is new key, add a copy of the object to the Map
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
Here's a snippet:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
function groupSum(array, totalKeys) {
const group = arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
return r;
}, new Map);
return Array.from(group.values())
}
console.log(
groupSum(arr, ['used', 'instances'])
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
You can make it even more dynamic by providing an array of keys to group by. Create the key using the values of the object separated by a |
const key = groupKeys.map(k => o[k]).join("|");
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
Here's a snippet:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
function groupSum(array, groupKeys, totalKeys) {
const group = arr.reduce((r, o) => {
const key = groupKeys.map(k => o[k]).join("|");
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
return r;
}, new Map);
return Array.from(group.values())
}
console.log(
groupSum(arr, ['shape', 'color'], ['used', 'instances'])
)
You could vastly simplify the dataset too by not using the combination of array.reduce() with a map()... and instead just build your new array by looping through all elements of the original array with array.forEach().
I added your use of the gee array as being a list of numeric fields you want to have added... to include making sure they exist on every object of the result array...whether or not they existed on each of the previous objects in arr.
const arr = [{
"shape": "square",
"color": "red",
"used": 1,
"instances": 1
}, {
"shape": "square",
"color": "red",
"used": 2,
"instances": 1
}, {
"shape": "circle",
"color": "blue",
"used": 0,
"instances": 0
}, {
"shape": "square",
"color": "blue",
"used": 4,
"instances": 4
}, {
"shape": "circle",
"color": "red",
"used": 1,
"instances": 1
}, {
"shape": "circle",
"color": "red",
"used": 1,
"instances": 0,
"testProp": 1
}, {
"shape": "square",
"color": "blue",
"used": 4,
"instances": 5
}, {
"shape": "square",
"color": "red",
"used": 2,
"instances": 1
}];
let gee = ['used', 'instances', 'testProp'];
let result = [];
arr.forEach((o) => {
// Setup TempSource since not all o may have all elements in gee
let tempSource = {};
gee.forEach((key) => {
if (o.hasOwnProperty(key)) {
tempSource[key] = o[key];
} else {
tempSource[key] = 0;
}
});
// Look to see if the result array already has an object with same shape/color
const matchingObject = result.find(element => {
let returnValue = true;
returnValue &= (element.shape == o.shape);
returnValue &= (element.color == o.color);
return returnValue;
});
if (matchingObject) {
// Matching Object already exists... so increment values
gee.forEach((key) => {
matchingObject[key] += tempSource[key];
});
} else {
// Matching Object missing, so merge newObject and insert
let newObj = {};
Object.assign(newObj, o, tempSource);
result.push(newObj);
}
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Maybe this would be a way to go:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}],
nums=["used","instances"]
function summationOn(ar,cnts){ // cnts: add up counts on these properties
const grp=Object.keys(ar[0]).filter(k=>cnts.indexOf(k)<0) // grp: group over these
return Object.values(ar.reduce((a,c,t)=>{
const k=grp.map(g=>c[g]).join("|");
if (a[k]) cnts.forEach(p=>a[k][p]+=c[p])
else a[k]={...c};
return a
},{}))
}
const res=summationOn(arr,nums);
console.log(res);
re-write
Similar to #adiga I now expect the "countable" properties to be given in the array cnts. With this array I collect all other properties of the first object of input array ar into array grp. These are the properties I will group over.
Related
I'm trying to create a JavaScript method which loops over 2 arrays and returns an array of the matched value.
My a1 parameter in the 'getMatchedArray' method is an array of strings and objects, while arr2 is always array of objects.
However, a2 parameter in the 'getMatchedArray' method is an array that can contain an object with value property or without value property as seen in the sample arrays used.
I'm very close to it but somehow not able to figure out, what is the mistake I'm making?
Is there a faster way using intersection to achieve this?
const arr1 = ["red", {
"code": "red",
"label": "test"
}, {
"code": "blue",
"label": "test1"
}, "white", "blue", {
"code": "red",
"label": "test2"
}];
const arr2 = [{
"code": "red",
"value": "test2"
}];
const arr3 = [{
"code": "blue"
}];
const arr4 = [{
"code": "red",
"value": "test3"
}]
function getMatchedArray(a1, a2) {
return a1.reduce((memo, opt) => {
const isOptionFound = a2.some(obj => {
if (obj.value) {
return obj.value === opt.label;
} else {
return !opt.code && opt === obj.code;
}
});
if (isOptionFound) {
memo.push(opt);
}
return memo;
}, []);
}
const result1 = getMatchedArray(arr1, arr2);
const result2 = getMatchedArray(arr1, arr3);
const result3 = getMatchedArray(arr1, arr4);
console.log(result1);
console.log(result2);
console.log(result3);
Expected output:
result1:
[{
"code": "red",
"label": "test2"
}]
result2: ["blue"]
result3: ["red"]
result1, result 2 are fine, but my result3 is incorrect.
Any help on this?
//Try this
function findMatchingValues(arr1, arr2) {
const hashTable = {};
const matchingValues = [];
// Populate hash table with values from arr1
for (let i = 0; i < arr1.length; i++) {
const val = arr1[i];
hashTable[val] = true;
}
// Check arr2 for matching values
for (let i = 0; i < arr2.length; i++) {
const val = arr2[i];
if (hashTable[val]) {
matchingValues.push(val);
}
}
return matchingValues;
}
You can also achieve this requirement by separating the string and object elements from an array and then applied filter on those arrays based on the passed 2nd parameter in the function by stringify the passed parameters in the function.
Live Demo :
const arr1 = ["red", {
"code": "red",
"label": "test"
}, {
"code": "blue",
"label": "test1"
}, "white", "blue", {
"code": "red",
"label": "test2"
}];
const arr2 = [{
"code": "red",
"value": "test2"
}];
const arr3 = [{
"code": "blue"
}];
const arr4 = [{
"code": "red",
"value": "test3"
}];
function getMatchedArray(a1, a2) {
let strA2 = JSON.stringify(a2);
const strArrayFromA1 = a1.filter(item => typeof item === 'string');
const objArrayFromA1 = a1.filter(item => typeof item === 'object');
const matchedObject = objArrayFromA1.filter(elem => {
strA2 = strA2.replaceAll('value', 'label');
return strA2.includes(JSON.stringify(elem));
});
const matchedString = strArrayFromA1.filter(elem => strA2.includes(elem));
return matchedObject.length ? matchedObject : matchedString.length ? matchedString : 'No match found.';
}
const result1 = getMatchedArray(arr1, arr2);
const result2 = getMatchedArray(arr1, arr3);
const result3 = getMatchedArray(arr1, arr4);
console.log(result1);
console.log(result2);
console.log(result3);
The issue with the third result is that the expected output is an array of objects with the matching values, but the current implementation is returning a single object. To fix this, you can modify the function to push the opt value to memo instead of the opt object when there is a match in the arr4.
Here is the modified function:
function getMatchedArray(a1, a2) {
return a1.reduce((memo, opt) => {
const isOptionFound = a2.some(obj => {
if (obj.value) {
return obj.value === opt.label;
} else {
return !opt.code && opt === obj.code;
}
});
if (isOptionFound) {
memo.push(opt.label || opt);
}
return memo;
}, []);
}
With this modification, the output for result3 will be ["red"], which is the expected result.
Regarding the second part of the question, there is a faster way to achieve this using the filter and includes array methods. Here is an example implementation:
function getMatchedArray(a1, a2) {
return a1.filter(opt => {
return a2.some(obj => {
if (obj.value) {
return obj.value === opt.label;
} else {
return opt.code && obj.code && opt.code === obj.code;
}
});
});
}
This implementation uses filter to create a new array with all the elements that match the condition, and includes to check if the a2 array includes the opt value.
I have arr array of objects, I need to pivot it with product,calorie and apply (grouping & sum) on remaining parameters.
And then require data in single object.
I tried below code, it works fine but I divided code in 3 parts.
Could I have better code than this or it is ok.
var arr = [{
"product": "Jam",
"calorie": 2000,
"A": 300,
"B": 500,
"type": "Daily"
},
{
"product": "Sugar",
"calorie": 1000,
"A": 100,
"B": 200,
"type": "Daily"
}
]
var a1 = {}
var a2 = {}
//Step-1 Pivot
for (let i = 0; i < arr.length; i++) {
a1[arr[i]['product']] = arr[i]['calorie'];
}
//Step-2 Group and sum
a2 = groupAndSum(arr, ['type'], ['A', 'B'])[0];
//Step-3 merging.
console.log({ ...a1,
...a2
})
//General grouping and summing function that accepts an
//#Array:Array of objects
//#groupKeys: An array of keys to group by,
//#sumKeys - An array of keys to sum.
function groupAndSum(arr, groupKeys, sumKeys) {
return Object.values(
arr.reduce((acc, curr) => {
const group = groupKeys.map(k => curr[k]).join('-');
acc[group] = acc[group] || Object.fromEntries(groupKeys.map(k => [k, curr[k]]).concat(sumKeys.map(k => [k, 0])));
sumKeys.forEach(k => acc[group][k] += curr[k]);
return acc;
}, {})
);
}
Here a single function which takes 3 params:
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
arr
containing the data
sum_vals
array with all props you want do be summed
pivot_vals
nested array with the props which should be linked
I wans't sure what to do with the type, since it is a string it can`t be summed. Did you want to count the amount of types ?
let arr = [
{
product: "Jam",
calorie: 2000,
A: 300,
B: 500,
type: "Daily",
},
{
product: "Sugar",
calorie: 1000,
A: 100,
B: 200,
type: "Daily",
},
];
let sum_vals = ["A","B"]
let pivot_vals = [["product", "calorie"]];
const func = (arr, pivot_vals, sum_vals) => {
return arr.reduce((a, v) => {
pivot_vals.forEach((pivot) => {
a[v[pivot[0]]] = v[pivot[1]];
});
sum_vals.forEach((key) => {
if (!a[key]) a[key] = 0;
a[key] += v[key];
});
return a;
},{});
};
console.log(func(arr, pivot_vals, sum_vals));
In my project, I need to classify an array and convert it to another type of array.
The difficulty I encountered was that there was no way to use concise and efficient execution. The following are my input and output:
const input = [{
"type": 1,
"color": "Red(268)"
},
{
"type": 1,
"color": "Blue(583)"
},
{
"type": 2,
"color": "Blue(185)"
},
{
"type": 4,
"color": "Red(326)"
},
{
"type": 4,
"color": "Blue(967)"
},
{
"type": 5,
"color": "Red(863)"
}
]
const output = [
"Type 1: Red(268), Blue(583)",
"Type 2: Blue(185)",
"Type 4: Red(326), Blue(967)",
"Type 5: Red(863)"
]
The following is my method. I use the set() to find out the number of types, and then use for loop to convert it into a string and push into the array, but it cannot be executed continuously, so my function cannot get the correct result, and it is not efficient.
this.ty = 1;
this.result = [];
const set = new Set();
const length = input.filter(item => !set.has(item.type) ? set.add(item.type) : false).length + 1;
for (let i = 1; i < length; i++) {
const temp = input.filter(x => {
return x.type === ty;
})
if (temp.length < 2) {
this.result.push(`Type ${ty}: ${temp[0].color}`);
} else {
this.result.push(`Type ${ty}: ${temp[0].color}, ${temp[1].color}`);
}
this.ty = i + 1;
}
This problem has troubled me for a long time. Can someone provide an easier way to convert this array? Thank you for your help.
const input = [{
"type": 1,
"color": "Red(268)"
},
{
"type": 1,
"color": "Blue(583)"
},
{
"type": 2,
"color": "Blue(185)"
},
{
"type": 4,
"color": "Red(326)"
},
{
"type": 4,
"color": "Blue(967)"
},
{
"type": 5,
"color": "Red(863)"
}
]
console.log('input', input);
this.ty = 1;
this.result = [];
const set = new Set();
const length = input.filter(item => !set.has(item.type) ? set.add(item.type) : false).length + 1;
for (let i = 1; i < length; i++) {
const temp = input.filter(x => {
return x.type === ty;
})
if (temp.length < 2) {
this.result.push(`Type ${ty}: ${temp[0].color}`);
} else {
this.result.push(`Type ${ty}: ${temp[0].color}, ${temp[1].color}`);
}
this.ty = i + 1;
}
console.log('result', this.result);
// output
/* const output = [
"Type 1: Red(268), Blue(583)",
"Type 2: Blue(185)",
"Type 4: Red(326), Blue(967)",
"Type 5: Red(863)"
] */
You can use the Array.reduce() function to iterate your array and construct a new object.
const input = [{
"type": 1,
"color": "Red(268)"
},
{
"type": 1,
"color": "Blue(583)"
},
{
"type": 2,
"color": "Blue(185)"
},
{
"type": 4,
"color": "Red(326)"
},
{
"type": 4,
"color": "Blue(967)"
},
{
"type": 5,
"color": "Red(863)"
}
];
const mappedInput = input.reduce((grouped, {
type,
color
}) => {
if (!grouped.hasOwnProperty(type)) {
grouped[type] = `Type ${type}: ${color}`;
} else {
grouped[type] += `, ${color}`;
}
return grouped;
}, {});
console.log(Object.values(mappedInput));
We use an object to provide efficient key lookup and at the end, retrieve just the array of strings that we need.
You could reduce over the array to create an object that uses the type as a key and an array as a value, pushing new instances into the array with each iteration.
Then map over the Object.entries to produce a new array of strings.
const input = [{"type":1,"color":"Red(268)"},{"type":1,"color":"Blue(583)"},{"type":2,"color":"Blue(185)"},{"type":4,"color":"Red(326)"},{"type":4,"color":"Blue(967)"},{"type":5,"color":"Red(863)"}];
const out = input.reduce((acc, c) => {
const [ key, value ] = Object.values(c);
acc[key] = acc[key] || [];
acc[key].push(value);
return acc;
}, {});
const result = Object.entries(out).map(([key, value]) => {
return `Type ${key}: ${value.join(', ')}`
});
console.log(result);
Here's a simple, functional solution:
// Get list of unique input types
const types = Array.from(new Set(input.map(x => x.type)));
// Map over unique types, filter for matching inputs, yield all colors
const output = types.map(type => `Type ${type}: ${input.filter(x => x.type == type).map(x => x.color).join(', ')}`);
This is the Dynamic Solution for your problem.
let output = input.map(d => {
let k = Object.keys(d);
let v = Object.values(d)
let text = '';
for (var i in k) {
text += `${k[i]}: ${v[i]}, `
}
text = text.substring(0, text.length - 1);
return text })
My existing array object are
//existing object
var existing = [
{
'ProviderCode':'aa',
'msg':'....',
},{
'ProviderCode':'bb',
'msg':'....',
},{
'ProviderCode':'cc',
'msg':'....',
},{
'ProviderCode':'dd',
'msg':'....',
},{
'ProviderCode':'ee',
'msg':'....',
}];
new object I'm comparing to
var new = [
{
'ProviderCode':'bb',
'msg':'....',
},{
'ProviderCode':'cc',
'msg':'....',
},{
'ProviderCode':'ee',
'msg':'....',
},{
'ProviderCode':'ff',
'msg':'....',
},{
'ProviderCode':'gg',
'msg':'....',
}];
I would like to generate same, remove and add array based on the two array objects, I can get the same array object but not the remove and add object from the objects.
var same = []; //bb, cc, ee //these will be the match
var remove = []; //aa , dd //will be remove from existing
var add = []; //ff, gg //will be consider as add
//I can get the same using below:
e.forEach(function(ev,ei,ea){
n.forEach(function(nv,ni,na){
if( ev.ProviderCode === nv.ProviderCode ){
s.push({ProviderCode:ev.ProviderCode,msg:"Same, do nothing"});
}
});
});
/* //output:
[{
"ProviderCode": "bb",
"msg": "Same, do nothing"
}, {
"ProviderCode": "cc",
"msg": "Same, do nothing"
}, {
"ProviderCode": "ee",
"msg": "Same, do nothing"
}]
*/
//but how do I get remove and add array object?
//remove will be:
/* //output:
[{
"ProviderCode": "aa",
"msg": "removed"
}, {
"ProviderCode": "dd",
"msg": "removed"
}]
*/
//add will be:
/* //output:
[{
"ProviderCode": "ff",
"msg": "added"
}, {
"ProviderCode": "gg",
"msg": "added"
}]
*/
You can use Array.prototype.filter & Array.prototype.find for this:
let existing = [{ProviderCode:'aa'},{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'dd'},{ProviderCode:'ee'}];
let newData = [{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'ee'},{ProviderCode:'ff'},{ProviderCode:'gg'}];
let added = newData.filter(d => !existing.find(e => d.ProviderCode === e.ProviderCode));
console.log("ADDED:", added);
let removed = existing.filter(d => !newData.find(e => d.ProviderCode === e.ProviderCode));
console.log("REMOVED:", added);
let same = newData.filter(d => existing.find(e => d.ProviderCode === e.ProviderCode));
console.log("SAME:", same);
With a library like lodash this is a bit easier:
let existing = [{ProviderCode:'aa'},{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'dd'},{ProviderCode:'ee'}];
let newData = [{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'ee'},{ProviderCode:'ff'},{ProviderCode:'gg'}];
console.log("ADDED:" , _.differenceBy(newData, existing, 'ProviderCode'));
console.log("REMOVED:", _.differenceBy(existing, newData, 'ProviderCode'));
console.log("SAME:" , _.intersectionBy(newData, existing, 'ProviderCode'));
<script src="https://unpkg.com/lodash#4.17.15/lodash.min.js"></script>
You can use this little "library" that provides set operations for JS Map objects:
function mapUnion(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
m.set(k, v);
for (let [k, v] of m2)
m.set(k, v);
return m;
}
function mapIntersection(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
if (m2.has(k))
m.set(k, v);
return m;
}
function mapDifference(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
if (!m2.has(k))
m.set(k, v);
return m;
}
Having this, you can convert both your arrays to Maps:
let m1 = new Map(oldArray.map(x => [x.ProviderCode, x]))
let m2 = new Map(newArray.map(x => [x.ProviderCode, x]))
and do whatever you want with these, for example,
console.log(mapIntersection(m1, m2)) // bb=>..., cc=>..., ee=>...
console.log(mapDifference(m1, m2)) // aa=>..., dd=>...
If you need arrays as results:
commonObjects = Array.from(mapIntersection(m1, m2).values())
You could take some sets and get the wanted items for each change.
const
providerCode = ({ ProviderCode }) => ProviderCode;
var existingData = [{ ProviderCode: 'aa' }, { ProviderCode: 'bb' }, { ProviderCode: 'cc' }, { ProviderCode:'dd' }, { ProviderCode: 'ee' }],
newData = [{ ProviderCode: 'bb' }, { ProviderCode: 'cc' }, { ProviderCode: 'ee' }, { ProviderCode: 'ff' }, { ProviderCode: 'gg' }],
existingSet = new Set(existingData.map(providerCode)),
newSet = new Set(newData.map(providerCode)),
same = [...existingSet].filter(Set.prototype.has, newSet),
add = [...newSet].filter(v => !existingSet.has(v)),
remove = [...existingSet].filter(v => !newSet.has(v));
console.log(...same);
console.log(...add);
console.log(...remove);
Here are some data:
data = [
{"Age":26,"Level":8},
{"Age":37,"Level":9},
{"Age":null,"Level":15},
{"Age":null,"Level":45}
];
from which I'm trying to calculate average for their properties:
var avg = {};
var rows = data.length;
data.forEach(obj => {
Object.keys(obj).forEach(k => {
if(obj[k] != null){
avg[k] = (avg[k] || 0) + obj[k] / rows;
}
});
});
return avg;
but the problem is in items that has properties with null values, where I'm trying to exclude null values from the calculation, and if you take a look at the the codepen there is Age: 15.75 instead of 31.5
because length of the data is always 4 (and should be 2 since 2 of them are null). How would be the best way to get the length to not be including the nulls?
You can have an object with nested object which has two properties value and count
const data = [
{"Age":26,"Level":8},
{"Age":37,"Level":9},
{"Age":null,"Level":15},
{"Age":null,"Level":45}
];
let avg = {}
data.forEach(x => {
for(let k in x){
if(!avg[k]){
avg[k] = {value:0,count:0};
}
if(x[k] !== null){
avg[k].value += x[k]
avg[k].count++;
}
}
})
avg = Object.fromEntries(Object.entries(avg).map(([k,v]) => ([k,v.value/v.count])))
console.log(avg)
let data = [
{"Age": 26, "Level": 8},
{"Age": 37, "Level": 9},
{"Age": null, "Level": 15},
{"Age": null, "Level": 45}
];
let averages = data.reduce((values, o) => {
Object.entries(o).forEach(([k, v]) => {
if (v !== null)
values[k] = (values[k] || []).concat(v);
});
return values;
}, {});
Object.entries(averages).forEach(([k, vs]) =>
averages[k] = vs.reduce((a, b) => a + b) / vs.length);
console.log(averages);
You can use a simple for...of and for...in loop to get the sum and count for each non-null item. You can add a get property to automatically calculate the average based on the sum and the count properties in the counter
const data = [{Age:26,Level:8},{Age:37,Level:9},{Age:null,Level:15},{Age:null,Level:45}];
let counter = {}
for (const item of data) {
for (const key in item) {
if (item[key] !== null) {
counter[key] = counter[key] || {
sum: 0,
count: 0,
get average() { return this.sum/this.count }
};
counter[key].sum += item[key]
counter[key].count++
}
}
}
console.log(counter)
I would do something like this: (not tested yet)
var data = [
{"Age":26,"Level":8},
{"Age":37,"Level":9},
{"Age":null,"Level":15},
{"Age":null,"Level":45}
];
var sum = { "Age": 0, "Level": 0 };
var average = { "Age": 0, "Level": 0 };
var sumCount = { "Age": 0, "Level": 0 };
// sum up all objects
for (var i = 0; i < data.length; i++) {
Object.keys(data[i]).forEach(function (key) {
if (data[i][key] == null || data[i][key] == undefined)
return;
sumCount[key]++;
sum[key] = sum[key] + data[i][key];
});
}
// make average object
Object.keys(average).forEach(function (key) {
average[key] = sum[key] / sumCount[key];
});
You could store the sum and count for every key independently.
var data = [{ "Age": 26, "Level": 8 }, { "Age": 37, "Level": 9 }, { "Age": null, "Level": 15 }, { "Age": null, "Level": 45 }],
avg = {},
temp = {};
data.forEach(obj => Object.keys(obj).forEach(k => {
if (obj[k] === null) return;
temp[k] = temp[k] || { sum: 0, count: 0 };
temp[k].sum += obj[k];
temp[k].count++;
avg[k] = temp[k].sum / temp[k].count;
}));
console.log(avg);
console.log(temp);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could also do it in a relatively concise manner with just Array.reduce and inside of it just iterate over the Object.keys:
var data = [ {"Age":26,"Level":8}, {"Age":37,"Level":9}, {"Age":null,"Level":15}, {"Age":null,"Level":45} ];
let result = data.reduce((r, c) => (Object.keys(c).forEach(k => {
r[k] = (r[k] || { Sum: 0, Count: 0, Avg: 0 })
r[k].Sum += c[k] || 0
r[k].Count += c[k] ? 1 : 0
r[k].Avg = r[k].Sum / r[k].Count
}), r), {})
console.log(result)