Можно ли вычислить производную первого порядка, используя совокупную структуру?
Например, у меня есть данные:
{time_series : [10,20,40,70,110]}
Я пытаюсь получить вывод вроде:
{derivative : [10,20,30,40]}
Можно ли вычислить производную первого порядка, используя совокупную структуру?
Например, у меня есть данные:
{time_series : [10,20,40,70,110]}
Я пытаюсь получить вывод вроде:
{derivative : [10,20,30,40]}
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'])
это немного грязно, но возможно что-то вроде этого?
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({})