Node-Redを利用して基本的なデータ分析をやってみました

# はじめに
弊社のデモ環境でオフィスのCO2濃度と気温を測定し、CSVファイルに出力する仕組みを作りました。
当該環境で出力される１日分のデータについて、Node-Redを使用して、基本的なデータ分析にチャレンジしてみることにしました。			
※データは１５秒間隔で取得していますが、ところどころ取れていないケースがあります。
　２０２２年１月頃の寒い時期のデータです。

# 基本的なデータ分析　今回の表示目標

　　CO2濃度と気温
　　　１日分の折れ線グラフ
　　　最大値、最小値、平均値、ヒストグラム
　　　散布図　

　　　が今回の表示目標となります。



【１】CO2濃度と気温　１日分の折れ線グラフ
　Chartノードを使用して、時系列の折れ線グラフを表示します

【２】CO2濃度と気温　最大値、最小値、平均値、ヒストグラム
　vegaノードを使用して、CO2濃度、気温のヒストグラムを表示します
　平均値、最大値、最小値を計算し表示します。　（最頻値はヒストグラムから目視で認識します）

　まずCO2についてです　![基本的なデータ分析　CO2グラフ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/16beb4cf-32ed-bed7-1441-bb3468ace1c3.png)　＜少し考えてみました＞
　　〇Co2濃度の折れ線グラフ
　　　夜間は600ppm前後で一定値、朝、始業前後で450ppmくらいまで下降、
　　　その後お昼にかけて550ppmくらいまで上昇、午後は少し下降し、夕方にかけて上昇。
　　　夕方から夜間は650ppmくらいまで上昇　　といった様子が読み取れます。
　　〇Co2濃度のヒストグラム他
　　　最頻値は600ppm付近。他に550ppm付近にも山があり、、２つのピークがあります
　　　平均は565ppm、最大値653ppm、最小値461ppm、変動幅は約200ppm

　次に温度についてです ![基本的なデータ分析　温度グラフ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/3015dfd5-5b88-dbf4-e0b7-6a1986cd2c1a.png)　＜少し考えてみました＞
　　〇温度の折れ線グラフ
　　　空調が聞いているためか１日を通して2.8℃程度の変動。
　　　夜間から朝にかけて気温は下落傾向、始業前後から１１時ころまでに２度ほど上昇。
　　　その後は１７時ころまで緩やかに上昇、以降は緩やかに下降　といった様子が読み取れます
　　〇温度のヒストグラム他
　　　最頻値は23.9℃付近。他に21.4℃付近にも山があり、２つのピークがあります。
　　　平均は22.68℃、最大値23.98℃、最小値21.23℃、変動幅は約2.8℃

＜両方のグラフから　もう少し考えてみました＞
　　　夜間から朝にかけては換気によって、CO2濃度、温度、両方の低下
　　　朝からお昼　人の出勤によるCO2濃度の上昇、出勤に合わせた空調コントロール（気温上昇）
　　　夕方以降CO2濃度が上がっていくところは推測がついておりません。
　　　　在館者は減っているはずで、植物がたくさんある環境でもなく。少数の人が残っていて、空調稼働が少なくなっているような状況でしょうか。。。。

【３】CO2濃度と気温　散布図
　vegaノードを使用して、縦軸に気温、横軸にCO2濃度をとり、散布図を表示します
　完成形は以下となります![基本的なデータ分析　散布図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/2020ae90-2e55-99f6-0bbd-d4b2e316c730.png)

　＜少し考えてみました＞
　　〇１日分の相関を見ると、直線的な相関はなさそうです。Ｇの字のように見えています。
　　　時間帯でわけてみると、直線的な相関が見えてくるかもしれません。


# Node-Redで構築　フローの全体図
　当方環境はNode-Red　ver2.2.2をWindows上で動かしております。
　フローの全体は以下となります。大きく、「CSVファイル読込」、「折れ線グラフ」、「平均・最大：最小・ヒストグラム」、「散布図」の４つに分かれております。　![基本的なデータ分析　フロー案.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/dfa80d53-8bef-466f-3437-9eacbb6d7909.png)



