はじめに
弊社のデモ環境でオフィスのCO2濃度と気温を測定し、CSVファイルに出力する仕組みを作りました。
当該環境で出力される1日分のデータについて、Node-Redを使用して、基本的なデータ分析にチャレンジしてみることにしました。
※データは15秒間隔で取得していますが、ところどころ取れていないケースがあります。
2022年1月頃の寒い時期のデータです。
基本的なデータ分析 今回の表示目標
CO2濃度と気温
1日分の折れ線グラフ
最大値、最小値、平均値、ヒストグラム
散布図
が今回の表示目標となります。
【1】CO2濃度と気温 1日分の折れ線グラフ
Chartノードを使用して、時系列の折れ線グラフを表示します
【2】CO2濃度と気温 最大値、最小値、平均値、ヒストグラム
vegaノードを使用して、CO2濃度、気温のヒストグラムを表示します
平均値、最大値、最小値を計算し表示します。 (最頻値はヒストグラムから目視で認識します)
まずCO2についてです <少し考えてみました>
〇Co2濃度の折れ線グラフ
夜間は600ppm前後で一定値、朝、始業前後で450ppmくらいまで下降、
その後お昼にかけて550ppmくらいまで上昇、午後は少し下降し、夕方にかけて上昇。
夕方から夜間は650ppmくらいまで上昇 といった様子が読み取れます。
〇Co2濃度のヒストグラム他
最頻値は600ppm付近。他に550ppm付近にも山があり、、2つのピークがあります
平均は565ppm、最大値653ppm、最小値461ppm、変動幅は約200ppm
次に温度についてです <少し考えてみました>
〇温度の折れ線グラフ
空調が聞いているためか1日を通して2.8℃程度の変動。
夜間から朝にかけて気温は下落傾向、始業前後から11時ころまでに2度ほど上昇。
その後は17時ころまで緩やかに上昇、以降は緩やかに下降 といった様子が読み取れます
〇温度のヒストグラム他
最頻値は23.9℃付近。他に21.4℃付近にも山があり、2つのピークがあります。
平均は22.68℃、最大値23.98℃、最小値21.23℃、変動幅は約2.8℃
<両方のグラフから もう少し考えてみました>
夜間から朝にかけては換気によって、CO2濃度、温度、両方の低下
朝からお昼 人の出勤によるCO2濃度の上昇、出勤に合わせた空調コントロール(気温上昇)
夕方以降CO2濃度が上がっていくところは推測がついておりません。
在館者は減っているはずで、植物がたくさんある環境でもなく。少数の人が残っていて、空調稼働が少なくなっているような状況でしょうか。。。。
【3】CO2濃度と気温 散布図
vegaノードを使用して、縦軸に気温、横軸にCO2濃度をとり、散布図を表示します
完成形は以下となります
<少し考えてみました>
〇1日分の相関を見ると、直線的な相関はなさそうです。Gの字のように見えています。
時間帯でわけてみると、直線的な相関が見えてくるかもしれません。
Node-Redで構築 フローの全体図
当方環境はNode-Red ver2.2.2をWindows上で動かしております。
フローの全体は以下となります。大きく、「CSVファイル読込」、「折れ線グラフ」、「平均・最大:最小・ヒストグラム」、「散布図」の4つに分かれております。
Node-Redで構築 CSVファイル読込
読み込むCSVファイルはヘッダがなく、以下のような形式です。
1列目がタイムスタンプ、2列目がCO2濃度(整数)、3列目が温度となっております。
===
2022/01/04 00:01:19,527,21.1625
・・・
2022/01/04 23:59:19,605,22.2250
===
まず、injectノード(file)ではfilenameをセットします(他に3つinjectノードを配置し、他のファイルも読めるようにしています。)
次に、read fileノード 出力は文字列、文字コードはデフォルトです
最後にCSVノード 区切り文字はコンマ、数値を変換するにチェックを入れます
これでCSVデータをNode-Red上に持ってくる設定ができました。
Node-Redで構築 折れ線グラフ
まず、Functionノード(ファイル調整)で、折れ線グラフを表示できるよう、データを整理します。
「1列目がタイムスタンプ、2列目がCO2濃度、3列目が温度」のデータから、
以下の2つのファイルを作成します。
・CO2折れ線グラフデータ 「1列目がタイムスタンプ、2列目がCO2濃度」
・温度折れ線グラフデータ 「1列目がタイムスタンプ、2列目が温度」
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分のみを以下、掲載します
ご参考: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データの処理
1.Changeノード(looup col2)で、msg.payloadに入っているCO2(2列目データ)のみを抽出し。msg_data_bに格納します。
JSONata $lookup(payload,”col2")
で抽出できました
ご参考:JSONata オブジェクト関数
https://docs.jsonata.org/object-functions
2.calculatorノードでmsg_data_bに格納されたCO2データの平均値をmsg.data_avgcol2に出力します。同様に最大値と最小値を、それぞれmsg.data_maxcol2、msg.data_mincol2に出力します。
3.最後に計算した数値をtextノードで出力します。
Changeノード(looup col2)、calculatorノード(平均)、textノード(平均)は以下となります。
※温度データの処理
calculatorノードではなく、Changeノードを使用してみました。表示上はすっきりします。
1.Changeノード(looup col3)で、msg.payloadに入っている温度(3列目データ)のみを抽出し、msg_data_cに格納します。
JSONata $lookup(payload,”col3")
を使用します。CO2と設定手順はほぼ同じです。
2.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
3.最後に計算した数値をtextノードで出力します。
3.最後に計算した数値をtextノードで出力します。
Node-Redで構築 ヒストグラム
CO2データの処理手順を以下説明いたします。
(温度についてはCO2とほぼ同様の手順のため、割愛いたします。)
1.Functionノード(文字列変換CO2)で、ヒストグラム集計用のCO2KEYを作成します。
「1列目がタイムスタンプ、2列目がCO2濃度、3列目が温度」のデータから、
以下の2つのファイルを作成します。
・CO2折れ線グラフデータ 「1列目がタイムスタンプ、2列目がCO2濃度」
・温度折れ線グラフデータ 「1列目がタイムスタンプ、2列目が温度」
こちらも、ChangeロールでjSONataなどで設定したかったのですが、方法がわからず、
JavaScriptで実装することといたしました。
最初に変数設定
var file_old = msg.payload;
var v = []; var w = [];var x = [];var y = [];var z = [];var values = [];
var ix=0;
日時、CO2、温度を抜き出します。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;
2.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
3.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
・以下4か所を変更
・ "width": 1000, "height": 250, を追加
・ "values": [] への変更 中身のデータを消去
・ {"field": "a", を {"field": "CO2", に変更
・ {"field": "b", を {"field": "件数", に変更
4.Chengeノード(SET)でmsg.dataco2_cntからmsg.payload.data.valuesへ値をセットします。
5.最後にVegaノードをセットし、Group,Size,Nameをセットします
Functionノード(文字列変換CO2)、Changeノード(group col2)、templateノード、Chengeノード(SET)、Vegaノードは以下となります。
Node-Redで構築 散布図
1.Chengeノード(SET)でmsg.payloadからmsg.data_bへ値をセットします
2.以下を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
}
3.Functionノード(数値編集)で、相関表示用のデータを作成します。
//変数設定
var file_old = msg.data_b;
var x = [];var y = [];var z = [];var values = [];
var ix=0;
//処理 CO2、温度を抜き出す
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;
4.最後にVegaノードをセットし、Group,Size,Nameをセットします
Chengeノード(SET)、templateノード、Functionノード(数値編集)、Vegaノードは以下となります。
おわりに、続編に向けて
「基本的なデータ分析」で次に取り組みたいのは 「箱ひげ図」 となります。
Vegaノードでは各種可視化が可能なようですので、今後いろいろと試していきたいと考えています。
散布図は、時間帯でわけた場合に、直線的な相関が見えてくる可能性もありそうです。
グラフ全体で時間帯別切替表示といったことができるとよいかもしれません。
Node-Redにおいては、ChangeノードJSONataの更なる理解と活用、Functionノードのシンプル化などなどの課題がございます。
参考資料
- Node-REDダッシュボードを用いたデータ可視化
- Vega
- Vega-lite
- JSONata
- node-red-node-ui-vegaを使った各種センサーのグラフ表示
- 二酸化炭素濃度モニタリングリモートデモシステムの構築