Search code examples
crossfilter

How to group, sum, and average times in crossfilter.js


I am trying to find average time taken by the name. May i know the best way to find total time across all the name as well as the average time taken. Please find the details below

let data = [{"name":"A","children":"8:17:33"},{"name":"B","children":"9:30:45"},{"name":"C","children":"12:45:56"},{"name":"D","children":"4:20:30"},{"name":"E","children":"7:12:38"},{"name":"F","children":"6:29:45"},{"name":"G","children":"11:34:45"},{"name":"H","children":"10:30:45"},{"name":"I","children":"8:34:45"},{"name":"J","children":"8:34:12"}];

let CFX =  crossfilter(data);
let dimName = CFX.dimension( (d)=> d.name);
let grpTime = dimName.group().reduceSum( (d)=> d.children);
console.log( grpTime.all() );
<script src="https://square.github.io/crossfilter/crossfilter.v1.min.js"></script>

EDIT: Here is a custom reduce to do it, but the resulting times come out as strings, and the average values are too large:

let data = [{"name":"A","children":"8:17:33"},{"name":"B","children":"9:30:45"},{"name":"C","children":"12:45:56"},{"name":"D","children":"4:20:30"},{"name":"E","children":"7:12:38"},{"name":"F","children":"6:29:45"},{"name":"G","children":"11:34:45"},{"name":"H","children":"10:30:45"},{"name":"I","children":"8:34:45"},{"name":"J","children":"8:34:12"}];

	let CFX =  crossfilter(data);
	let dimName = CFX.dimension( (d)=> d.name);
	let grpTime = dimName.group().reduceSum( (d)=> d.children);
	//console.log( grpTime.all() );
	let timeGrp = dimName.group().reduce(
		function( p , v) {
			p.count++;
			let time = v.children.split(':');
			p.time += time[0] * 60 * 60 + time[1] * 60 + time[2];
			p.avg = p.time / p.count;
			return p;
		},
		function( p , v) {
			p.count--;
			let time = v.children.split(':');
			p.time -= time[0] * 60 * 60 + time[1] * 60 + time[2];
			p.avg = p.count ? p.time / p.count : 0;
			return p;
		},
		function( ) {
			return {
				time: 0,
				avg: 0,
				count : 0
			}
		}
	);
	console.log(timeGrp.all());
<script src="https://square.github.io/crossfilter/crossfilter.v1.min.js"></script>


Solution

  • One of the trickier things about JavaScript is that it will silently and happily convert strings to numbers, and vice versa, and it doesn't always do this correctly.

    In this case, since your times are strings, and splitting those strings produces more strings, you've got a mix of strings and numbers.

    But does JavaScript complain? No. It automatically converts strings to numbers, like

    "8" * 60 = 480
    

    But then it also converts numbers to strings, like

    90 + "9" = 909
    

    The right thing to do is convert those times to numbers immediately:

    let time = v.children.split(':').map(x => +x);
    

    let data = [{"name":"A","children":"8:17:33"},{"name":"B","children":"9:30:45"},{"name":"C","children":"12:45:56"},{"name":"D","children":"4:20:30"},{"name":"E","children":"7:12:38"},{"name":"F","children":"6:29:45"},{"name":"G","children":"11:34:45"},{"name":"H","children":"10:30:45"},{"name":"I","children":"8:34:45"},{"name":"J","children":"8:34:12"}];
    
    	let CFX =  crossfilter(data);
    	let dimName = CFX.dimension( (d)=> d.name);
    	let grpTime = dimName.group().reduceSum( (d)=> d.children);
    	//console.log( grpTime.all() );
    	let timeGrp = dimName.group().reduce(
    		function( p , v) {
    			p.count++;
    			let time = v.children.split(':').map(x => +x);
    			p.time += time[0] * 60 * 60 + time[1] * 60 + time[2];
    			p.avg = p.time / p.count;
    			return p;
    		},
    		function( p , v) {
    			p.count--;
    			let time = v.children.split(':').map(x => +x)
    			p.time -= time[0] * 60 * 60 + time[1] * 60 + time[2];
    			p.avg = p.count ? p.time / p.count : 0;
    			return p;
    		},
    		function( ) {
    			return {
    				time: 0,
    				avg: 0,
    				count : 0
    			}
    		}
    	);
    	console.log(timeGrp.all());
    <script src="https://square.github.io/crossfilter/crossfilter.v1.min.js"></script>