# Node-Redで構築　CSVファイル読込
　読み込むCSVファイルはヘッダがなく、以下のような形式です。
　１列目がタイムスタンプ、２列目がCO2濃度（整数）、３列目が温度となっております。
　＝＝＝
　2022/01/04 00:01:19,527,21.1625
　・・・
　2022/01/04 23:59:19,605,22.2250
　＝＝＝

　まず、injectノード（file）ではfilenameをセットします（他に３つinjectノードを配置し、他のファイルも読めるようにしています。）
　次に、read fileノード　出力は文字列、文字コードはデフォルトです
　最後にCSVノード　区切り文字はコンマ、数値を変換するにチェックを入れます
![基本的なデータ分析　CSV設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/16913067-da8e-b2ce-c7cc-f3eabd407a31.png)


　これでCSVデータをNode-Red上に持ってくる設定ができました。　

# Node-Redで構築　折れ線グラフ
　
　まず、Functionノード(ファイル調整)で、折れ線グラフを表示できるよう、データを整理します。
　「１列目がタイムスタンプ、２列目がCO2濃度、３列目が温度」のデータから、
　以下の２つのファイルを作成します。
　・CO2折れ線グラフデータ　「１列目がタイムスタンプ、２列目がCO2濃度」
　・温度折れ線グラフデータ　「１列目がタイムスタンプ、２列目が温度」
　
　ChangeロールでjSONataなどで設定したかったのですが、方法がわからず、
　JavaScriptで実装することといたしました。

　次に、Changeノード（SET Y、SET Z）では
　　CO2折れ線グラフのデータ　file_new_y　を　msg.payload　にSET
　　温度折れ線グラフのデータ　file_new_z　を　msg.payload　にSET

　最後に、Chartノードでは種類は折れ線グラフとし、グループ、サイズ、ラベルを設定します。
　CahgeノードとChartノードはCO2分のみを以下、掲載します![基本的なデータ分析　CO2グラフ設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/18d67bf7-b9cb-1734-e509-de8e2cd91b99.png)

　
　ご参考：Functionノード(ファイル調整)のロジックとなります

以下は変数設定
```
　var ix;var x = [];var y = [];var z = [];
　var file_old = msg.payload;
　var file_new_y = [];var file_new_z = [];
```

　次に、読み込んだCSVファイルをデータ件数分以下の処理を実施し、
　CO2折れ線グラフのデータと温度折れ線グラフのデータを編集します。
```
　for (ix = 0; ix<file_old.length; ix++) {
    x[ix] = new Date(file_old[ix].col1).getTime();
    y[ix] = file_old[ix].col2;
    z[ix] = file_old[ix].col3;
    file_new_y[ix] = {"x":x[ix], "y":y[ix]};
    file_new_z[ix] = {"x":x[ix], "y":z[ix]};
　}
```

　最後にグラフでデータ出力できるように以下設定を行います
```
　msg.file_new_y = [{"series":["csv"], "labels":["Co2"], "data":[file_new_y]}];
　msg.file_new_z = [{"series":["csv"], "labels":["温度"], "data":[file_new_z]}];
　return msg;
```




　　


# Node-Redで構築　平均・最大：最小

※CO2データの処理

１．Changeノード(looup col2)で、msg.payloadに入っているCO2（２列目データ）のみを抽出し。msg_data_bに格納します。
　JSONata　```$lookup(payload,”col2")```　で抽出できました

　ご参考：JSONata　オブジェクト関数
　https://docs.jsonata.org/object-functions

２．calculatorノードでmsg_data_bに格納されたCO2データの平均値をmsg.data_avgcol2に出力します。同様に最大値と最小値を、それぞれmsg.data_maxcol2、msg.data_mincol2に出力します。

３．最後に計算した数値をtextノードで出力します。

Changeノード(looup col2)、calculatorノード（平均）、textノード（平均）は以下となります。
![基本的なデータ分析　CO2平均設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/1dee8f43-31c8-a462-de4d-aef090e799e2.png)

