Mongo中的分箱和制表(唯一/计数)

Posted

技术标签:

【中文标题】Mongo中的分箱和制表(唯一/计数)【英文标题】:Binning and tabulate (unique/count) in Mongo 【发布时间】:2012-07-21 14:23:50 【问题描述】:

我正在寻找一种使用 Mongo 生成一些汇总统计信息的方法。假设我有一个包含许多表单记录的集合

"name" : "Jeroen", "gender" : "m", "age" :27.53 

现在我想获取性别和年龄的分布。假设性别,只有值"m""f"。获取我的收藏中男性和女性总数的最有效方法是什么?

对于年龄,有没有一种方法可以进行一些“分箱”并给我一个像摘要这样的直方图;即年龄在区间内的记录数:[0, 2), [2, 4), [4, 6) ... 等?

【问题讨论】:

【参考方案1】:

我刚刚试用了 MongoDB 2.2 版(2.2.0-rc0 已经发布)中将提供的新聚合框架,它应该比 map reduce 具有更高的性能,因为它不依赖于 javascript

输入数据:

 "_id" : 1, "age" : 22.34, "gender" : "f" 
 "_id" : 2, "age" : 23.9, "gender" : "f" 
 "_id" : 3, "age" : 27.4, "gender" : "f" 
 "_id" : 4, "age" : 26.9, "gender" : "m" 
 "_id" : 5, "age" : 26, "gender" : "m" 

性别聚合命令:

db.collection.aggregate(
   $project: gender:1,
   $group: 
        _id: "$gender",
        count: $sum: 1
   )

结果:

"result" : 
   [
     "_id" : "m", "count" : 2,
     "_id" : "f", "count" : 3
   ],
   "ok" : 1

获取垃圾箱中的年龄:

db.collection.aggregate(
   $project: 
        ageLowerBound: $subtract:["$age", $mod:["$age",2]]
   ,
   $group: 
       _id:"$ageLowerBound", 
       count:$sum:1
   
)

结果:

"result" : 
    [
       "_id" : 26, "count" : 3,
       "_id" : 22, "count" : 2
    ],
    "ok" : 1

【讨论】:

我还应该提醒任何对聚合框架感兴趣的人,建议尽早在聚合命令中使用 $match 以防止全表扫描。 对于不是某个数字的倍数的任意 bin,您可以使用 $cond 尽管语法很糟糕:$project: ageLowerBound: $cond: [$lt: [$age , 2], "0", $cond: [$lt: [$age, 4], "2", "4"]] ...或类似的东西。 @Jenna 我们可以对 GeoJSON 条目做些什么?【参考方案2】:

康斯坦丁的回答是对的。 MapReduce 完成了工作。这是完整的解决方案,以防其他人觉得这很有趣。

要计算性别,地图功能键是每条记录的this.gender 属性。然后 reduce 函数简单地将它们相加:

// count genders
db.persons.mapReduce(
    function()
        emit(this["gender"], count: 1)
    , function(key, values)
        var result = count: 0;
        values.forEach(function(value) 
            result.count += value.count;
        );
        return result;
    , out:  inline : 1
);

为了进行分箱,我们将 map 函数中的键设置为向下舍入到最接近的除以二。因此,例如10 到 11.9999 之间的任何值都将获得相同的密钥 "10-12"。然后我们再次简单地将它们相加:

db.responses.mapReduce(
    function()
        var x = Math.floor(this["age"]/2)*2;
        var key = x + "-" + (x+2);
        emit(key, count: 1)
    , function(state, values)
        var result = count: 0;
        values.forEach(function(value) 
            result.count += value.count;
        );
        return result;
    , out:  inline : 1
);

【讨论】:

这很酷。在哪里可以找到 mapReduce() 函数的文档?我搜索了一下,但似乎没有找到官方来源...【参考方案3】:

获取男性总数的简单方法是db.x.find("gender": "m").count()

如果您只想在一个查询中同时计算男性和女性人数,那么没有简单的方法。映射/减少将是一种可能性。或者也许是新的aggregation framework。您的 binning 要求也是如此

Mongo 不适合聚合,但对于许多小的增量更新来说却很棒。 所以用 mongo 解决这个问题的最好方法是在单独的集合中收集聚合数据。

因此,如果您使用这样的一个文档来保存统计信息集合:

stats: [
  
     "male": 23,
     "female": 17,
     "ageDistribution": 
       "0_2" : 3,
       "2_4" : 5,
       "4_6" : 7
     
  
]

...然后,每次您从另一个集合中添加或删除一个人时,您都会在统计信息集合中向上或向下计数相应的字段。

db.stats.update("$inc": "male": 1, "ageDistribution.2_4": 1)

通过这种方式查询统计数据会非常快,并且您几乎不会注意到统计数据上下计数的任何性能开销。

【讨论】:

【参考方案4】:

根据数据量,找到男性和女性数量的最有效方法可能是 天真的查询或地图减少工作。分箱最好通过 map reduce 完成:

在map阶段你的key是一个bin,value是1,而在reduce阶段你只是总结值

【讨论】:

你的速度更快 - 我正在度假,几乎离线【参考方案5】:

借助 Mongo 3.4,这变得更加容易,这要归功于新的 $bucket 和 $bucketAuto 聚合函数。以下查询自动分为两组:

db.bucket.aggregate( [
   
     $bucketAuto: 
         groupBy: "$gender",
         buckets: 2
     
   
] )

使用以下输入数据:

 "_id" : 1, "age" : 22.34, "gender" : "f" 
 "_id" : 2, "age" : 23.9, "gender" : "f" 
 "_id" : 3, "age" : 27.4, "gender" : "f" 
 "_id" : 4, "age" : 26.9, "gender" : "m" 
 "_id" : 5, "age" : 26, "gender" : "m" 

它给出以下结果:

 "_id" :  "min" : "f", "max" : "m" , "count" : 3 
 "_id" :  "min" : "m", "max" : "m" , "count" : 2 

注意,bucket 和 auto-bucket 通常用于连续变量(数字、日期),但在这种情况下 auto-bucket 工作得很好。

【讨论】:

【参考方案6】:

根据@ColinE 的答案,直方图的分箱可以通过

db.persons.aggregate([
  
  $bucket: 
    groupBy: "$j.age",
    boundaries: [0,2,4,6,8,10,12,14,16,18,20],
    default: "Other",
    output: 
      "count":  $sum: 1 
    
  
],
allowDiskUse:true)

$bucketAuto 对我不起作用,因为桶似乎是按对数刻度收集的。 allowDiskUse 仅在您拥有数百万个文档时才需要

【讨论】:

以上是关于Mongo中的分箱和制表(唯一/计数)的主要内容,如果未能解决你的问题,请参考以下文章

获取bash中列中唯一值的计数

如何在 R 中绘制预分箱直方图

更新 mongo 文档数组中的单个子文档的计数

制表关联频率计数

Mongo 聚合计数子文档(计数回复评论)

sklearn 集成和树中连续变量的分箱