Python日期值的值和值

时间:2014-08-06 14:22:12

标签: python mongodb mapreduce aggregation-framework

我的列表中有一对时间戳/日期时间和我的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

但是,如何将列表项的数据与新的时间跨度项组合并求和?我想在给定时间内总结这些值。例如。总结每小时,每天,每周,每月,每年的值。任何提示?

1 个答案:

答案 0 :(得分:1)

当前存储数据的方式并不能真正帮助您。最好还是将“转换”更进一步,并以这种方式存储数据:

{ "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 }

原因是您目前拥有的“子文档”结构无法很好地转换为服务器端聚合方法。这实际上与“部分数据”被表示为“关键”有关,这不是一个非常好的模式。

有些情况下使用表示间隔的“子文档”进行结构化,但通常这些涉及将离散值的“桶”保持在特定的时间间隔内,并且主要的一点是避免“嵌套数组”,这通常是不适合更新。

但在提议的表单中,您的查询只是应用aggregation framework的简单问题。有date operators可用于处理特定时间间隔内的分组:

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" }
    }}
])

基本上,您可以在时间戳值中定义“分组键”,并在结果中定义其他值,您可以应用Group accumulator operator中的任何一个,在这种情况下是平均值。

或者使用date aggregation operators,您还可以将日期对象转换为纪元时间戳值,并应用间隔的日期数学。其中epochDate这里是作为参数传递的日期对象,表示“1970-01-01”,即0纪元日期:

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" }
    }}
])

如果您需要,可以将生成的时间戳值反馈到日期对象中。这里的诀窍是,从另一个日期对象中“减去”一个日期对象会导致以毫秒表示的数字差异。


使用当前结构,您正在寻找使用mapReduce进行JavaScript处理以在服务器端处理此问题。由于需要“解释”代码,这种情况会慢得多。

所以在映射器中,每月分组“总和”

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
)

所以一般来说有点丑陋而且肯定更慢。正如您在“映射器”定义中所看到的那样,需要遍历“子文档”结构或者选择“特定”键,例如每小时累积的情况。


在任何一种情况下,服务器端处理通常都是您想要的方式,因为您的数据库服务器最有可能比您的应用程序服务器更加笨拙,或者至少应该是这种情况。

尝试改变数据结构。查询和进一步聚合的回报超过了一次性数据操作的成本。