是否可以使用聚合框架计算一阶导数?
例如,我有数据:
{time_series : [10,20,40,70,110]}
我正在尝试获取类似的输出:
{derivative : [10,20,30,40]}
答案 0 :(得分:7)
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" s元素索引的数组来做文档,我们还反转了时间序列数组,并分别使用$range
和{{ 3}}运算符
我们在这里颠倒了数组,因为数组中位置p
的元素总是大于位置p+1
的元素,这意味着[p] - [p+1] < 0
我们不想使用$reverseArray
这里。(参见版本3.2的管道)
接下来我们$zipped
使用索引数组的时间序列数据,并使用$map
运算符将$multiply
表达式应用于结果数组。
我们然后$slice
结果丢弃数组中的null/None
值并重新反转结果。
在3.2中,我们可以使用substract
运算符来展开我们的数组,并通过将文档指定为操作数而不是传统的&#34;来包含数组中每个元素的索引。路径&#34;以 $ 为前缀。
接下来在管道中,我们需要$unwind
我们的文档并使用$group
累加器运算符返回一个如下所示的子文档数组:
{
"_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) }
]
}
最后来到$push
阶段。在这个阶段,我们需要使用$project
运算符将一系列表达式应用于$group
阶段中新计算数组中的每个元素。
以下是$map
内部的内容(请参阅$map
作为for循环) 表达式
对于每个子文档,我们使用$map
变量运算符将值字段分配给变量。然后我们从&#34;值&#34;的值中减去它的值。数组中下一个元素的字段。
由于数组中的下一个元素是当前索引加上1的元素,我们所需要的只是$let
运算符的帮助和当前元素的简单$arrayElemAt
ition&#39; s索引和1
。
$add
表达式返回负值,因此我们需要使用$subtract
运算符将值乘以-1
。
我们还需要$multiply
结果数组,因为最后一个元素是None
或null
。原因是当当前元素是最后一个元素时,$subtract
返回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
]
}
}
}
}
}
])
我认为效率较低的另一个选项是使用$filter
方法对我们的集合执行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'])
答案 1 :(得分:4)
它有点脏,但也许是这样的?
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({})
这里的“策略”是发出要操作两次的数据,以便在reduce阶段可以访问,返回一个对象以避免消息“reduce - &gt;多个不支持”然后在终结器中过滤回数组。
然后该脚本产生:
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({})