I want to use month as the column, location as the row header and then the center item is as value. but unable to make it.
I want to make a matrix form table, like below:
my Json is as below:
//Output is as below....{ "QATAR": 464785, "UAE": 223428, "SAUDI ARABIA": 355212 }
$scope.resArray = [{
"Month": "January-2016",
"Year": "2016",
"Value": 26000,
"Location": "QATAR"
},
{
"Month": "January-2016",
"Year": "2016",
"Value": 0,
"Location": "QATAR"
},
{
"Month": "January-2016",
"Year": "2016",
"Value": 8700,
"Location": "UAE"
},
{
"Month": "January-2016",
"Year": "2016",
"Value": 311912,
"Location": "SAUDI ARABIA"
},{"Month": "January-2016","Year": "2016","Value": 15300,"Location":SAUDI ARABIA"},{"Month": "January-2016","Year": "2016","Value": 3000,"Location": "QATAR"},{"Month": "January-2016","Year": "2016","Value": 2500,"Location": "QATAR"},{"Month": "January-2016","Year": "2016","Value": 2300,"Location": "UAE"
}]var groupedData = {};$scope.resArray.forEach(function(item) {var Location = item.Location;var value = item.Value;if (groupedData.hasOwnProperty(Location)) {groupedData[Location] += value;
} else {groupedData[Location] = value;}});
**Please see the plunker PlunkerLink
First of all I suggest to use more than one object for grouping, one for grouping by Month and Location and another one for collection groups specific totals, like for the before mentioned groups and a total value. This is necessary to calculate the percent values, later.
In the result table, you could address the grouped values by taking row and column name for the result.
var $scope = { resArray: [{ Month: "January-2016", Year: "2016", Value: 26000, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 0, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 8700, Location: "UAE" }, { Month: "January-2016", Year: "2016", Value: 311912, Location: "SAUDI ARABIA" }, { Month: "January-2016", Year: "2016", Value: 15300, Location: "SAUDI ARABIA" }, { Month: "January-2016", Year: "2016", Value: 3000, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 2500, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 2300, Location: "UAE" }] },
grouped = {},
total = { total: 0 };
$scope.resArray.forEach(function (item) {
grouped[item.Month] = grouped[item.Month] || {};
grouped[item.Month][item.Location] = (grouped[item.Month][item.Location] || 0) + item.Value;
['Month', 'Location'].forEach(function (key) {
total[key] = total[key] || {};
total[key][item[key]] = (total[key][item[key]] || 0) + item.Value;
});
total.total += item.Value;
});
console.log(grouped);
console.log(total);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I am trying to get the below output from the array using reduce, however, I can't wrap my head about some parts on how reduce behave , appreciate some explanation so I can fully grasp it.
final goal is after reducing the result into a single array, removing duplicate objects based on the vch_number
Reduce function
const result = car.reduce((acc,vch)=>{
const temp = {...acc,[vch.name]:vch.Vehciles}
for (const [key, value] of Object.entries(temp)){
const fillterd = value.map(item => {
item.status = key
return item
})
}
return temp
}
,{})
console.log(result)
// final desired output vs current output
current = { available:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51511,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2020,
vch_number: 51541,
status: 'available' } ],
parked:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'parked' } ],
service:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'service' } ] }
desired = [
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51511,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2020,
vch_number: 51541,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'parked' } ,
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'service' }
]
// Original API array
const car = [
{
"name": "available",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51511,
},
{
"make": "bwm",
"model": "i8",
"year": 2020,
"vch_number": 51541,
}
]
},
{
"name": "parked",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
},
{
"name": "service",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
}
]
First problem is your using a {} as the second argument (the so called accumulator) in the .reduce() function. You'll want to pass an empty array [].
Second off all you have the Vehciles array inside those objects so you have to perform one more transformation inside.
more about reducers:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
I think the key is to wrap your head around the initialValue and the accumulator and how it relates to the currentValue when it comes to Array.reduce()
I've whipped up this code:
const car = [
{
"name": "available",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51511,
},
{
"make": "bwm",
"model": "i8",
"year": 2020,
"vch_number": 51541,
}
]
},
{
"name": "parked",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
},
{
"name": "service",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
}
];
const result = car.reduce((acc,vch)=>{
const cars = vch.Vehciles.map(vehicle => {
const temp = {
status: vch.name,
...vehicle
};
return temp;
}).reduce((carAcc, car) => {
carAcc.push(car);
return carAcc;
}, acc);
return acc;
}, []);
console.log(result)
You could map the objects and check with a Set.
const
data = [{ name: "available", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51511 }, { make: "bwm", model: "i8", year: 2020, vch_number: 51541 }] }, { name: "parked", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51510 }] }, { name: "service", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51510 }] }],
result = data.flatMap(
(seen => ({ name: status, Vehciles }) => Vehciles.flatMap(o => seen.has(o.vch_number)
? []
: (seen.add(o.vch_number), { ...o, status })
))
(new Set)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
From the following array:
var arr = [{ "Year": 2019, "Title": "Sample1", "Sum": 1020000.0, "Budget":0},
{ "Year": 2019, "Title": "Sample2", "Sum": 2546658.0, "Budget":100},
{ "Year": 2019, "Title": "Sample3", "Sum": 1020000.0, "Budget":1000},
{ "Year": 2020, "Title": "Sample1", "Sum": 3472000.0, "Budget":100},
{ "Year": 2020, "Title": "Sample2", "Sum": 1020000.0, "Budget":10},
{ "Year": 2020, "Title": "Sample3", "Sum": 2452000.0, "Budget":50},
{ "Year": 2021, "Title": "Sample1", "Sum": 1000.0, "Budget":100},
{ "Year": 2021, "Title": "Sample2", "Sum": 119000.0, "Budget":10},
{ "Year": 2021, "Title": "Sample3", "Sum": 234000.0, "Budget":50}]
]
I need to change this into a single year per row, were the value of each "Title" has an entry with its "Sum" value and the Budget values should be aggregated together ie.
[{ "Year": 2019, "Sample1": 1020000.0, "Sample2":2546658.0, "Sample3":1020000.0 , "Budget":1100},{ etc]
My platform does not support ES6, through answers from an earlier post I have used .reduce as follows to get most of the way:
var res = arr.reduce(function(acc, curr) {
acc[curr.Year] = acc[curr.Year];
acc[curr.Year] = acc[curr.Year] || { Year: curr.Year } ;
acc[curr.Year][curr.Title] = curr.Sum;
return acc;
res = Object.keys(res).map(function(key) {
return res[key];
});
This produces:
[{ "Year": 2019, "Sample1": 1020000.0, "Sample2":2546658.0, "Sample3":1020000.0 },
{ "Year": 2020, "Sample2": 3472000.0, "Sample2":1020000.0, "Sample3":2452000.0},
{ "Year": 2021, "Sample3": 1000.0, "Sample2":119000.0, "Sample3":234000.0}]
But I cannot find a way to also sum the Budget figures together and add it to the same entry. I suspect I need to perform a separate reduce function on a duplicate array and push the result into the res array using the forEach loop with Year as the key. Can anyone see a way of doing this in the same reduce function?
When initializing a Year object in the reduce callback, also initialize a Budget property to 0. Then, on each iteration for that year, add to the budget property in addition to setting the Sample property:
var arr = [{ "Year": 2019, "Title": "Sample1", "Sum": 1020000.0, "Budget":0},
{ "Year": 2019, "Title": "Sample2", "Sum": 2546658.0, "Budget":100},
{ "Year": 2019, "Title": "Sample3", "Sum": 1020000.0, "Budget":1000},
{ "Year": 2020, "Title": "Sample1", "Sum": 3472000.0, "Budget":100},
{ "Year": 2020, "Title": "Sample2", "Sum": 1020000.0, "Budget":10},
{ "Year": 2020, "Title": "Sample3", "Sum": 2452000.0, "Budget":50},
{ "Year": 2021, "Title": "Sample1", "Sum": 1000.0, "Budget":100},
{ "Year": 2021, "Title": "Sample2", "Sum": 119000.0, "Budget":10},
{ "Year": 2021, "Title": "Sample3", "Sum": 234000.0, "Budget":50}
]
var res = arr.reduce(function(acc, curr) {
acc[curr.Year] = acc[curr.Year] || { Year: curr.Year, Budget: 0 } ;
// ^^^^^^^^^
acc[curr.Year][curr.Title] = curr.Sum;
acc[curr.Year].Budget += curr.Budget;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return acc;
}, {});
var output = Object.keys(res).map(function(key) {
return res[key];
});
console.log(output);
Note that the line in your original code
acc[curr.Year] = acc[curr.Year];
doesn't accomplish anything at all - you may omit it entirely.
You could consider using Babel and polyfills, allowing you to write code in the latest and greatest version of the language, while preserving compatibility for obsolete browsers, in which case, the code could be prettified to:
var arr=[{"Year":2019,"Title":"Sample1","Sum":1020000.0,"Budget":0},{"Year":2019,"Title":"Sample2","Sum":2546658.0,"Budget":100},{"Year":2019,"Title":"Sample3","Sum":1020000.0,"Budget":1000},{"Year":2020,"Title":"Sample1","Sum":3472000.0,"Budget":100},{"Year":2020,"Title":"Sample2","Sum":1020000.0,"Budget":10},{"Year":2020,"Title":"Sample3","Sum":2452000.0,"Budget":50},{"Year":2021,"Title":"Sample1","Sum":1000.0,"Budget":100},{"Year":2021,"Title":"Sample2","Sum":119000.0,"Budget":10},{"Year":2021,"Title":"Sample3","Sum":234000.0,"Budget":50}]
const output = Object.values(arr.reduce((a, { Year, Title, Sum, Budget }) => {
a[Year] = a[Year] || { Year, Budget: 0 };
a[Year][Title] = Sum;
a[Year].Budget += Budget;
return a;
}, {}));
console.log(output);
[{
"_id": {
"year": 2017,
"month": 4
},
"Confirm": 0
}, {
"_id": {
"year": 2017,
"month": 4
},
"Expired": 25
}, {
"_id": {
"year": 2017,
"month": 4
},
"Pending": 390
}, {
"_id": {
"year": 2017,
"month": 5
},
"Pending": 1400
}]
The array above contain same value month and year. Generated from MongoDB Aggregate. And I want to merge them into a single object and preserve whatever keys and values they have.
Expected output:
[{
month: 4,
year: 2017,
Expired: 25,
Pending: 390
}, {
month: 5,
year: 2017,
Pending: 1400
}]
I prefer the fastest execution implementation. Underscorejs or native are welcome. Thanks
This takes a little to pick apart, but it is linear:
const ary = [{
"_id": {
"year": 2017,
"month": 4
},
"Confirm": 0
}, {
"_id": {
"year": 2017,
"month": 4
},
"Expired": 25
}, {
"_id": {
"year": 2017,
"month": 4
},
"Pending": 390
}, {
"_id": {
"year": 2017,
"month": 5
},
"Pending": 1400
}];
const result = Object.values(ary.reduce((acc, cur) => {
const { month, year } = cur._id;
const key = `${month}-${year}`;
const obj = Object.assign({}, cur);
delete obj._id;
acc[key] = Object.assign(acc[key] || { month, year }, obj);
return acc;
}, {}));
console.log(result);
You could use a Map for grouping, and then Array.from to extract the final objects:
function merge(data) {
return Array.from(data.reduce( (acc, o) => {
const k = o._id.year * 100 + o._id.month;
const v = acc.get(k) || Object.assign({}, o._id);
for (let prop in o) {
if (prop !== '_id') v[prop] = o[prop];
}
return acc.set(k, v);
}, new Map), ([k, v]) => v);
}
// Sample data
const data = [{
"_id": {
"year": 2017,
"month": 4
},
"Confirm": 0
}, {
"_id": {
"year": 2017,
"month": 4
},
"Expired": 25
}, {
"_id": {
"year": 2017,
"month": 4
},
"Pending": 390
}, {
"_id": {
"year": 2017,
"month": 5
},
"Pending": 1400
}];
const result = merge(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This runs in O(N*logN) for sorting and O(N) for merging the json.
Hope this works for you!
var obj = [{
_id: {
year: 2017,
month: 5,
},
Pending: 1400,
}, {
_id: {
year: 2017,
month: 4,
},
Expired: 25,
}, {
_id: {
year: 2017,
month: 4,
},
Pending: 390,
}, {
_id: {
year: 2017,
month: 4,
},
Confirm: 0,
}];
function compare(a, b) {
return a._id.year !== b._id.year
? a._id.year - b._id.year
: a._id.month - b._id.month;
}
var sorted = obj.sort(compare);
function join(a, b) {
return {
_id: a._id,
Pending: (a.Pending? a.Pending : 0) + (b.Pending? b.Pending : 0),
Confirm: (a.Confirm? a.Confirm : 0) + (b.Confirm? b.Confirm : 0),
Expired: (a.Expired? a.Expired : 0) + (b.Expired? b.Expired : 0),
};
}
var compressed = sorted.filter(function (value, index) {
if (!sorted[index + 1]) {
return true;
}
if (compare(value, sorted[index + 1]) === 0) {
sorted[index + 1] = join(value, sorted[index + 1]);
return false;
}
return true;
});
console.log(compressed);
// if you want month and year formatted:
console.log(compressed.map(function (o) {
const result = {
month: o._id.month,
year: o._id.year,
};
if (o.Pending !== undefined) result.Pending = o.Pending;
if (o.Confirm !== undefined) result.Confirm = o.Confirm;
if (o.Expired !== undefined) result.Expired = o.Expired;
return result;
}));
I feel embarrassed for asking this question as I should know how to figure it out, but I'm spinning my wheels on grouping an array of objects by multiple keys.
Here's the data:
[
{
"car": "audi",
"type": "A6",
"style": "Avant",
"year": "1996"
},
{
"car": "audi",
"type": "A4",
"style": "2",
"year": "2006"
},
{
"car": "audi",
"type": "A4",
"style": "L W12",
"year": "2006"
},
{
"car": "audi",
"type": "80",
"style": "GLE",
"year": "1975"
},
{
"car": "audi",
"type": "A6",
"style": "Avant L",
"year": "1996"
},
{
"car": "audi",
"type": "A6",
"style": "3.2 Multitronic",
"year": "2006"
},
]
What I've been trying to generate with little success is the following:
[{
"audi": [{
"1996": {
"A6": ["Avant, Avant L"]
}
}, {
"2006": }
"A6": ["3.2 Multitronic"],
"A4": ["L W12", "2"]
}
}
....
}]
The schema is:
{
"car1": [{
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
},
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
}
}],
"car2": [{
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
},
"year2": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
}
}]
}
I've tried the following with lodash
let result = _.chain(carData)
.groupBy('car')
.toPairs()
.map(function(curr) {
return _.zipObject(['car', 'year'], curr);
})
.value();
This gets me part of the way, but I end up with incomplete data when it comes to the styles and types for each year of the car.
You could use a hash object and a nested approach for the given properties.
var data = [{ car: "audi", type: "A6", style: "Avant", year: 1996 }, { car: "audi", type: "A4", style: 2, year: 2006 }, { car: "audi", type: "A4", style: "L W12", year: 2006 }, { car: "audi", type: 80, style: "GLE", year: 1975 }, { car: "audi", type: "A6", style: "Avant L", year: 1996 }, { car: "audi", type: "A6", style: "3.2 Multitronic", year: 2006 }],
keys = ['car', 'year', 'type'],
result = [];
data.forEach(function (a) {
keys.reduce(function (r, k) {
var o = {};
if (!r[a[k]]) {
r[a[k]] = { _: [] };
o[a[k]] = r[a[k]]._;
r._.push(o);
}
return r[a[k]];
}, this)._.push(a.style);
}, { _: result });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a (slightly verbose) solution that generates exactly the JSON object shape you wanted and groups by unlimited keys:
var cars = [{
"car": "audi",
"type": "A6",
"style": "Avant",
"year": "1996"
}, {
"car": "audi",
"type": "A4",
"style": "2",
"year": "2006"
}, {
"car": "audi",
"type": "A4",
"style": "L W12",
"year": "2006"
}, {
"car": "audi",
"type": "80",
"style": "GLE",
"year": "1975"
}, {
"car": "audi",
"type": "A6",
"style": "Avant L",
"year": "1996"
}, {
"car": "audi",
"type": "A6",
"style": "3.2 Multitronic",
"year": "2006"
}, ];
function groupBy(list, prop) {
return list.reduce((groupped, item) => {
var key = item[prop];
delete item[prop];
if (groupped.hasOwnProperty(key)) {
groupped[key].push(item);
} else {
groupped[key] = [item];
}
return groupped
}, {});
}
function groupSubKeys(obj, properties, propIndex) {
var grouppedObj = groupBy(obj, properties[propIndex]);
Object.keys(grouppedObj).forEach((key) => {
if (propIndex < properties.length - 2) {
grouppedObj[key] = groupSubKeys(grouppedObj[key], properties, propIndex + 1);
} else {
grouppedObj[key] = grouppedObj[key].map(item => item[properties[propIndex + 1]])
}
});
return grouppedObj;
}
function groupByProperties(list, properties) {
return groupSubKeys(list, properties, 0);
}
console.log(groupByProperties(cars, ['car', 'year', 'type', 'style']));
Here's a running example:
http://codepen.io/rarmatei/pen/evmBOo
const groupBy = function groupBy(list, properties, propertyIndex) {
// current property index
let i = propertyIndex === undefined ? 0 : propertyIndex;
// group by
let grouppedObj = list.reduce((acc, obj) => {
let groupedValue = obj[properties[i]];
if (!groupedValue) {
return acc;
}
if (!acc[groupedValue]) {
acc[groupedValue] = [];
}
acc[groupedValue].push({ ...obj, groupBy: properties.join(",") });
return acc;
}, {});
// group by nested
const keys = Object.keys(grouppedObj);
if (i === properties.length - 1) {
return grouppedObj;
}
keys.forEach((key) => {
grouppedObj[key] = groupBy(grouppedObj[key], properties, i + 1);
});
return grouppedObj;
};
const data =[
{
"year": "2021",
"cabin": "1",
"months": ["1", "2"]
},
{
"year": "2021",
"cabin": "1",
"months": ["4"]
},
{
"year": "2021",
"cabin": "2",
"months": ["1", "2"]
},
{
"year": "2022",
"cabin": "1",
"months": ["1", "2"]
},
{
"year": "2022",
"cabin": "1",
"months": ["4"]
},
{
"year": "2022",
"cabin": "2",
"months": ["1", "2"]
}
];
const results=groupBy(data, ["year", "cabin"]);
console.log(results);
I have an API that returns indicator data in this format:
[
{
"concept": "population",
"year": 2012,
"value": 9.5,
"country-name": "Sweden",
},
{
"concept": "education",
"year": 2012,
"value": 12,
"country-name": "Sweden",
},
{
"concept": "population",
"year": 2012,
"value": 5,
"country-name": "Norway",
},
{
"concept": "eduction",
"year": 2012,
"value": 12,
"country-name": "Norway",
}
]
Based on this, I typically need to group this by country and year to get something like:
var data = [
{id : Sweden, population : 9.5, education: 12},
{id : Norway, population : 5, education: 12},
]
Or just by country and year-values as an array:
var data = [
{id : Sweden, values : [ { year : 2012, population : 9.5, education: 12 }, {...} ]},
{id : Norway, values : [ { year : 2012, population : 5, education: 12 }, {...} ]},
]
What JS libraries can help manage these common data transformations?
Can you provide an example of doing the above (using a library or vanilla js)?
You can do it yourself, just iterate and create whatever you need, something like
var data = [];
json.forEach(function(item) {
var country = item['country-name'],
match = false,
obj = {}
data.forEach(function(dataItem, i) {
if (dataItem.id === country) {
match = true;
obj = data[i];
}
});
obj.id = country;
obj[item.concept] = item.value;
if (!match) data.push(obj)
});
FIDDLE
You might find an object easier to work with than an array:
var data = {};
indicatordata.forEach(function(item){
var country = item['country-name'];
var year = item.year;
data[country] = data[country] || {id: item['country-name'], values: {}};
var values = data[country].values;
values[year] = values[year] || {};
values[year][item.concept] = item.value;
});
You can do it with D3. d3.nest and Array.prototype.reduce specifically. The code is declarative and easy to maintain.
var rawData = [
{
"concept": "population",
"year": 2012,
"value": 9.5,
"country-name": "Sweden",
},
{
"concept": "education",
"year": 2012,
"value": 12,
"country-name": "Sweden",
},
{
"concept": "population",
"year": 2012,
"value": 5,
"country-name": "Norway",
},
{
"concept": "eduction",
"year": 2012,
"value": 12,
"country-name": "Norway",
}
];
var data = d3.nest()
.key(function(d)
{
return d['year'];
})
.key(function(d)
{
return d['country-name'];
})
.rollup(function(group)
{
return group.reduce(function(result, d)
{
result[d['concept']] = d['value'];
return result;
}, {});
})
.entries(rawData);
console.log(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>