D3.jsとGridDBによる気象データの可視化

今回は、GridDBを使って気象データを可視化・分析する方法を紹介します。CSVファイルで提供されているデータセットをGridDBにロードし、GridDBからデータを取得してデータ分析を行います。気象データセットを使用します。

[ この記事の全ソースコードはこちら ][1] をご覧ください。

## GridDBにデータをロードし、GridDBからデータを取得する

```javascript
var griddb = require('griddb_node');

const createCsvWriter = require('csv-writer').createObjectCsvWriter;
const csvWriter = createCsvWriter({
  path: 'out.csv',
  header: [
    {id: "DATE", title:"DATE"}, 
    {id: "WIND", title:"WIND"}, 
    {id: "IND", title:"IND"}, 
    {id: "RAIN", title:"RAIN"}, 
    {id: "IND.1", title:"IND.1"}, 
    {id: "T.MAX", title:"T.MAX"}, 
    {id: "IND.2" , title:"IND.2"}, 
    {id: "T.MIN", title:"T.MIN"}, 
    {id: "T.MIN.G", title:"T.MIN.G"}, 
  ]
});

const factory = griddb.StoreFactory.getInstance();
const store = factory.getStore({
    "host": '239.0.0.1',
    "port": 31999,
    "clusterName": "defaultCluster",
    "username": "admin",
    "password": "admin"
});
// For connecting to the GridDB Server we have to make containers and specify the schema.
const conInfo = new griddb.ContainerInfo({
    'name': "windspeedanalysis",
    'columnInfoList': [
      ["name", griddb.Type.STRING],
      ["DATE", griddb.Type.STRING],
        ["WIND", griddb.Type.STRING],
        ["IND", griddb.Type.STRING],
        ["RAIN", griddb.Type.STRING],
        ["IND.1", griddb.Type.STRING],
        ["T.MAX", griddb.Type.STRING],
        ["IND.2", griddb.Type.STRING],
        ["T.MIN", griddb.Type.STRING],
        ["T.MIN.G", griddb.Type.STRING]
    ],
    'type': griddb.ContainerType.COLLECTION, 'rowKey': true
});

const csv = require('csv-parser');

const fs = require('fs');
var lst = []
var lst2 = []
var i =0;
fs.createReadStream('./dataset/windspeed.csv')
  .pipe(csv())
  .on('data', (row) => {
    lst.push(row);
    console.log(lst);

  })
  .on('end', () => {

    var container;
    var idx = 0;
    
    for(let i=0;i&lt;lst.length;i++){

        // lst[i]['DATE'] = String(lst[i]["DATE"])
        // lst[i]['WIND'] = parseFloat(lst[i]["WIND"])
        // lst[i]['IND'] = parseInt(lst[i]["IND"])
        // lst[i]['RAIN'] = parseFloat(lst[i]["RAIN"])
        // lst[i]['IND.1'] = parseInt(lst[i]["IND.1"])
        // lst[i]['T.MAX'] = parseFloat(lst[i]["T.MAX"])
        // lst[i]['IND.2'] = parseInt(lst[i]["IND.2"])
        // lst[i]['T.MIN'] = parseFloat(lst[i]["T.MIN"])
        // lst[i]['T.MIN.G'] = parseFloat(lst[i]["T.MIN.G"])

    store.putContainer(conInfo, false)
        .then(cont => {
            container = cont;
            return container.createIndex({ 'columnName': 'name', 'indexType': griddb.IndexType.DEFAULT });
        })
        .then(() => {
            idx++;
            container.setAutoCommit(false);
            return container.put([String(idx), lst[i]['DATE'],lst[i]["WIND"],lst[i]["IND"],lst[i]["RAIN"],lst[i]["IND.1"],lst[i]["T.MAX"],lst[i]["IND.2"],lst[i]["T.MIN"],lst[i]["T.MIN.G"]]);
        })
        .then(() => {
            return container.commit();
        })
       
        .catch(err => {
            if (err.constructor.name == "GSException") {
                for (var i = 0; i &lt; err.getErrorStackSize(); i++) {
                    console.log("[", i, "]");
                    console.log(err.getErrorCode(i));
                    console.log(err.getMessage(i));
                }
            } else {
                console.log(err);
            }
        });
    
    }

    store.getContainer("windspeedanalysis")
    .then(ts => {
        container = ts;
      query = container.query("select *")
      return query.fetch();
  })
  .then(rs => {
      while (rs.hasNext()) {
          let rsNext = rs.next()
          lst2.push(
                      
            {
                'DATE': rsNext[1],
                "WIND": rsNext[2],
                "IND": rsNext[3],
                "RAIN": rsNext[4],
                "IND.1": rsNext[5],
                "T.MAX": rsNext[6],
                "IND.2": rsNext[7],
                "T.MIN": rsNext[8],
                "T.MIN.G": rsNext[9],
            }
          );
      }
        csvWriter
        .writeRecords(lst2)
        .then(()=> console.log('The CSV file was written successfully'));
      return 
  }).catch(err => {
      if (err.constructor.name == "GSException") {
          for (var i = 0; i &lt; err.getErrorStackSize(); i++) {
              console.log("[", i, "]");
              console.log(err.getErrorCode(i));
              console.log(err.getMessage(i));
          }
      } else {
          console.log(err);
      }
  });   
});
```

