I have JSON data:
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)
}
});
1 Answer 1
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, 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] };
});