使用d3js将桶聚合转换为直方图

时间:2018-04-23 15:10:44

标签: javascript d3.js histogram

我有一个以下结构的json:

{“1”:35,“2”:12,....}

密钥是一个数字,该值是db中集合中该值的聚合(另一个数字)。 可能有数千个不同的密钥。 我想将此聚合转换为最多10个桶的直方图。 我想使用d3js库,因为我们已经将它用于其他事情。 从api看来,它只知道接受一组数字,但我想自定义值。

我只需要一个对象而不关心可视化。 谢谢你的帮助!

2 个答案:

答案 0 :(得分:0)

D3已有直方图功能,如果你通过API,我相信你可以找到它。但是既然你想要一个自定义的解决方案,这些方面的东西都可以工作(我是特意写的,所以检查一下):

VERSION 1 - 事件循环的单一刻度 - 同步

让我们创建一个示例数据集:

var x = {};
for (var i =0;i<1000;++i){
    x[i]=Math.random()
}

创建一个将接受对象,多个存储桶和存储区间隔的函数:

function allocate(obj,bucketCount,bucketStep){
    var objLastIndex = Object.keys(obj).length - 1;
    var retValue = Array.apply(null,Array(bucketCount))
        .map(function(d,i){
            var obj = {};
            obj.count = 0;
            obj.freq = 0;
            return obj;
        });
    Object.keys(obj).forEach(function(d,i){
        var bucketIndex = obj[d]/bucketStep<<0;
        retValue[bucketIndex] && ++retValue[bucketIndex].count;
        if(i === objLastIndex){
            retValue.forEach(function(d,i){
                d.freq = d.count/(objLastIndex+1)
            })
        }
    });
    return retValue;
}

使用它:

allocate(x,10,0.1);
"[
    {
        "count": 84,
        "freq": 0.084
    },
    {
        "count": 90,
        "freq": 0.09
    },
    {
        "count": 113,
        "freq": 0.113
    },
    {
        "count": 98,
        "freq": 0.098
    },
    {
        "count": 111,
        "freq": 0.111
    },
    {
        "count": 108,
        "freq": 0.108
    },
    {
        "count": 108,
        "freq": 0.108
    },
    {
        "count": 82,
        "freq": 0.082
    },
    {
        "count": 108,
        "freq": 0.108
    },
    {
        "count": 98,
        "freq": 0.098
    }
]"

或者:

allocate(x,10,0.5);
"[
    {
        "count": 496,
        "freq": 0.496
    },
    {
        "count": 504,
        "freq": 0.504
    },
    {
        "count": 0,
        "freq": 0
    },
    {
        "count": 0,
        "freq": 0
    },
    {
        "count": 0,
        "freq": 0
    },
    {
        "count": 0,
        "freq": 0
    },
    {
        "count": 0,
        "freq": 0
    },
    {
        "count": 0,
        "freq": 0
    },
    {
        "count": 0,
        "freq": 0
    },
    {
        "count": 0,
        "freq": 0
    }
]"

VERSION 2 - 多个周期 - 异步 - 处理&gt; 1000000点

由于您没有提供任何反馈或指示输出,我认为VERSION 1中的格式是您想要的。

现在我将稍微修改上面的功能。我将它分为2,第一部分将返回一个像ES6 Promises或yield关键字返回的对象,另一部分(附在第一部分上,因此它不必一次又一次地创建)将会繁重的举动。如果需要,您可以附加回调:

function allocate(obj,bucketCount,bucketStep,callback){
    var keys =  Object.keys(obj);
    var retValue = Array.apply(null,Array(bucketCount))
        .map(function(d,i){
            var obj = {};
            obj.count = 0;
            obj.freq = 0;
            return obj;
        });
    console.log(retValue)
    retValue.__parent = {result:retValue,done:false};
    allocate._iterate(keys,0,retValue,obj,bucketCount,bucketStep,callback);
    return retValue.__parent;
}
allocate._iterate = function(keys,iteration,buckets,obj,bucketCount,bucketStep,callback){
    var currentLoad = keys.slice(iteration*10000,++iteration*10000),
        currentLoadLength = currentLoad.length,
        currentLoadLastIndex = currentLoadLength - 1,
        length = keys.length;
    currentLoad.forEach(function(d,i){
        var bucketIndex = obj[d]/bucketStep<<0;
        buckets[bucketIndex] && ++buckets[bucketIndex].count;
    });
    if(currentLoadLength < 10000) {
        buckets.forEach(function(d,i){
                d.freq = d.count/length;
        });
        buckets.__parent.done = true;
        return callback && callback(buckets);
    } else {
        window.requestAnimationFrame(
            allocate._iterate(keys,iteration,buckets,obj,bucketCount,bucketStep,callback)
        );
    }
}

要使用它(最后一个参数是一个可选的回调函数,它将数组作为第一个参数传递):

var x = {}; //1000000 keys, I could do more but chrome could not handle his own object.
for (var i =0;i<1000000;++i){
    x[i]=Math.random()
}
var returnValue = allocate(x,10,0.1,function(){console.log("done!")});
//{result: Array(10), done: false}
//after few seconds --> 'done!' is logged
returnValue.result;
//"[
    {
        "count": 100156,
        "freq": 0.100156
    },
    {
        "count": 100575,
        "freq": 0.100575
    },
    {
        "count": 100009,
        "freq": 0.100009
    },
    {
        "count": 99818,
        "freq": 0.099818
    },
    {
        "count": 99785,
        "freq": 0.099785
    },
    {
        "count": 100332,
        "freq": 0.100332
    },
    {
        "count": 99778,
        "freq": 0.099778
    },
    {
        "count": 99790,
        "freq": 0.09979
    },
    {
        "count": 99795,
        "freq": 0.099795
    },
    {
        "count": 99962,
        "freq": 0.099962
    }
]"

答案 1 :(得分:0)

看起来ibowenkenobi有一个合理的解决方案,但如果你更愿意利用d3,而不是完全重新发明轮子,你可以。 Here's a JSFiddle

请记住d3.histogram().thresholds()有点难以预测;你建议一些桶,它会选择一个接近它的实际数字。输入10导致13个桶,在这种情况下。

function parseBaseTenInt(n) {
    return parseInt(n, 10);
}

function accumulateValueCounts(accumulator, currentValue) {
    return accumulator + data[currentValue.toString()];
}

function calcBinSum(bin) {
    return bin.reduce(accumulateValueCounts, 0);
}

var numbers = Object.keys(data).map(parseBaseTenInt);

var histogram = d3.histogram().thresholds(9);

var bins = histogram(numbers);
console.log('bins:', bins);

var binSums = bins.map(calcBinSum);
console.log('totals for each bin:', binSums);