注意：htmlファイルでd3.jsを実行するには、GridDBから取得したデータを含む出力csvファイルを読み込むために、まずhttpsサーバーを実行する必要があります。

## データの可視化

表のプロットや描画を行うために、データを元に文書を操作することができるライブラリ「D3.js」を使用します。このライブラリにより、HTMLのDOM操作や視覚化が可能です。

まず、データセットの最初の数行を印刷して、データの感触をつかむことから始めましょう。この目的のために、データを作成して html テーブルに挿入するコードを書かなければなりません。これはヘルパーコードとなり、ソースファイルに含まれることになりますが、この記事の範囲外です。

これができたら、表を印刷するためのコードを書きます。

```javascript
let result = data.flatMap(Object.keys);
result = [...new Set(result)];

tables("data_table",data,result,true)
```

上のコードで、resultはデータの列の配列、data_tableはデータを挿入するテーブルのHTML要素IDです。最後の引数は、上位5行を表示するためのフラグで、ここではtrueに設定しています。

ここで、私たちのコードの結果をご覧ください。

[<img src="https://griddb.net/en/wp-content/uploads/2022/05/Head.png" alt="" width="486" height="184" class="aligncenter size-full wp-image-28294" />][2]

さて、データの種類を確認したところで、今度はデータのすべての数値列に関連するさまざまな統計のサマリーを見たいと思います。 HTML文書への表の描画には同じヘルパー関数が使えますが、要約されたすべての数量を取得するコードを書く必要があります。 最小値、最大値、偏差値、平均値、中央値、そしてデータ内でNULLでない行のカウントを取得することになります。幸いなことに、D3.jsはこれらのほとんどについて関数を提供していますが、NULLでない行のカウントについてはヘルパー関数を作成する必要があります。単純にforループを書いて、その列の行がNULLでなければ値をインクリメントし、最後にその値を返せばいいのです。

```javascript
const countNotNull = (array) => {
    let count = 0
    for(let i = 0; i &lt; array.length; i++){
        if(isNaN(parseInt(array[i]))==false && array[i]!==null){ count++ }}
    return count
};
```

これで、データセットの要約データを取得するためのforループが書けるようになりました。この表を描くために、IDが "data_table2 "のHTML要素を使用します。

```javascript
let obj = {}

let summary_data = []
for(let i = 0; i &lt; result.length; i++){
    
    obj[result[i]]  = data.map(function(elem){return elem[result[i]]});
    let count = 0
    let summarize = {}

    if(result[i]!='DATE'){
        obj[result[i]] = obj[result[i]].map(Number)
    }
    summarize['Field'] = result[i]
    summarize['Max'] = d3.max(obj[result[i]])
    summarize['Min'] = d3.min(obj[result[i]])
    summarize['Deviation'] = d3.deviation(obj[result[i]])
    summarize['Mean'] = d3.mean(obj[result[i]])
    summarize['Median'] = d3.median(obj[result[i]])
    
    summarize['Count Not Null'] = countNotNull(obj[result[i]])
    
    summary_data.push(summarize)
}
let result2 = summary_data.flatMap(Object.keys)
result2 = [...new Set(result2)];
tables("data_table2",summary_data,result2,false)
```

結果は以下のようになるはずです。

[<img src="https://griddb.net/en/wp-content/uploads/2022/05/Summary.png" alt="" width="716" height="306" class="aligncenter size-full wp-image-28290" />][3]

さて、ヒストグラムと箱ひげ図の形で、データの各列の実際の分布を見たいと思います。どちらのプロットでも、ソースファイルで提供されているヘルパー関数を記述する必要があります。これらのヘルパー関数を書いたら、メインのjavascriptファイルの中で直接呼び出すことができます。

