Скользящие средние с помощью агрегационной структуры MongoDB?

Если у вас есть 50-летние данные о температуре (ежедневно) (например), как бы вы вычисляли скользящие средние с использованием трехмесячных интервалов за этот период времени? Можете ли вы сделать это с одним запросом или у вас должно быть несколько запросов?

Example Data
01/01/2014 = 40 degrees
12/31/2013 = 38 degrees
12/30/2013 = 29 degrees
12/29/2013 = 31 degrees
12/28/2013 = 34 degrees
12/27/2013 = 36 degrees
12/26/2013 = 38 degrees
.....
4 ответа

Теперь структура agg имеет $map и $reduce и $range построенные таким образом, что обработка массивов намного более прямолинейна. Ниже приведен пример расчета скользящей средней по набору данных, в котором вы хотите отфильтровать какой-либо предикат. Базовая установка - это каждый документ, содержащий критерии фильтрации и значение, например

{sym: "A", d: ISODate("2018-01-01"), val: 10}
{sym: "A", d: ISODate("2018-01-02"), val: 30}

Вот:

// This controls the number of observations in the moving average:
days = 4;
c=db.foo.aggregate([
// Filter down to what you want. This can be anything or nothing at all.
{$match: {"sym": "S1"}}
// Ensure dates are going earliest to latest:
,{$sort: {d:1}}
// Turn docs into a single doc with a big vector of observations, e.g.
// {sym: "A", d: d1, val: 10}
// {sym: "A", d: d2, val: 11}
// {sym: "A", d: d3, val: 13}
// becomes
// {_id: "A", prx: [ {v:10,d:d1}, {v:11,d:d2}, {v:13,d:d3} ] }
//
// This will set us up to take advantage of array processing functions!
,{$group: {_id: "$sym", prx: {$push: {v:"$val",d:"$date"}} }}
// Nice additional info. Note use of dot notation on array to get
// just scalar date at elem 0, not the object {v:val,d:date}:
,{$addFields: {numDays: days, startDate: {$arrayElemAt: [ "$prx.d", 0 ]}} }
// The Juice! Assume we have a variable "days" which is the desired number
// of days of moving average.
// The complex expression below does this in python pseudocode:
//
// for z in range(0, size of value vector - # of days in moving avg):
// seg = vector[n:n+days]
// values = seg.v
// dates = seg.d
// for v in seg:
// tot += v
// avg = tot/len(seg)
// 
// Note that it is possible to overrun the segment at the end of the "walk"
// along the vector, i.e. not enough date-values. So we only run the
// vector to (len(vector) - (days-1).
// Also, for extra info, we also add the number of days *actually* used in the
// calculation AND the as-of date which is the tail date of the segment!
//
// Again we take advantage of dot notation to turn the vector of
// object {v:val, d:date} into two vectors of simple scalars [v1,v2,...]
// and [d1,d2,...] with $prx.v and $prx.d
//
,{$addFields: {"prx": {$map: {
 input: {$range:[0,{$subtract:[{$size:"$prx"}, (days-1)]}]} ,
 as: "z",
 in: {
 avg: {$avg: {$slice: [ "$prx.v", "$$z", days ] } },
 d: {$arrayElemAt: [ "$prx.d", {$add: ["$$z", (days-1)] } ]}
 }
 }}
 }}
 ]);

Это может привести к следующему результату:

{
 "_id" : "S1",
 "prx" : [
 {
 "avg" : 11.738793632512115,
 "d" : ISODate("2018-09-05T16:10:30.259Z")
 },
 {
 "avg" : 12.420766702631376,
 "d" : ISODate("2018-09-06T16:10:30.259Z")
 },
 ...
 ],
 "numDays" : 4,
 "startDate" : ISODate("2018-09-02T16:10:30.259Z")
}


То, как я хотел бы сделать это в MongoDB, - это поддерживать текущую сумму за последние 90 дней в документе за каждое значение дня, например.

{"day": 1, "tempMax": 40, "tempMaxSum90": 2232}
{"day": 2, "tempMax": 38, "tempMaxSum90": 2230}
{"day": 3, "tempMax": 36, "tempMaxSum90": 2231}
{"day": 4, "tempMax": 37, "tempMaxSum90": 2233}

Всякий раз, когда в коллекцию нужно добавить новую точку данных, вместо того, чтобы считывать и суммировать 90 значений, вы можете эффективно рассчитать следующую сумму двумя простыми запросами: одно добавление и одно вычитание, подобное этому (psuedo-code):

tempMaxSum90(day) = tempMaxSum90(day-1) + tempMax(day) - tempMax(day-90)

90-дневная скользящая средняя за каждый день составляет всего 90-дневную сумму, деленную на 90.

Если вы хотите также предлагать скользящие средние по разным временным шкалам (например, 1 неделя, 30 дней, 90 дней, 1 год), вы можете просто поддерживать массив сумм с каждым документом вместо одной суммы, одну сумму для каждого требуемого масштаба времени.

Этот подход требует дополнительного пространства для хранения и дополнительной обработки для вставки новых данных, однако он подходит для большинства сценариев построения временных рядов, где новые данные собираются относительно медленно, и желательно быстрое извлечение.


Думаю, у меня может быть ответ на мой вопрос. Сокращение карты сделало бы это. Первое использование испускает, чтобы сопоставить каждому документу соседи, чтобы он усреднялся, а затем использовать сокращение для каждого массива... и что новый массив средних значений должен быть сверхурочным графиком скользящих средних, поскольку он будет новым интервалом даты, который вы заботитесь о

Думаю, мне нужно было лучше понять, как лучше...

:)

Например, если мы хотим сделать это в памяти (позже мы можем создавать коллекции)

GIST https://gist.github.com/mrgcohen/3f67c597a397132c46f7

Правильно ли это выглядит?


Я не верю, что структура агрегации может сделать это для нескольких дат в текущей версии (2.6) или, по крайней мере, не может сделать это без какой-либо серьезной гимнастики. Причина в том, что конвейер агрегации обрабатывает только один документ за один раз и только один документ, поэтому необходимо каким-то образом создать документ для каждого дня, который содержит соответствующую информацию за 3 месяца. Это будет как этап $group, который будет вычислять среднее значение, что означает, что на предыдущем этапе было бы произведено около 90 копий записи каждого дня с помощью некоторого отличительного ключа, который можно использовать для $group.

Таким образом, я не вижу способа сделать это более чем за одну дату за один раз в одном агрегате. Я был бы счастлив ошибаться и должен отредактировать/удалить этот ответ, если кто-то найдет способ сделать это, даже если это так сложно, что это не практично. Функция типа PostgreSQL PARTITION выполнила бы здесь работу; возможно, эта функция будет добавлена ​​когда-нибудь.

licensed under cc by-sa 3.0 with attribution.