我的列表有一对时间戳/日期时间和我的mongodb给出的值。(mongodb的数据组织如下:)
"timestamp" : ISODate("2014-01-01T00:00:00.000Z"),
realPower" : {
"0" : {
"0": 545.5,
"15" : 614.5,
"30" : 586.25,
"45" : 565.75
},
"1" : {
"0" : 574.5,
"15" : 549.5,
"30" : 564,
"45" : 545.75
},
( … )
"22" : {
"0" : 604.75,
"15" : 605,
"30" : 605,
"45" : 605
},
"23" : {
"0" : 604.75,
"15" : 605,
"30" : 605,
"45" : 604.5
}
}
}
我已经将mongodb项目转换为以下列表
列出项目(一天):
[datetime.datetime(2014, 1, 1, 1, 0), 545.5]
[datetime.datetime(2014, 1, 1, 1, 15), 614.5]
[datetime.datetime(2014, 1, 1, 1, 30), 586.25]
[datetime.datetime(2014, 1, 1, 1, 45), 565.75]
(...)
[datetime.datetime(2014, 1, 1, 23, 45), 604.5]
我有一个方法可以为我的数据生成一个很好的间隔:
def date_span(start_date, end_date, data):
delta = datetime.timedelta(hours=15)
current_date = start_date.replace(minute=0)
while current_date < end_date:
yield current_date
current_date += delta
但是,如何将列表项的数据与新的时间跨度项合并并求和?我想总结一下给定时间的值。例如,总结每小时、每天、每周、每月、每年的值。有提示吗?
当前存储数据的方式在这里并没有真正帮助您。您最好将"transform"更进一步,以这种方式存储数据:
{ "timestamp": ISODate("2014-01-01T00:00:00.000Z"), "realPower": 545.5 }
{ "timestamp": ISODate("2014-01-01T00:15:00.000Z"), "realPower": 614.5 }
{ "timestamp": ISODate("2014-01-01T00:30:00.000Z"), "realPower": 586.25 }
{ "timestamp": ISODate("2014-01-01T00:45:00.000Z"), "realPower": 565.75 }
{ "timestamp": ISODate("2014-01-01T01:00:00.000Z"), "realPower": 574.5 }
{ "timestamp": ISODate("2014-01-01T01:15:00.000Z"), "realPower": 549.5 }
{ "timestamp": ISODate("2014-01-01T01:30:00.000Z"), "realPower": 564 }
{ "timestamp": ISODate("2014-01-01T01:45:00.000Z"), "realPower": 545.75 }
{ ... }
{ "timestamp": ISODate("2014-01-01T23:00:00.000Z"), "realPower": 604.75 }
{ "timestamp": ISODate("2014-01-01T23:15:00.000Z"), "realPower": 605 }
{ "timestamp": ISODate("2014-01-01T23:30:00.000Z"), "realPower": 605 }
{ "timestamp": ISODate("2014-01-01T23:45:00.000Z"), "realPower": 604.5 }
原因是你目前拥有的"子文档"结构不能很好地转换为服务器端聚合方法。这确实与"数据的一部分"被表示为"键"有关,这不是一个很好的模式。
也有使用表示间隔的"子文档"进行结构的情况,但通常这些情况涉及在特定间隔内保持离散值的"桶",主要是为了避免"嵌套数组",这通常不利于更新。
但是在建议的形式中,您的查询是应用聚合框架的简单问题。有日期操作符可用来处理特定间隔的分组:
db.collection.aggregate([
// Match documents between dates
{ "$match": {
"timestamp": { "$gte": startDate, "$lte": endDate }
}},
// Group by hour
{ "$group": {
"_id": {
"year": { "$year": "$timestamp" },
"month": { "$month": "$timestamp" },
"day": { "$dayOfMonth": "$timestamp" },
"hour": { "$hour": "$timestamp" }
},
"avgPower": { "$avg": "$realPower" }
}}
])
本质上你定义了一个"分组键"的时间戳值和其他值(s)你想在结果中应用任何Group累加运算符,在本例中是平均值。
除了使用日期聚合操作符之外,您还可以将date对象转换为epoch时间戳值,并对间隔应用日期数学。其中epochDate
是作为参数传递的日期对象,表示"1970-01-01",即0 epoch日期:
db.collection.aggregate([
//Match documents between dates
{ "$match": {
"timestamp": { "$gte": startDate, "$lte": endDate }
}},
//Group by day: 1000 * 60 * 60 * 24 = milliseconds in a day
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [
"$timestamp", epochDate
]},
{ "$mod": [
{ "$subtract": [
"$timestamp", epochDate
]},
1000 * 60 * 60 * 24
]}
]
},
"sumPower": { "$sum": "$realPower" }
}}
])
如果需要的话,可以将生成的时间戳值反馈到date对象中。这里的技巧是,做一些类似于从一个日期对象中"减去"另一个日期对象的操作会产生毫秒差,以数字表示。
对于当前的结构,然而,你正在寻找一个JavaScript处理与mapReduce在服务器端处理这个。由于需要"解释"代码,这将发生得慢得多。
在mapper中,按月分组"sum"
function() {
var values = [];
var realPower = this.realPower;
for ( var k in realPower ) {
for ( var i in k ) {
values.push( realPower[k][i] );
}
}
emit(
{
"year": this.timestamp.getFullYear(),
"month": this.timestamp.getMonth() + 1
},
{ "values": values }
);
}
然后是减速器:
function(key,values) {
var result = { "values": [] };
values.forEach(function(value) {
value.values.forEach(function(item) {
result.values.push( item );
}
}
}
并在finalize函数中处理"sum",以防在给定分组中只发出单个键:
function(key,value) {
return Array.sum( value.values );
}
并使用查询调用mapReduce:
results = db.collection.inline_map_reduce(
map,
reduce,
query={ "timestamp": { "$gte": startDate, "$lte": endDate } },
finalize=finalize
)
所以一般来说有点丑,当然也比较慢。正如您在"mapper"定义中看到的那样,需要遍历"子文档"结构,否则将挑选出"特定"键,例如每小时累积的情况。
在任何一种情况下,服务器端处理通常是您想要的方式,因为数据库服务器最有可能比应用服务器有更多的grunt,或者至少应该是这样。
尝试改变数据结构。查询和进一步聚合的回报大于一次性数据操作的成本。