```javascript
## WindSpeed
histogram("my_dataviz",obj['WIND'],'Average Wind Speed')
boxplot("my_dataviz",obj['WIND'],"Wind Speed")

## Precipitation
histogram("my_dataviz2",obj['RAIN'],'Precipitation (Rain)',"orange")
boxplot("my_dataviz2",obj['RAIN'],"Precipitation (Rain)",'brown')

## Max Temperature
histogram("my_dataviz3",obj['T.MAX'],'Max Temperature',"green")
boxplot("my_dataviz3",obj['T.MAX'],"Max Temperature",'yellow')

## Min Temperature
histogram("my_dataviz4",obj['T.MIN'],'Min Temperature',"purple")
boxplot("my_dataviz4",obj['T.MIN'],"Min Temperature",'pink')

## Minimum Grass Temperature
histogram("my_dataviz5",obj['T.MIN.G'],'Minimum Grass Temperature',"cyan")
boxplot("my_dataviz5",obj['T.MIN.G'],"Minimum Grass Temperature",'orange')

## Indicator Variable
histogram("my_dataviz6",obj['IND'],'Indicator')

## Indicator Variable 1
histogram("my_dataviz7",obj['IND.1'],'Indicator 1',"orange")

## Indicator Variable 2
histogram("my_dataviz8",obj['IND.2'],'Indicator 2',"green")
```

これらのプロット関数のそれぞれにおいて、最初の引数はプロットを描きたいhtml要素のID、次にプロットしたいデータのカラムの配列、そしてタイトルとCSS互換のカラーコードです。Webサーバーからhtmlファイルを開けば、結果を可視化することができます。

[<img src="https://griddb.net/en/wp-content/uploads/2022/05/Hist1.png" alt="" width="1552" height="642" class="aligncenter size-full wp-image-28296" />][4]

[<img src="https://griddb.net/en/wp-content/uploads/2022/05/Hist2.png" alt="" width="1552" height="642" class="aligncenter size-full wp-image-28296" />][5]

分布のプロットを見てきましたが、このデータは時系列なので、データのトレンドやパターンを可視化するために、時間に対する列のプロットも行う必要があります。そこで、データセットの各変数を折れ線グラフにすることにします。`lineplot()`関数は私たちが作成したもので、ソースファイルに含まれています。

```javascript
lineplot("my_dataviz9",data,obj,'DATE','WIND','#4169e1')
lineplot("my_dataviz9",data,obj,'DATE','RAIN','#4169e1')
lineplot("my_dataviz9",data,obj,'DATE','T.MAX','#4169e1')
lineplot("my_dataviz9",data,obj,'DATE','T.MIN','#4169e1')
lineplot("my_dataviz9",data,obj,'DATE','T.MIN.G','#4169e1')
```

そして、その結果がこちらです。

[<img src="https://griddb.net/en/wp-content/uploads/2022/05/LinePlots.png" alt="" width="1606" height="523" class="aligncenter size-full wp-image-28288" />][6]

グラフから、すべての変数が気象データから予想される周期的なパターンを持ち、ヒストグラムで示されたとおりの平均値を持つことが分かります。

最後に、相関行列をプロットして、どの列がお互いに相関しているかを見ることができます。これは予測をするときに重要なことで、ターゲット変数と相関する変数をモデルに含め、お互いに相関する変数を除外したいと思うからです。データを引数としてcorrelogram()関数を呼び出すだけで、相関行列が得られます。correlogram()は、ソースファイルにあるcorrel.jsファイルから得られます。

```javascript
correlogram(data)
```

そして、相関行列はこのようになります。

[<img src="https://griddb.net/en/wp-content/uploads/2022/05/correl.png" alt="" width="430" height="477" class="aligncenter size-full wp-image-28292" />][7]

[この記事の全ソースコードはこちら][1]をご覧ください。

相関行列から、T.MIN と T.MIN.G は互いに高い相関があり、T.MIN と IND2 は負の相関があることが分かります。他の変数はほとんど互いにゼロに近い相関を持っていますが、これは予想されることです。

 [1]: https://github.com/griddbnet/Blogs/tree/main/Visualization%20of%20Weather%20Data%20with%20D3.js%20and%20GridDB
 [2]: https://griddb.net/en/wp-content/uploads/2022/05/Head.png
 [3]: https://griddb.net/en/wp-content/uploads/2022/05/Summary.png
 [4]: https://griddb.net/en/wp-content/uploads/2022/05/Hist1.png
 [5]: https://griddb.net/en/wp-content/uploads/2022/05/Hist2.png
 [6]: https://griddb.net/en/wp-content/uploads/2022/05/LinePlots.png
 [7]: https://griddb.net/en/wp-content/uploads/2022/05/correl.png