【问题标题】:Efficient Median Calculation in MongoDBMongoDB 中的高效中值计算
【发布时间】:2015-05-04 10:54:37
【问题描述】:

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

Mongo does not yet have an internal method for calculating the median.我已经使用下面的方法来确定它,但我担心有一个更有效的方法,因为我对 JS 还很陌生。任何 cmets 将不胜感激。

// 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}
                                 }}
                                 )

【问题讨论】:

  • 求中位数的用例是什么?您想要整个集合的值的中值,只是随着集合的变化而不断地重新计算?或者你想要不同查询结果集的中位数?
  • 例如,我们跟踪房地产搜索并想知道他们正在查看的房屋的中位标价。是的,我一直在假设我们需要随着集合的增长重新计算中位数。我在想我们可以只保留计算之间的中间 3-4 值,但我还不确定如何合并它。

标签: mongodb mapreduce median


【解决方案1】:

获取中值的简单方法是在字段上进行索引,然后在结果的中途跳到该值。

> 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

因为skip,这并不奇怪,但至少查询会被索引覆盖。对于更新中位数,您可能会更花哨。当有新文档进入或文档的value 更新时,您将其value 与中值进行比较。如果新的value 更高,您需要通过从当前中值文档中找到下一个最高的value 来向上调整中值(或者取平均值,或者根据您的规则正确计算新中值)

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

如果新的value 小于当前的中位数,你会做类似的事情。这会成为您在此更新过程中写入的瓶颈,并且需要考虑各种情况(您如何允许自己一次更新多个文档?更新具有中值的文档?更新 value 小于中值的文档到value 大于中位数的人?),因此最好根据跳过过程偶尔更新。

【讨论】:

  • skip 方法对我们来说似乎有太多的副作用。此外,当我们有偶数条记录时,我们需要计算中间两条的平均值,并且需要内置该逻辑。我们最终决定在 Mongo 之外进行计算。
  • @Crowson 你需要两个中间值的平均值吗?真的吗?如果您能证明它对您的业务产生了影响,那么我相信我可以为您解决您的问题。
  • 我一直在假设进行真正的中位数计算。如果我们决定只使用两个中间值中的较低值(当呈现偶数条记录时),这种跳过方法是否仍然更可取?我可以看到计算上的好处,并且可能不会对业务案例造成重大损失。感谢您的洞察力。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2014-01-02
    • 2017-03-25
    • 2023-03-05
    • 1970-01-01
    • 2021-01-26
    • 1970-01-01
    • 2018-06-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多