средние массивы mongodb по многим документам

Используя mongodb, у меня есть коллекция документов, где каждый документ имеет вектор фиксированной длины значений с плавающей запятой, как показано ниже:

    items = [
        {"id": "1", "vec": [1, 2, 0]},
        {"id": "2", "vec": [6, 4, 1]},
        {"id": "3", "vec": [3, 2, 2]},
    ]

Я хотел бы взять среднее значение этих векторов по строкам. В этом примере я ожидал бы, что результат вернется

[ (1 + 6 + 3) / 3, (2 + 4 + 2) / 3, (0 + 1 + 2) / 3 ]

Этот ответ очень близок к тому, что я ищу, но, насколько я могу судить, он будет работать только с векторами размера 2. mongoDB — среднее значение по массиву

Был предоставлен ответ, который не очень эффективен для больших массивов. Для контекста я использую ~ 700 векторов измерений.


person capturesteve    schedule 24.07.2020    source источник
comment
Пожалуйста, проверьте мой ответ, мне любопытно узнать, как он будет работать с такими большими массивами.   -  person thammada.ts    schedule 29.07.2020


Ответы (2)


Это должно работать: https://mongoplayground.net/p/PKXqmmW31nW

[
  {
    $group: {
      _id: null,
      a: {
        $push: {
          $arrayElemAt: ["$vec", 0]
        }
      },
      b: {
        $push: {
          $arrayElemAt: ["$vec", 1]
        }
      },
      c: {
        $push: {
          $arrayElemAt: ["$vec", 2]
        }
      }
    }
  },
  {
    $project: {
      a: {
        $avg: "$a"
      },
      b: {
        $avg: "$b"
      },
      c: {
        $avg: "$c"
      }
    }
  }
]

Что выводит:

[
  {
    "_id": null,
    "a": 3.3333333333333335,
    "b": 2.6666666666666665,
    "c": 1
  }
]

Вот более эффективный вариант без оператора $avg. Я оставлю другой ответ для справки. https://mongoplayground.net/p/rVERc8YjKZv

db.collection.aggregate([
  {
    $group: {
      _id: null,
      a: {
        $sum: {
          $arrayElemAt: ["$vec", 0]
        }
      },
      b: {
        $sum: {
          $arrayElemAt: ["$vec", 1]
        }
      },
      c: {
        $sum: {
          $arrayElemAt: ["$vec", 2]
        }
      },
      totalDocuments: {
        $sum: 1
      }
    }
  },
  {
    $project: {
      a: {
        $divide: ["$a", "$totalDocuments"]
      },
      b: {
        $divide: ["$b", "$totalDocuments"]
      },
      c: {
        $divide: ["$c", "$totalDocuments"]
      }
    }
  }
])
person GitGitBoom    schedule 24.07.2020
comment
Этот ответ технически работает, но, похоже, у него довольно плохое время выполнения для векторов больших размеров. Я запускаю агрегацию при поиске, которая соответствует примерно 10-50 тыс. документов с размерами векторов ~ 700 измерений. Мне нужно было передать для параметра allowDiskUse значение true, чтобы завершить вычисление. Любой способ ускорить это? - person capturesteve; 27.07.2020
comment
Да, я должен был уделить больше внимания размеру твоей коллекции. Я опубликовал несколько настроек, первый ответ — создание массивных массивов для использования оператора mongos $avg. Второй ответ просто суммирует каждое значение вектора по строкам и выполняет усреднение вручную. Время выполнения по-прежнему будет увеличиваться по мере увеличения размера вашей коллекции, поскольку вы по-прежнему сканируете каждый документ, но это значительно сократит использование памяти и также должно сократить время выполнения. - person GitGitBoom; 28.07.2020
comment
Правки работают хорошо. Насколько я могу судить, в конечном итоге мне нужно найти другое решение. На самом деле мы используем aws documentdb, который, по-видимому, выдает ошибки об ограничениях при агрегировании более 45 полей. Я не смог найти никакой документации о том, как изменить эти ограничения. Производительность на 45 полях также очень низкая по сравнению с реальным mongodb. (Я использовал образ докера mongodb локально для тестирования) - person capturesteve; 28.07.2020

Вы можете использовать $unwind для получения значений в отдельных документах, главное сохранить индекс значений. Затем вы можете использовать $group по индексу и вычислить среднее значение с помощью оператора $avg.

db.collection.aggregate([
  {
    $unwind: {
      path: "$vec",
      includeArrayIndex: "i" // unwind and keep index
    }
  },
  {
    $group: {
      _id: "$i", // group by index
      avg: { $avg: "$vec" }
    }
  }, // at this stage, you already get all the values you need, in separate documents. The following stages will put all the values in an array
  {
    $sort: { _id: 1 }
  },
  {
    $group: {
      _id: null,
      avg: { $push: "$avg" }
    }
  }
])

Игровая площадка Mongo

person thammada.ts    schedule 27.07.2020