patternjavascriptMinor
Aggregate sum by month
Viewed 0 times
aggregatemonthsum
Problem
I have JSON data:
Data contains objects with timestamp and value, can be more than one in month.
I need to sum values from objects which have the same month and year.
First I use each function to add additional field
var data = [ { dat: 'timestamp', val: 10 }, { dat: 'timestamp', val: 20}, (...)]Data contains objects with timestamp and value, can be more than one in month.
I need to sum values from objects which have the same month and year.
First I use each function to add additional field
cat which contains formatted date in format MMM'YY for example: Sep'15, after that I group the result by this cat field. This result I map to objects with properties name and value. Values are summed by using reduce function on plucked values - val from each grouped result. I'm using underscore.var withCategories = _.each(data, function (elem) {
elem.cat = moment(elem.dat).format("MMM[']YY");
});
var groupedResult = _.groupBy(withCategories , 'cat');
var finalResult = _.map(groupedResult, function (elems) {
var valuesArray = _.pluck(elems, 'val'); // from group
return {
name: elems[0] ? elems[0].cat : '',
value: _.reduce(valuesArray, function (memo, num) {
return memo + num;
}, 0)
}
});Solution
First of all, your indentation is odd. Use a beautifier tool like JSBeautifier. If you use Sublime Text 3, there's an HTML-CSS-JS Prettifier plugin which uses the same tool internally.
Next,
Next, you're using moment.js in one of the loops. Moment.js is very slow based on personal experience. It tries to do as much as possible to get dates correctly, sacrificing execution speed. If you profile your code, you'll see a lot of internal
Now you look like you're just operating on arrays. You can use the native array methods instead of underscore (unless you're still trying to support IE8???)
So if I understand your code correctly, this should do the same thing with lesser loops.
Next,
dat and val don't really tell anything about your data. Name it meaningfully. It's a timestamp, maybe date? And value for val? If you worry about size on transmission, just use gzip. It's better than losing hair on misnamed data.Next, you're using moment.js in one of the loops. Moment.js is very slow based on personal experience. It tries to do as much as possible to get dates correctly, sacrificing execution speed. If you profile your code, you'll see a lot of internal
extend calls in moment to generate your date. Since you're just getting year and month, why not use native getFullYear and getMonth?Now you look like you're just operating on arrays. You can use the native array methods instead of underscore (unless you're still trying to support IE8???)
So if I understand your code correctly, this should do the same thing with lesser loops.
// Because JS doesn't have a nice way to name months because they may differ per locale
var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// Use reduce to aggregate your data. Pass around a hash so that we have
// direct access to groups as well as ensure groups appear just once.
var dataByMonth= data.reduce(function(dataByMonth, datum){
var date = new Date(datum.dat);
var value = datum.val;
var month = monthNames[date.getMonth()];
var year = ('' + date.getFullYear()).slice(-2);
var group = month + '\'' + year;
dataByMonth[group] = (dataByMonth[group] || 0) + value;
return dataByMonth;
}, {});
// Now just turn the hash into an array.
var finalResult = Object.keys(dataObjectByMonth).map(function(group){
return { name: group, value: dataByMonth[group] };
});Code Snippets
// Because JS doesn't have a nice way to name months because they may differ per locale
var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// Use reduce to aggregate your data. Pass around a hash so that we have
// direct access to groups as well as ensure groups appear just once.
var dataByMonth= data.reduce(function(dataByMonth, datum){
var date = new Date(datum.dat);
var value = datum.val;
var month = monthNames[date.getMonth()];
var year = ('' + date.getFullYear()).slice(-2);
var group = month + '\'' + year;
dataByMonth[group] = (dataByMonth[group] || 0) + value;
return dataByMonth;
}, {});
// Now just turn the hash into an array.
var finalResult = Object.keys(dataObjectByMonth).map(function(group){
return { name: group, value: dataByMonth[group] };
});Context
StackExchange Code Review Q#104531, answer score: 3
Revisions (0)
No revisions yet.