※温度データの処理
　calculatorノードではなく、Changeノードを使用してみました。表示上はすっきりします。
１．Changeノード(looup col3)で、msg.payloadに入っている温度（３列目データ）のみを抽出し、msg_data_cに格納します。
　JSONata　```$lookup(payload,”col3")```　を使用します。CO2と設定手順はほぼ同じです。

２．Changeノードでmsg_data_cに格納された温度データについて、
　　平均値,,最大値,最小値を、msg.data_avgcol3msg.data_maxcol3、msg.data_mincol3に出力します。

　　JSONata　```$average(data_c)```、```$max(data_c)```、```$min(data_c)```で計算できました。
　　
　　ご参考：JSONata　数値集計関数
　　https://docs.jsonata.org/aggregation-functions

３．最後に計算した数値をtextノードで出力します。

Changeノードは以下となります。
![基本的なデータ分析　数値集計設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/980e2b0f-7cab-537d-b7a4-9a6fd2366383.png)



３．最後に計算した数値をtextノードで出力します。

# Node-Redで構築　ヒストグラム

CO2データの処理手順を以下説明いたします。
（温度についてはCO2とほぼ同様の手順のため、割愛いたします。）

１．Functionノード(文字列変換CO2)で、ヒストグラム集計用のCO2KEYを作成します。
　
　「１列目がタイムスタンプ、２列目がCO2濃度、３列目が温度」のデータから、
　以下の２つのファイルを作成します。
　・CO2折れ線グラフデータ　「１列目がタイムスタンプ、２列目がCO2濃度」
　・温度折れ線グラフデータ　「１列目がタイムスタンプ、２列目が温度」
　
　こちらも、ChangeロールでjSONataなどで設定したかったのですが、方法がわからず、
　JavaScriptで実装することといたしました。

最初に変数設定
```
var file_old = msg.payload;
var v = []; var w = [];var x = [];var y = [];var z = [];var values = [];
var ix=0;
```
日時、CO２、温度を抜き出します。CO2の濃度を四捨五入し10単位に丸め、文字列化します。
```
for (ix = 0; ix < file_old.length-1; ix ++) {
    v[ix] = file_old[ix].col1;
    w[ix] = String(Math.round(file_old[ix].col2/10)*10);
    x[ix] = Number(file_old[ix].col2);
    y[ix] = file_old[ix].col3.toFixed(1);
    z[ix] = Number(file_old[ix].col3) ;
    values.push({ Time: v[ix], Co2key: w[ix], Co2: x[ix], Tempkey: y[ix], Temp: z[ix]   })      
    }     
```
　最後に並べ替えを実施し、データを格納します
```
msg.dataco2 = values;
msg.dataco2.sort(function (e, f) {
    return e.Co2 - f.Co2;
});
return msg;
```

２．Changeノード(group col2)で、msg.dataco2に入っているデータをヒストグラム向けに集計し、msg.dataco2_cntに格納します。JSONata文は以下となります。　
```
dataco2{
  Co2key: $count(Co2) 
} ~> $each(function($v, $k) {
 {"CO2": $k, "件数": $v}  
})
```

式の最初の部分はグループ化と集計を実施、
2 番目の部分は各グループ (キーと値のペア) を目的の形式に変換。

以下を参考に作成してみました。
https://stackoverflow.com/questions/53895674/jsonata-or-js-group-and-sum-json-array-objects
http://try.jsonata.org/ByxzyQ0x4


