MongoDB中的高效中位数计算

时间:2015-03-03 17:07:07

标签: mongodb mapreduce median

我们有一个名为analytics的Mongo集合,它通过cookie ID跟踪用户访问。当用户访问不同的页面时,我们想要计算几个变量的中位数。

Mongo does not yet have an internal method for calculating the median.我使用了以下方法来确定它,但我担心有一种更有效的方式,因为我对JS很新。任何意见将不胜感激。

// Saves the JS function for calculating the Median. Makes it accessible to the Reducer.
db.system.js.save({_id: "myMedianValue",
    value: function (sortedArray) {
    var m = 0.0;
    if (sortedArray.length % 2 === 0) {
        //Even numbered array, average the middle two values
        idx2 = sortedArray.length / 2;
        idx1 = idx2 - 1;
        m = (sortedArray[idx1] + sortedArray[idx2]) / 2;
    } else {
        //Odd numbered array, take the middle value
        idx = Math.floor(sortedArray.length/2);
        m = sortedArray[idx];
    }
        return m
    }
});


var mapFunction = function () {
    key = this.cookieId;
    value = {
        // If there is only 1 view it will look like this
        // If there are multiple it gets passed to the reduceFunction
        medianVar1: this.Var1,
        medianVar2: this.Var2,
        viewCount: 1
    };

    emit(key, value);
    };

var reduceFunction = function(keyCookieId, valueDicts) {
    Var1Array = Array();
    Var2Array = Array();
    views = 0;

    for (var idx = 0; idx < valueDicts.length; idx++) {
        Var1Array.push(valueDicts[idx].medianVar1);
        Var2Array.push(valueDicts[idx].medianVar2);
        views += valueDicts[idx].viewCount;
    }


    reducedDict = {
        medianVar1: myMedianValue(Var1Array.sort(function(a, b){return a-b})),
        medianVar2: myMedianValue(Var2Array.sort(function(a, b){return a-b})),
        viewCount: views
    };

    return reducedDict
    };


db.analytics.mapReduce(mapFunction,
                       reduceFunction,
                       { out: "analytics_medians",
                         query: {Var1: {$exists:true},
                                 Var2: {$exists:true}
                                 }}
                                 )

2 个答案:

答案 0 :(得分:0)

获取中值的简单方法是在字段上建立索引,然后跳到结果中间的值。

> db.test.drop()
> db.test.insert([
    { "_id" : 0, "value" : 23 },
    { "_id" : 1, "value" : 45 },
    { "_id" : 2, "value" : 18 },
    { "_id" : 3, "value" : 94 },
    { "_id" : 4, "value" : 52 },
])
> db.test.ensureIndex({ "value" : 1 })
> var get_median = function() {
    var T = db.test.count()    // may want { "value" : { "$exists" : true } } if some fields may be missing the value field
    return db.test.find({}, { "_id" : 0, "value" : 1 }).sort({ "value" : 1 }).skip(Math.floor(T / 2)).limit(1).toArray()[0].value    // may want to adjust skip this a bit depending on how you compute median e.g. in case of even T
}
> get_median()
45

由于跳过这并不令人惊讶,但至少查询将被索引覆盖。为了更新中位数,你可能会更高兴。当新文档进入或文档的value更新时,您将其value与中位数进行比较。如果新的value更高,则需要通过从当前中位数doc找到下一个最高value来调整中位数(或者使用它的平均值,或者正确计算新中位数的任何内容)根据你的规则)

> db.test.find({ "value" : { "$gt" : median } }, { "_id" : 0, "value" : 1 }).sort({ "value" : 1 }).limit(1)

如果新的value小于当前的中位数,你会做类似的事情。这会阻碍您对此更新过程的撰写,并有各种需要考虑的案例(您如何允许自己一次更新多个文档?更新具有中值的文档?更新value小于的文档中位数为value大于中位数的那个?),因此根据跳过程序偶尔更新可能会更好。

答案 1 :(得分:0)

我们最终更新了每个页面请求的中位数,而不是使用cron作业或其他内容批量更新。我们有一个Node API,它使用Mongo的聚合框架来匹配/排序用户的结果。然后,结果数组传递给Node内的中值函数。然后将结果写回给该用户的Mongo。对此并不十分满意,但它似乎没有锁定问题并且表现良好。