python - 计算嵌套字段的所有唯一值的频率计数器
问题描述
我想在 Mongo Document 中找到嵌套字段的所有唯一值的频率计数器。
更具体地说,如果我的集合说 db['sample'],则包含以下文档 -
{'a' : 1, 'b' : {'c' : 25, 'd' : "x", 'e' : 36}},
{'a' : 2, 'b' : {'c' : 5, 'd' : "xx", 'e' : 36}},
{'a' : 33, 'b' : {'c' : 25, 'd' : "xx", 'e' : 36}},
{'a' : 17, 'b' : {'c' : 25, 'd' : "xxx", 'e' : 36}},
如何获取字段“d”的所有唯一值的频率计数器?即我的输出应该是 {'d' : { "xx" : 2, "x" : 1, "xxx" : 1} }
这甚至可能吗?感谢您对此的任何帮助。谢谢你。
我查找了聚合和 objectToArray 转换的文档以将地图转换为数组,并在 PyMongo 中尝试了以下操作
1)
db['sample'].aggregate([ { "$addFields" : { "b" : {"$objectToArray" : "$b"}}},\
{"$unwind" : "$b"},\
{"$group" : { "_id" : "$b.k",\
"count" : {"$sum" : "$b.v"}}} ])
这给出了每个可能的字段的累积计数 - 'c' : 25 + 5 + 25 + 25 例如。
2)
db['sample'].aggregate([ { "$addFields" : { "b" : {"$objectToArray" : "$b"}}},\
{"$unwind" : "$b"}, \
{"$group" : { "_id" : "$b.k", \
"count" : {"$sum" : 1 }}} ])
这给出了字段在文档中出现的总次数 - 'c' : 4, 'd' : 4 等。
解决方案
您基本上是在以错误的方式处理这个问题。您有一个明确的路径"b.d"
作为要聚合的键,无需将其转换为数组:
cursor = db.sample.aggregate([
{ "$group": {
"_id": "$b.d",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": None,
"data": { "$push": { "k": "$_id", "v": "$count" } }
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$data" }
}}
])
for doc in cursor:
print(doc)
退货
{ 'x': 1, 'xx': 2, 'xxx': 1 }
但这实际上是矫枉过正,因为实际上所有的工作都是在最初的$group
声明中完成的。您真正需要做的就是运行它并获取结果并将它们组合到一个字典中作为所需的输出:
cursor = db.sample.aggregate([
{ "$group": {
"_id": "$b.d",
"count": { "$sum": 1 }
}}
])
data = list(cursor)
result = reduce(
lambda x,y:
dict(x.items() + { y['_id']: y['count'] }.items()), data,{})
print(result)
它返回完全相同的东西:
{ 'x': 1, 'xx': 2, 'xxx': 1 }
此外,它无需添加其他聚合阶段和运算符所需的体操,并且您没有更改从服务器真正返回的内容,因为初始$group
响应基本上是:
{ "_id" : "xxx", "count" : 1 }
{ "_id" : "xx", "count" : 2 }
{ "_id" : "x", "count" : 1 }
因此,这里真正的教训是,虽然您可以在聚合管道中进行花哨的操作,但您真正应该考虑的是,当替代方案更清晰、更易读的代码时,您可能不应该这样做。
作为参考,尽管发生的所有事情都是使用和键创建数组的额外$group
用途,正如在下一个管道阶段所预期的那样。下一阶段用于从前一阶段创建的数组中获取输出,并且基本上将其转换为对象/字典。$push
k
v
$replaceRoot
$arrayToObject
相比之下,它reduce
正在做完全相同的事情。我们基本上将游标结果放入 alist
中,以便 python 函数可以作用于该列表。然后,只需遍历该列表中的文档,这些文档始终具有_id
“计数”输出(这里我们使用count
)的键和另一个命名属性,并简单地将它们转换为最终字典输出的键和值对。
只是为了好玩,基于您最初尝试的一些东西可能是:
db.sample.aggregate([
{ "$addFields": { "b": { "$objectToArray": "$b" } } },
{ "$unwind": "$b" },
{ "$group": {
"_id": {
"_id": "$b.k",
"k": "$b.v"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"data": { "$push": { "k": { "$toString": "$_id.k" }, "v": "$count" } }
}},
{ "$addFields": {
"data": { "$arrayToObject": "$data" }
}}
])
哪个会返回:
{ "_id" : "c", "data" : { "25" : 3, "5" : 1 } }
{ "_id" : "e", "data" : { "36" : 4 } }
{ "_id" : "d", "data" : { "xxx" : 1, "xx" : 2, "x" : 1 } }
同样,没有额外的管道阶段来转换的相同结果来自使用map
和reduce
使用 python:
cursor = db.sample.aggregate([
{ "$addFields": { "b": { "$objectToArray": "$b" } } },
{ "$unwind": "$b" },
{ "$group": {
"_id": {
"_id": "$b.k",
"k": "$b.v"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"data": { "$push": { "k": "$_id.k", "v": "$count" } }
}}
])
data = list(cursor)
result = map(lambda d: {
'_id': d['_id'],
'data': reduce(lambda x,y:
dict(x.items() + { y['k']: y['v'] }.items()), d['data'],
{})
},data)
推荐阅读
- android - 如何从 TextAppearance 获取字母间距
- jquery - 有没有办法使用 .load() 方法从页面获取数据而不加载该页面上的所有图像?
- c# - 捕获使用的 USB 设备名称。VB.net
- image-processing - 如何解决此错误?import cv2 ImportError: DLL load failed: 找不到指定的模块
- php - 在laravel中生成视图时如何使php vars保持不变?
- javascript - 如何在页面加载时将变量传递给输入值?
- java - 在进行休息调用以触发 Jenkins 作业时获取“400 此页面需要表单提交”
- reactjs - 本地存储在 redux 令牌身份验证中的页面刷新时被清除
- javascript - 节点JS | 如何将图像从套接字服务器发送到其他服务器
- google-api - 如何在 Google Places Autocomplete API 中获取位置