LoginSignup
1
0

More than 5 years have passed since last update.

javascriptの.map、.reduceで区間の平均値を計算する

Last updated at Posted at 2016-09-01

javascriptの.map、.reduceを使って、時系列に記録されたデーターの区間ごとの平均値を計算しました。Javascript and MapReduceというサイトを参考にしました。

やりたいこと

次のような時系列データーがあるとします。この例ではd1とd2というデーターが30秒ごとのタイムスタンプで記録されています。例は12件、6分間のデーターですが、実際にはこれが延々と続いていると考えてください。

var data = [
    {"created":"2016-04-04T15:00:20.199Z","d1":3.2,"d2":1},
    {"created":"2016-04-04T15:00:50.200Z","d1":3.1,"d2":1.1},
    {"created":"2016-04-04T15:01:20.204Z","d1":3.2,"d2":1.1},
    {"created":"2016-04-04T15:01:50.201Z","d1":3.2,"d2":1.1},
    {"created":"2016-04-04T15:02:20.204Z","d1":3.1,"d2":1.1},
    {"created":"2016-04-04T15:02:50.206Z","d1":3,  "d2":1.1},
    {"created":"2016-04-04T15:03:20.193Z","d1":2.9,"d2":1},
    {"created":"2016-04-04T15:03:50.199Z","d1":3.3,"d2":1.1},
    {"created":"2016-04-04T15:04:20.196Z","d1":2.9,"d2":1.1},
    {"created":"2016-04-04T15:04:50.202Z","d1":3,  "d2":1},
    {"created":"2016-04-04T15:05:20.197Z","d1":3.1,"d2":1.1},
    {"created":"2016-04-04T15:05:50.196Z","d1":3.1,"d2":1.1}
];

やりたいことは、この時系列のデーターから5分間とか10分間とかの区間の平均値を計算することです。計算する区間は1分間、2分間、3分間、...20分間、30分間、60分間と60を割り切れる数とし、60分以上は考えないことにします。以下は平均する区間を2分にした例で説明します。

.map

最初にやることは.map()で30秒ごとのデーターをn分、ここでは2分ごとのデーターにまとめることです。

    var n = 2;
    var result = data
    .map(function(item) {
        var _d = new Date(item.created);
        var d = _d.getFullYear() + '-' + (_d.getMonth() + 1) + '-' + _d.getDate()
            + ' ' + _d.getHours() + ':' + Math.floor(_d.getMinutes() / n) * n + ':0';
        return {key: d, value: {d1: item.d1, d2: item.d2}};
    });
    console.log(result);

タイムスタンプの分の部分をn分ごとに切り捨てて、同じ時刻にまとめています。これで元のデーターは次のようになります。時刻のフォーマットが元は協定世界時(UTC)で、処理後は現地時間になっていますが、支障はないので気にしないことにします。

var data = [
    {key: "2016-4-5 0:0:0", value: {"d1":3.2,"d2":1}},
    {key: "2016-4-5 0:0:0", value: {"d1":3.1,"d2":1.1}},
    {key: "2016-4-5 0:0:0", value: {"d1":3.2,"d2":1.1}},
    {key: "2016-4-5 0:0:0", value: {"d1":3.2,"d2":1.1}},
    {key: "2016-4-5 0:2:0", value: {"d1":3.1,"d2":1.1}},
    {key: "2016-4-5 0:2:0", value: {"d1":3,  "d2":1.1}},
    {key: "2016-4-5 0:2:0", value: {"d1":2.9,"d2":1}},
    {key: "2016-4-5 0:2:0", value: {"d1":3.3,"d2":1.1}},
    {key: "2016-4-5 0:4:0", value: {"d1":2.9,"d2":1.1}},
    {key: "2016-4-5 0:4:0", value: {"d1":3,  "d2":1}},
    {key: "2016-4-5 0:4:0", value: {"d1":3.1,"d2":1.1}},
    {key: "2016-4-5 0:4:0", value: {"d1":3.1,"d2":1.1}}
];

.reduce

次に.reduce()でn分ごとのデーターを足しこみ、データーの件数もカウントします。

    var n = 2;
    var result = data
    .map(function(item) {
        var _d = new Date(item.created);
        var d = _d.getFullYear() + '-' + (_d.getMonth() + 1) + '-' + _d.getDate()
            + ' ' + _d.getHours() + ':' + Math.floor(_d.getMinutes() / n) * n + ':0';
        return {key: d, value: {d1: item.d1, d2: item.d2}};
    })
    .reduce(function(last, now) {
        var index = last[0].indexOf(now.key);
        if (index == -1) {
            last[0].push(now.key);
            last[1].push(now.value);
            last[2].push(1);
        } else {
            last[1][index].d1 += now.value.d1;
            last[1][index].d2 += now.value.d2;
            last[2][index] += 1;
        }
        return last;
    }, [[], [], []]);
    console.log(result);

最初に空の初期値[[], [], []]を渡し、.map()の出力に対して、配列の1番目にタイムスタンプ、2番目にデーターを足し込んだ値、3番目にデーター件数をカウントしていきます。データーは次のように加工されます。30秒ごとのデーターを2分ごとに区切って計算すれば、データー件数が「4」になるのは自明な気もします。実際に使うときは外部のセンサーで測定して送信されたデーターで、到着間隔がゆらいだり、データーが欠落している可能性があることを想定したため、データー件数をカウントするようにしました。

[
    ["2016-4-5 0:0:0", "2016-4-5 0:2:0", "2016-4-5 0:4:0"],
    [{d1: 12.7, d2: 4.3}, {d1: 12.3, d2: 4.3}, {d1: 12.1, d2: 4.3}],
    [4, 4, 4]
]

出力

最後に、この配列を列ごとにまとめて、平均値を計算すればできあがりです。元のデーターと同じ形式で出力することにします。

    var n = 2;
    var result = data
    .map(function(item) {
        var _d = new Date(item.created);
        var d = _d.getFullYear() + '-' + (_d.getMonth() + 1) + '-' + _d.getDate()
            + ' ' + _d.getHours() + ':' + Math.floor(_d.getMinutes() / n) * n + ':0';
        return {key: d, value: {d1: item.d1, d2: item.d2}};
    })
    .reduce(function(last, now) {
        var index = last[0].indexOf(now.key);
        if (index == -1) {
            last[0].push(now.key);
            last[1].push(now.value);
            last[2].push(1);
        } else {
            last[1][index].d1 += now.value.d1;
            last[1][index].d2 += now.value.d2;
            last[2][index] += 1;
        }
        return last;
    }, [[], [], []]);
    var zip = [];
    result[0].forEach(function(key, index) {
        var jsondata = {created: key};
        jsondata.d1 = result[1][index].d1 / result[2][index];
        jsondata.d2 = result[1][index].d2 / result[2][index];
        zip.push(jsondata);
    });
    console.log(zip);

次のような結果が得られました。

[
    {created: "2016-4-5 0:0:0", d1: 3.175, d2: 1.075},
    {created: "2016-4-5 0:2:0", d1: 3.075, d2: 1.075},
    {created: "2016-4-5 0:4:0", d1: 3.025, d2: 1.075}
]

この処理は、IoTクラウドサービスAmbientで、受信したデーターをグラフ表示する際、区間の平均値を表示するところで使いました。

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0