Вычисление частотомера всех уникальных значений вложенного поля - PullRequest
1 голос
/ 24 марта 2019

Я хочу найти частотный счетчик всех уникальных значений вложенного поля в 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 и т. д.

1 Ответ

0 голосов
/ 24 марта 2019

Вы в основном подходите к этому неправильно. У вас есть четкий путь к "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 делает то же самое. Мы в основном принимаем результаты курсора в list, чтобы функции 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)
...