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
}
}
]
)
Podemos usar o pipeline acima na versão 3.4+ para fazer isso. No pipeline, usamos o
$addFields
estágio de tubulação. para adicionar o array do índice de elementos do "time_series" para fazer o documento, também invertemos o array da série temporal e o adicionamos ao documento usando respectivamente o $range
e $reverseArray
operadores Invertemos o array aqui porque o elemento na posição
p
na matriz é sempre maior que o elemento na posição p+1
o que significa que [p] - [p+1] <0
e não queremos usar o $multiply
aqui. (veja pipeline para a versão 3.2) Em seguida,
$zipamos
os dados da série temporal com a matriz de índices e aplicou um substract
expressão para o array resultante usando o $map
operador. Em seguida,
$slice
o resultado para descartar o null/None
valor da matriz e reverteu o resultado. Na versão 3.2, podemos usar o
$unwind
operador para descontrair nosso array e inclua o índice de cada elemento no array especificando um documento como operando em vez do tradicional "caminho" prefixado por $ . Em seguida no pipeline, precisamos
$group
nossos documentos e use o $push
operador acumulador para retornar uma matriz de subdocumentos que se parecem com isso:{
"_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) }
]
}
Finalmente vem o
$project
palco. Nesta etapa, precisamos usar o $map
operador para aplicar uma série de expressões a cada elemento no array recém-computado no $group
palco. Aqui está o que está acontecendo dentro do
$map
(veja $map
como um loop for) em expressão:Para cada subdocumento, atribuímos o valor campo para uma variável usando o
$let
operador variável. Em seguida, subtraímos o valor do valor do campo "valor" do próximo elemento na matriz. Como o próximo elemento na matriz é o elemento no índice atual mais um, tudo o que precisamos é a ajuda do
$arrayElemAt
operador e um simples $add
ção do índice do elemento atual e 1
. O
$subtract
expressão retorna um valor negativo, então precisamos multiplicar o valor por -1
usando o $multiply
operador. Também precisamos
$filter
a matriz resultante porque o último elemento é Nenhum
ou null
. A razão é que quando o elemento atual é o último elemento, $subtract
return Nenhum
porque o índice do próximo elemento é igual ao tamanho da matriz. 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
]
}
}
}
}
}
])
Outra opção que acho menos eficiente é realizar uma operação map/reduce em nossa coleção usando o
map_reduce
método. >>> 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')}
Você também pode recuperar todo o documento e usar o
numpy.diff
para retornar a derivada assim:import numpy as np
for document in collection.find({}, {'time_series': 1}):
result = np.diff(document['time_series'])