３．templateノードでVegaの設定をします。ヒストグラム用ですが実際には棒グラフにしています。
```
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "description": "A simple bar chart with embedded data.",
  "width": 1000,
  "height": 250,
  "data": {
    "values": [

    ]
  },
  "mark": "bar",
  "encoding": {
    "x": {"field": "CO2", "type": "nominal", "axis": {"labelAngle": 0}},
    "y": {"field": "件数", "type": "quantitative"}
  }
}
```
上記の設定手順は以下となります。
・以下をCopyしtemplateノードに貼り付け、実際には棒グラフとなります。
　　https://vega.github.io/vega-lite/examples/bar.html
・以下４か所を変更
　　・  "width": 1000,  "height": 250,　を追加
　　・  "values": []　への変更　　中身のデータを消去
　　・　{"field": "a", 　を　{"field": "CO2",　　に変更 
　　・　{"field": "b", 　を　{"field": "件数",　　に変更 
　

４．Chengeノード（SET）でmsg.dataco2_cntからmsg.payload.data.valuesへ値をセットします。

５．最後にVegaノードをセットし、Group,Size,Nameをセットします

Functionノード(文字列変換CO2)、Changeノード(group col2)、templateノード、Chengeノード（SET）、Vegaノードは以下となります。
![基本的なデータ分析　ヒストグラム設定１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/39404625-9339-cef9-5d30-e86a89d12552.png)

![基本的なデータ分析　ヒストグラム設定２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/c992d51d-9631-4358-7821-7c5f50429d09.png)

# Node-Redで構築　散布図
１．Chengeノード（SET）でmsg.payloadからmsg.data_bへ値をセットします
２．以下をCopyしtemplateノードに貼り付け、回帰直線付きの散布図になります。
　　https://vega.github.io/vega-lite/examples/layer_point_line_regression.html
　　変更箇所は以下となります。
　　・  "width": 900　  "height": 400　の追加
　　・  "name": "source"    "values": []}の追加
　　・ 以下の追加、変更
```     
        "encoding": {
        "x": {
          "field": "CO2"
          "type": "quantitative"
          "scale": {"zero": false}
        }
        "y": {
          "field": "temp"
          "type": "quantitative"
          "scale": {"zero": false}
        }　
```
　　・以下の追加、変更
```
      "transform": [
        {
          "regression": "temp"
          "on": "CO2"
        }
      ]
      "encoding": {
        "x": {
          "field": "CO2"
          "type": "quantitative"
        }
        "y": {
          "field": "temp"
          "type": "quantitative"
        }
      }
    }
    {
      "transform": [
        {
          "regression": "temp"
          "on": "CO2"
          "params": true
        }
```



３．Functionノード(数値編集)で、相関表示用のデータを作成します。
```
//変数設定
var file_old = msg.data_b;
var x = [];var y = [];var z = [];var values = [];
var ix=0;

//処理　CO２、温度を抜き出す
for (ix = 0; ix<file_old.length-1; ix++) {
     y[ix] = Number(file_old[ix].col2) ;
     z[ix] = Number(file_old[ix].col3) ;
    values.push ({ CO2:  y[ix] , temp:  z[ix]  })
    }     

// 出力設定:
msg.payload.data.values = values;
return msg;
```
４．最後にVegaノードをセットし、Group,Size,Nameをセットします

Chengeノード（SET）、templateノード、Functionノード(数値編集)、Vegaノードは以下となります。
![基本的なデータ分析　相関設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2742464/50f9319b-a51c-51d2-34b0-df456e799484.png)

# おわりに、続編に向けて

　　　「基本的なデータ分析」で次に取り組みたいのは　「箱ひげ図」　となります。
　　　Vegaノードでは各種可視化が可能なようですので、今後いろいろと試していきたいと考えています。

　　　散布図は、時間帯でわけた場合に、直線的な相関が見えてくる可能性もありそうです。
　　　グラフ全体で時間帯別切替表示といったことができるとよいかもしれません。
　　　
　　　Node-Redにおいては、ChangeノードJSONataの更なる理解と活用、Functionノードのシンプル化などなどの課題がございます。

# 参考資料

- [Node-REDダッシュボードを用いたデータ可視化](https://qiita.com/HiroyasuNishiyama/items/2433d275c667bc75ed8c)
- [Vega](https://vega.github.io/vega/)
- [Vega-lite](https://vega.github.io/vega-lite/)
- [JSONata](https://docs.jsonata.org/overview.html)
- [node-red-node-ui-vegaを使った各種センサーのグラフ表示](https://qiita.com/kz1017/items/a9690f1e7cb06b0e3f24)
- [二酸化炭素濃度モニタリングリモートデモシステムの構築](https://qiita.com/hrsakuma/items/b29de6c1b482ee15f03b)