Подтвердить что ты не робот

Вычислить производные первого порядка с системой агрегации MongoDB

Можно ли вычислить производную первого порядка, используя совокупную структуру?

Например, у меня есть данные:

{time_series : [10,20,40,70,110]}

Я пытаюсь получить вывод вроде:

{derivative : [10,20,30,40]}
4b9b3361

Ответ 1

db.collection.aggregate(
    [
      {
        "$addFields": {
          "indexes": {
            "$range": [
              0,
              {
                "$size": "$time_series"
              }
            ]
          },
          "reversedSeries": {
            "$reverseArray": "$time_series"
          }
        }
      },
      {
        "$project": {
          "derivatives": {
            "$reverseArray": {
              "$slice": [
                {
                  "$map": {
                    "input": {
                      "$zip": {
                        "inputs": [
                          "$reversedSeries",
                          "$indexes"
                        ]
                      }
                    },
                    "in": {
                      "$subtract": [
                        {
                          "$arrayElemAt": [
                            "$$this",
                            0
                          ]
                        },
                        {
                          "$arrayElemAt": [
                            "$reversedSeries",
                            {
                              "$add": [
                                {
                                  "$arrayElemAt": [
                                    "$$this",
                                    1
                                  ]
                                },
                                1
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  }
                },
                {
                  "$subtract": [
                    {
                      "$size": "$time_series"
                    },
                    1
                  ]
                }
              ]
            }
          },
          "time_series": 1
        }
      }
    ]
)

Мы можем использовать конвейер выше в версии 3.4+ для этого. В конвейере мы используем этап $addFields. оператор, чтобы добавить массив индекса элементов "time_series" для выполнения документа, мы также изменили массив временных рядов и добавили его в документ, используя соответственно $range и $reverseArray Операторы

Мы изменили массив здесь, потому что элемент в позиции p в массиве всегда больше, чем элемент в позиции p+1, что означает, что [p] - [p+1] < 0 и мы не хотим использовать $multiply (см. конвейер для версии 3.2)

Далее мы $zipped данные временных рядов с массивом индексов и применили выражение substract к полученному массиву с использованием $map.

Затем мы получаем $slice результат, чтобы отбросить значение null/None из массива и повторно отменить результат.


В 3.2 мы можем использовать оператор $unwind, чтобы развернуть наш массив и включить индекс каждого элемента в массив, указав document в качестве операнда вместо традиционного "пути" с префиксом $.

Далее в конвейере нам нужно $group наши документы и использовать > t215 > возвращает массив поддокументов, который выглядит следующим образом:

{
    "_id" : ObjectId("57c11ddbe860bd0b5df6bc64"),
    "time_series" : [
        { "value" : 10, "index" : NumberLong(0) },
        { "value" : 20, "index" : NumberLong(1) },
        { "value" : 40, "index" : NumberLong(2) },
        { "value" : 70, "index" : NumberLong(3) },
        { "value" : 110, "index" : NumberLong(4) }
    ]
}

Наконец приходит этап $project. На этом этапе нам нужно использовать оператор $map, чтобы применить серию выражений к каждому элементу в новом вычисляемом массиве в $group.

Вот что происходит внутри $map (см. $map как цикл цикла) в:

Для каждого поддокумента мы присваиваем значение значению переменной переменной $let. Затем мы вычтем его значение из значения поля "значение" следующего элемента в массиве.

Поскольку следующий элемент в массиве - это элемент с текущим индексом плюс один, все, что нам нужно, это помощь $arrayElemAt и простой $add ition текущего индекса элемента и 1.

$subtract выражение возвращает отрицательное значение, поэтому нам нужно умножить значение на -1 с помощью $multiply.

Нам также нужно $filter приведенный массив, потому что последний элемент None или null. Причина в том, что когда текущий элемент является последним элементом, $subtract return None, потому что индекс следующего элемента равен размеру массива.

db.collection.aggregate([
  {
    "$unwind": {
      "path": "$time_series",
      "includeArrayIndex": "index"
    }
  },
  {
    "$group": {
      "_id": "$_id",
      "time_series": {
        "$push": {
          "value": "$time_series",
          "index": "$index"
        }
      }
    }
  },
  {
    "$project": {
      "time_series": {
        "$filter": {
          "input": {
            "$map": {
              "input": "$time_series",
              "as": "el",
              "in": {
                "$multiply": [
                  {
                    "$subtract": [
                      "$$el.value",
                      {
                        "$let": {
                          "vars": {
                            "nextElement": {
                              "$arrayElemAt": [
                                "$time_series",
                                {
                                  "$add": [
                                    "$$el.index",
                                    1
                                  ]
                                }
                              ]
                            }
                          },
                          "in": "$$nextElement.value"
                        }
                      }
                    ]
                  },
                  -1
                ]
              }
            }
          },
          "as": "item",
          "cond": {
            "$gte": [
              "$$item",
              0
            ]
          }
        }
      }
    }
  }
])

Другим вариантом, который, по моему мнению, является менее эффективным, является выполнение операции map/reduce в нашей коллекции с помощью метода map_reduce.

>>> import pymongo
>>> from bson.code import Code
>>> client = pymongo.MongoClient()
>>> db = client.test
>>> collection = db.collection
>>> mapper = Code("""
...               function() {
...                 var derivatives = [];
...                 for (var index=1; index<this.time_series.length; index++) {
...                   derivatives.push(this.time_series[index] - this.time_series[index-1]);
...                 }
...                 emit(this._id, derivatives);
...               }
...               """)
>>> reducer = Code("""
...                function(key, value) {}
...                """)
>>> for res in collection.map_reduce(mapper, reducer, out={'inline': 1})['results']:
...     print(res)  # or do something with the document.
... 
{'value': [10.0, 20.0, 30.0, 40.0], '_id': ObjectId('57c11ddbe860bd0b5df6bc64')}

Вы также можете получить весь документ и использовать numpy.diff, чтобы вернуть производную, как это:

import numpy as np


for document in collection.find({}, {'time_series': 1}):
    result = np.diff(document['time_series']) 

Ответ 2

это немного грязно, но возможно что-то вроде этого?

use test_db
db['data'].remove({})
db['data'].insert({id: 1, time_series: [10,20,40,70,110]})

var mapF = function() {
    emit(this.id, this.time_series);
    emit(this.id, this.time_series);
};

var reduceF = function(key, values){
    var n = values[0].length;
    var ret = [];
    for(var i = 0; i < n-1; i++){
        ret.push( values[0][i+1] - values[0][i] );
    }
    return {'gradient': ret};
};

var finalizeF = function(key, val){
    return val.gradient;
}

db['data'].mapReduce(
    mapF,
    reduceF,
    { out: 'data_d1', finalize: finalizeF }
)

db['data_d1'].find({})

"Стратегия" здесь состоит в том, чтобы испускать данные, которые будут работать в два раза, чтобы они были доступны на этапе уменьшения, вернуть объект, чтобы избежать сообщения "уменьшить → несколько не поддерживаемых пока", а затем отфильтровать массив в финализаторе.

Этот script затем производит:

MongoDB shell version: 3.2.9
connecting to: test
switched to db test_db
WriteResult({ "nRemoved" : 1 })
WriteResult({ "nInserted" : 1 })
{
    "result" : "data_d1",
        "timeMillis" : 13,
        "counts" : {
            "input" : 1,
            "emit" : 2,     
            "reduce" : 1,           
            "output" : 1                    
        },                                      
        "ok" : 1                                    
}                                                   
{ "_id" : 1, "value" : [ 10, 20, 30, 40 ] }         
bye

В качестве альтернативы можно переместить всю обработку в финализатор (reduceF здесь не вызывается, так как предполагается, что mapF испускает уникальные ключи):

use test_db
db['data'].remove({})
db['data'].insert({id: 1, time_series: [10,20,40,70,110]})

var mapF = function() {
    emit(this.id, this.time_series);
};

var reduceF = function(key, values){
};

var finalizeF = function(key, val){
    var x = val;
    var n = x.length;

    var ret = [];
    for(var i = 0; i < n-1; i++){
        ret.push( x[i+1] - x[i] );
    }
    return ret;
}

db['data'].mapReduce(
    mapF,
    reduceF,
    { out: 'data_d1', finalize: finalizeF }
)

db['data_d1'].find({})