サーバーサイドプログラミング(4)集計処理のプログラム

サーバーサイドプログラミング

今回は、Excelでもお馴染みの集計処理をNode.jsのプログラムでやってみます。

準備

今回は、雛型のディレクトリを下記GitHubからcloneします。

GitHub画面右上のForkを押して、フォークしたリポジトリをクローンして、VS Code、Dockerを立ち上げます。

cd ~/workspace/
git clone git@github.com:あなたのユーザ名/adding-up.git
cd adding-up
git checkout main-2022
code .
docker-compose up -d
docker-compose exec app bash

なお、本プロジェクトフォルダのpopu-pref.csvファイルは、これから集計をする各県の人口推移のデータのファイルで、残りのファイルはこれまでと同じです。csvファイルは、事務職ではお馴染みではないでしょうか。会社の色々なシステムからの出力データを加工する際に、お目にかかっていると思います。

今回の集計処理テーマ

今回の集計処理のテーマは、「2010 年から 2015 年にかけて 15〜19 歳の人が増えた割合の都道府県ランキング」です。具体的な要件は下記の通りです。

  1. ファイルからデータを読み取る
  2. 2010 年と 2015 年のデータを選ぶ
  3. 都道府県ごとの変化率を計算する
  4. 変化率ごとに並べる
  5. 並べられたものを表示する

正直に言うと、これらの処理をするだけならば、CSVをExcelで加工した方が早いと思います。ただ、こららの内容をシステムに反映したかったり、Webサイトでデータを公開したいときには、サーバーサイドプログラミングで実装する必要があります。

手順1.ファイルからデータを読み取る

'use strict';
const fs = require('fs');
const readline = require('readline');

const rs = fs.createReadStream('./popu-pref.csv');
const rl = readline.createInterface({input:rs});

rl.on('line',lineString => {
 console.log(lineString);
});

最初のrequire部分は、Node.jsのモジュールを呼び出しています。fs(=FileSystem)はファイルを扱うためのモジュールで、readlineはファイルを一行ずつ読み込むためのモジュールです。

次のブロックのcreateReadStreamでは、当該csvファイルからファイルを読み込むStreamを生成し、次に、それを{input : rs}として設定し(rs=ReadStream)、readlineオブジェクトを生成しています。ここで作成されたrl(=ReadLine)オブジェクトも、Streamのインターフェイスを持ちます(createInterface)。

  • Stream:今回は、①csvデータ読み込み処理を監視するイベントと、②そのデータ読み込みが進んだ時に実行する関数の2つを設定しています。このうように、非同期で情報を扱う概念がStreamです。

最後のブロックでは、その直前で作成されたrl(=ReadLine)オブジェクトで、lineというイベントが発生したら、この無名関数を呼んでくださいというイベント駆動型プログラムです。普段のお仕事でAccessを使っている事務職の方ならば、イベントプロシージャのようなものと捉えてもらえればいいかと思います。

  • 無名関数:名前を定義していない関数で、今回は、lineというイベントの発生をトリガーとして呼ばれる関数。表記方法としては、function()(←カッコ内は何もなし)と、=>というアロー関数の形がある。

ExcelマクロでCSVを読み込むときも最初わかりづらかったかと思いますが、それと同じく、この辺は決まりきった型として捉えてしまった方が良いかと思います。

以上で、データの読み取りは完了です。Shell上で、node app.js と実行すると読み込まれたCSVデータが出力されるはずです。

手順2.2010 年と 2015 年のデータを選ぶ

次に特定のデータだけを抜き出す処理を実装します。前のセクションで実装したrlの無名関数を修正するところから始めます。

rl.on('line', lineString => {
  const columns = lineString.split(',');
  const year = parseInt(columns[0]);
  const prefecture = columns[1];
  const popu = parseInt(columns[3]);
  if (year === 2010 || year === 2015) {
    console.log(year);
    console.log(prefecture);
    console.log(popu);
  }
});

