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
}
}
]
)
Nous pouvons utiliser le pipeline ci-dessus dans la version 3.4+ pour ce faire. Dans le pipeline, nous utilisons le $addFields
étape du pipeline. pour ajouter le tableau de l'index des éléments de "time_series" au document, nous avons également inversé le tableau de la série temporelle et l'avons ajouté au document en utilisant respectivement le $range
et $reverseArray
opérateurs
Nous avons inversé le tableau ici car l'élément à la position p
dans le tableau est toujours supérieur à l'élément à la position p+1
ce qui signifie que [p] - [p+1] < 0
et nous ne voulons pas utiliser le $multiply
ici.(voir pipeline pour la version 3.2)
Ensuite, nous $zipped
les données de la série chronologique avec le tableau des index et appliqué un substract
expression au tableau résultant en utilisant le $map
opérateur.
Nous $slice
le résultat pour supprimer le null/None
valeur du tableau et a inversé le résultat.
En 3.2, nous pouvons utiliser le $unwind
opérateur pour se détendre notre tableau et inclure l'index de chaque élément du tableau en spécifiant un document comme opérande au lieu du "chemin" traditionnel préfixé par $ .
Ensuite dans le pipeline, nous devons $group
nos documents et utilisez le $push
opérateur d'accumulateur pour renvoyer un tableau de sous-documents qui ressemble à ceci :
{
"_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) }
]
}
Vient enfin le $project
organiser. Dans cette étape, nous devons utiliser le $map
opérateur pour appliquer une série d'expressions à chaque élément du tableau nouvellement calculé dans le $group
scène.
Voici ce qui se passe à l'intérieur de la $map
(voir $map
comme une boucle for) dans expression :
Pour chaque sous-document, nous attribuons la valeur champ à une variable en utilisant le $let
opérateur variable. Nous soustrayons ensuite sa valeur de la valeur du champ "valeur" de l'élément suivant du tableau.
Étant donné que l'élément suivant dans le tableau est l'élément à l'index actuel plus un, tout ce dont nous avons besoin est l'aide du $arrayElemAt
opérateur et un simple $add
ition de l'index de l'élément courant et 1
.
Le $subtract
l'expression renvoie une valeur négative, nous devons donc multiplier la valeur par -1
en utilisant le $multiply
opérateur.
Nous devons également $filter
le tableau résultant car le dernier élément est None
ou null
. La raison est que lorsque l'élément courant est le dernier élément, $subtract
renvoie None
car l'index de l'élément suivant est égal à la taille du tableau.
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
]
}
}
}
}
}
])
Une autre option qui, à mon avis, est moins efficace consiste à effectuer une opération map/reduce sur notre collection en utilisant le map_reduce
méthode.
>>> 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')}
Vous pouvez également récupérer tout le document et utiliser le numpy.diff
pour retourner la dérivée comme ceci :
import numpy as np
for document in collection.find({}, {'time_series': 1}):
result = np.diff(document['time_series'])