“columns = lineString.split( ‘ , ‘ )”で、lineStringで与えられた文字列をカンマで分割して、columnsという名前の配列に入れています。そして、year,prefecture,popuにそれぞれcolumns配列の添字0,1,3の値を入れています。yearとpopuには、parseInt関数を使っていますが、これは文字列で取得したcsvデータのデータ型を数字に変換しています。そして、最後のif文で2010年と2015年だけをコンソールに抽出しています。

手順3.都道府県ごとの変化率を計算する

'use strict';
const fs = require('fs');
const readline = require('readline');
const rs = fs.createReadStream('./popu-pref.csv');
const rl = readline.createInterface({ input: rs });
const prefectureDataMap = new Map(); // key: 都道府県 value: 集計データのオブジェクト
rl.on('line', lineString => {
  const columns = lineString.split(',');
  const year = parseInt(columns[0]);
  const prefecture = columns[1];
  const popu = parseInt(columns[3]);
  if (year === 2010 || year === 2015) {
    let value = null;
    if (prefectureDataMap.has(prefecture)) {
      value = prefectureDataMap.get(prefecture);
    } else {
      value = {
        popu10: 0,
        popu15: 0,
        change: null
      };
    }
    if (year === 2010) {
      value.popu10 = popu;
    }
    if (year === 2015) {
      value.popu15 = popu;
    }
    prefectureDataMap.set(prefecture, value);
  }
});
rl.on('close', () => {
  console.log(prefectureDataMap);

これまでのコードからの追加点として、まず、集計されたデータを格納するprefectureDataMapを連想配列で準備します。

let value = null;
if (prefectureDataMap.has(prefecture)) {
 value = prefectureDataMap.get(prefecture);
} else {
 value = {
popu10: 0,
popu15: 0,
change: null
};
}
そして、このコードで、prefectureDataMapに既にデータが存在していれば、それを返して、なければ初期値を代入しています。このifとhasの使い方は、前回でもありましたね。

その後のif文で、2010年と2015年の人口データを配列に格納しています。
そして、最後の文で、close(=全ての行を読み込み終わった)イベントの際に集計データがコンソールに出力されます。

次に都道府県ごとの変化率を計算します。最後のcloseイベントの無名関数を下記の通り修正します。

rl.on('close', () => {
  for (const [key, value] of prefectureDataMap) {
    value.change = value.popu15 / value.popu10;
  }
 console.log(prefectureDataMap);
});

for (const [key, value] of prefectureDataMap)の部分は、for~of構文になっています。prefectureDataMapの中身をkey,valueに代入して、ループします。添字が不要な点が便利です。そして、”value.change = value.popu15 / value.popu10″ でvalueオブジェクトのchangeプロパティに変化率を代入します。

手順4.変化率ごとに並べる

直前の最後の文console.log(prefectureDataMap);を以下の文に置き換えます。

 const rankingArray = Array.from(prefectureDataMap).sort((pair1, pair2) => {
    return pair2[1].change - pair1[1].change;
  });
  console.log(rankingArray);
});

Array.from(prefectureDataMap)の部分で、連想配列を普通の配列に変換します。そして、sort関数でソートします。

手順5.並べられたものを表示する

最後のconsole.log(rankingArray)を、以下の文と置き換えます。

 const rankingStrings = rankingArray.map(([key, value]) => {
    return `${key}: ${value.popu10}=>${value.popu15} 変化率: ${value.change}`;
  });
  console.log(rankingStrings);
});

まず、連想配列のMapでなく、関数のMapについて紹介します。配列の要素のそれぞれに対して、与えられた関数でそれぞれ変換するものです。
具体的には、
[1,2,3].map(i => {
return i * 2;
});
を実行すると、[2,4,6];と返します。

ここで返す${key}: ${value.popu10}=>${value.popu15} 変化率: ${value.change}は、
具体的には、
‘愛知県: 361670=>371756 変化率: 1.0278873005778748’,
‘大阪府: 416930=>426504 変化率: 1.0229630873288083’,
‘富山県: 47585=>48442 変化率: 1.0180098770620993’,
‘神奈川県: 421017=>426358 変化率: 1.0126859485483959’,
のような形になります。

以上になります。今回も最後までお読み頂きありがとうございました。

※こちらの内容は、N予備校の教材を参考しております。

コメント

タイトルとURLをコピーしました