インフルエンザ患者数のグラフを作る

(1/1)
PHP と JpGraph で人口ピラミッドを表示する」では棒グラフを扱ったが、同様に PHP と JpGraph を用い、インフルエンザなどの感染症患者数を折れ線グラフであらわすプログラムを作ってみる。
インフルエンザ患者数は日々変化しているので、国立感染症研究所が発表している患者数データを取り込むようにした。

サンプル・プログラムの実行例

インフルエンザ患者数のグラフを作る
サンプル・プログラムを実行すると、インフルエンザの定点あたり報告数(速報値)を 10 年分グラフ表示する。出力は PNG 形式の画像データだ。

グラフ描画する感染症は、インフルエンザを含めて 19種類設定できる。コマンドライン・オプション d に 0~18 の番号を指定する。番号と感染症名の対応は、配列変数 $ListDiseases に定義している。
たとえば "influenza.php?d=8" と指定すると、百日咳のグラフを表示する。

コマンドライン・オプション img を指定すると、img タグの中に画像データを埋め込むための文字列を生成する。詳しくは、「PHP で TrueType フォントを利用する(その2)」-「解説:画像を HTML コンテンツに埋め込む」を参照のこと。

コマンドライン・オプション file に続いてファイル名を指定すると、国立感染症研究所が発表している患者数データではなく、指定したデータファイルからデータを読み込んで表示する。データファイルの書式は後述する。

サンプル・プログラム

解説:準備

グラフ描画には JpGraph を利用する。導入方法については、「PHP と JpGraph で人口ピラミッドを表示する」を参照のこと。

0063: //JpGraph(各自の環境に合わせて)
0064: require_once('jpgraph/jpgraph.php');
0065: require_once('jpgraph/jpgraph_line.php');

解説:患者データの解析

0241: /**
0242:  * IDWR速報データのページから目的のCSVファイル名を取り出す
0243:  * @return string CSVファイル名
0244: */
0245: function getIDWR_trend() {
0246:     static $pat = '/href\=\"(.+\-trend\.csv)/ui';
0247: 
0248:     $context = array(
0249:         'ssl' => array(
0250:             'verify_peer' => FALSE,
0251:             'verify_peer_name' => FALSE,
0252:         )
0253:     );
0254: 
0255:     $infp = fopen(URI_IDWR, 'r', FALSEstream_context_create($context));
0256:     if ($infp == FALSEreturn FALSE;
0257: 
0258:     $fname = '';
0259:     while (! feof($infp)) {
0260:         $str = fgets($infp);
0261:         if (preg_match($pat$str$arr) > 0) {
0262:             $fname = $arr[1];
0263:             break;
0264:         }
0265:     }
0266:     fclose($infp);
0267: 
0268:     $arr = parse_url(URI_IDWR);
0269:     $fname = 'http://' . $arr['host'] . $fname;
0270: 
0271:     return $fname;
0272: }
0273: 
0274: /**
0275:  * IDWR速報データを解析
0276:  * @param string $fname 解析ファイル名
0277:  * @param string $disease 感染症名
0278:  * @param array  $item データを格納する配列
0279:  * @return int データの開始年/FALSE
0280: */
0281: function parseIDWR($fname$disease, &$items) {
0282:     $pat = array(
0283: '/' . $disease . ',/ui',
0284: '/,([0-9]+)週,/ui',
0285: '/([0-9]+)年,/ui',
0286: '/^,$/ui'
0287: );
0288: 
0289:     ini_set('auto_detect_line_endings', TRUE);
0290:     $infp = fopen($fname, 'r');
0291:     if ($infp == FALSEreturn FALSE;
0292: 
0293:     $start = 0;
0294:     $mode = 0;
0295:     while (! feof($infp)) {
0296:         $str = mb_convert_encoding(trim(fgets($infp)), INTERNAL_ENCODING, IDWR_ENCODING);
0297:         //データ領域の始まり
0298:         if (($mode == 0) && (preg_match($pat[0]$str) > 0)) {
0299:             $mode = 1;
0300:         //データ本体
0301:         } else if ($mode == 1) {
0302:             if (preg_match($pat[1]$str) > 0) {
0303:             } else if (preg_match($pat[2]$str$arr) > 0) {
0304:                 //西暦年の行
0305:                 $year = $arr[1];
0306:                 if ($year < 2000)   $year += 2000;
0307:                 if ($start == 0)    $start = $year;
0308:                 //データの行
0309:                 $arr = preg_split('/,/ui', $str);
0310:                 foreach ($arr as $week=>$data) {
0311:                     if (($week > 0) && ($data != '')) {
0312:                         $items[$year][$week] = $data;
0313:                     }
0314:                 }
0315:             } else {
0316:                 break;
0317:             }
0318:         }
0319:     }
0320:     fclose($infp);
0321: 
0322:     return $start;
0323: }

折れ線グラフに描くデータは、国立感染症研究所の「IDWR速報データ」の中から「疾病毎定点当たり報告数~過去 10 年間との比較~」を利用する。

まず、ユーザー関数 getIDWR_trend により、読み込むべき CSV ファイル名を取得する。CSV ファイル名は毎週変化するためである。

次に、ユーザー関数 parseIDWR により、CSV ファイルを解析し、データの開始年とデータ本体を取得する。
ターゲットの CSV ファイルはシフト JIS でエンコードされているが、改行コードが CR であるため、 fgets  が正常に機能しない。そこで、事前に ini_set に 'auto_detect_line_endings' を指定することで、CR を改行として認識できるようにしておく。

解説:初期化処理

患者数の増減は折れ線グラフで描くが、読みやすいように、年によってグラフの色や太さを変えている。
色は RGB 各 8bit で配列変数 $PlotColor に、太さは配列変数 $PlotWeight にあらかじめ代入しておく。

0070: //折れ線グラフの色(年ごとに少しずつ色を変える)
0071: $PlotColor = array(
0072:  0 => '#E5004F',
0073:  1 => '#FFA100',
0074:  2 => '#009944',
0075:  3 => '#00A0E9',
0076:  4 => '#920783',
0077:  5 => '#F39800',
0078:  6 => '#8FC31F',
0079:  7 => '#0068B7',
0080:  8 => '#E4007F',
0081:  9 => '#009E96',
0082: 10 => '#E60012',
0083: 11 => '#FFA100',
0084: 12 => '#8FC31F'
0085: );
0086: 
0087: //折れ線グラフの太さ(直近のグラフだけ太線にする)
0088: $PlotWeight = array(
0089:  0 => 1,
0090:  1 => 1,
0091:  2 => 1,
0092:  3 => 1,
0093:  4 => 1,
0094:  5 => 1,
0095:  6 => 1,
0096:  7 => 1,
0097:  8 => 1,
0098:  9 => 1,
0099: 10 => 3,
0100: 11 => 3,
0101: 12 => 3
0102: );

解説:グラフを描く

グラフの描画は JpGraph の機能を利用している。

まず、1 年分のデータを配列 $data_y に代入し、折れ線グラフを 1 本描く。
この作業を年数分繰り返してゆく。

0325: /**
0326:  * 感染症グラフ描画
0327:  * @param array  $item データ
0328:  * @param int    $start 描画開始年
0329:  * @param string $disease 感染症名
0330:  * @return object graphオブジェクト
0331: */
0332: function drawGraph($items$start$disease) {
0333:     global $PlotColor$PlotWeight;
0334: 
0335:     $plotdata = array();
0336: 
0337:     // 月(横軸)を作成
0338:     for ($i = 1; $i <= 53; $i++) {
0339:         $data_x[$i] = (int)($i / 4.5 + 1);
0340:     }
0341: 
0342:     //グラフ描画処理開始
0343:     $graph = new Graph(600, 400, 'auto');
0344:     $graph->SetScale('textlin');
0345:     $graph->SetBackgroundGradient('#FFFFCC','#FFFFCC', GRAD_HOR, BGRAD_MARGIN);
0346: 
0347:     //タイトル、凡例などの設定
0348:     $graph->img->SetImgFormat('png');
0349:     $graph->img->SetMargin(60, 100, 20, 40);
0350:     $graph->title->SetFont(FF_PGOTHIC, FS_NORMAL, 14);
0351:     $graph->title->Set($disease . ':定点あたり報告数(速報値)');
0352:     $graph->xaxis->title->SetFont(FF_PGOTHIC);
0353:     $graph->xaxis->title->Set('');
0354:     $graph->xaxis->SetTickLabels($data_x);
0355:     $graph->xaxis->SetTextTickInterval(5);
0356:     $graph->xaxis->SetTextLabelInterval(1);
0357: 
0358:     $graph->yaxis->title->SetFont(FF_PGOTHIC);
0359:     $graph->yaxis->title->Set('報告数');
0360:     $graph->yaxis->SetTitleMargin(40);
0361: 
0362:     //折れ線グラフ
0363:     foreach ($items as $year=>$data) {
0364:         unset($data_y);
0365:         foreach ($data as $week=>$val) {
0366:             $data_y[$week - 1] = $items[$year][$week];
0367:         }
0368: 
0369:         //折れ線グラフ作成
0370:         $plotdata[$year] = new LinePlot($data_y);
0371:         $plotdata[$year]->SetLegend($year . '');                  //凡例
0372:         //グラフを結合する
0373:         $graph->Add($plotdata[$year]);
0374:         $plotdata[$year]->SetColor($PlotColor[$year - $start]);    //色
0375:         $plotdata[$year]->SetWeight($PlotWeight[$year - $start]);  //太さ
0376:     }
0377: 
0378:     //凡例
0379:     $graph->legend->SetFont(FF_PGOTHIC);                   //フォント
0380:     $graph->legend->SetColumns(1);                         //表示列数
0381:     $graph->legend->SetPos(0.01, 0.1, 'right', 'top');     //位置
0382: 
0383:     return $graph;
0384: }

解説:画面出力

画面への出力も JpGraph の機能を利用している。

コマンドライン・オプション img が指定された場合は、出力データを img タグの中に画像データを埋め込むための文字列を生成する。詳しくは、「PHP で TrueType フォントを利用する(その2)」-「解説:画像を HTML コンテンツに埋め込む」を参照のこと。

0414: //グラフを表示
0415: $graph = drawGraph($items$start$disease);
0416: if ($mode == 1) {
0417:     header("Content-type: image/png");
0418:     $graph->Stroke();
0419: else {
0420:     $im = $graph->Stroke(_IMG_HANDLER); 
0421:     ob_start();
0422:     ImagePNG($im); 
0423:     imagedestroy($im);
0424:     $res = base64_encode(ob_get_clean());
0425:     echo $res;
0426: }

解説:画面出力

コマンドライン・オプション file に続いてファイル名を指定すると、国立感染症研究所が発表している患者数データではなく、指定したデータファイルからデータを読み込んで表示する。

あらかじめ、次のような書式のデータファイルを用意しておく。
1行目は感染症名。2行目以降が 1 年分のデータで、カンマ区切り。1 カラム目は西暦年である。

インフルエンザ
1999,8.86,20.3,33.56,33.86,27.16,22.67,20.95,20.96,19.2,14.95,11.71,5.47,,1.5,
1.22,0.9,0.59,0.26,0.27,0.17,0.13,0.1,0.08,0.06,0.06,0.05,0.05,0.05,0.04,0.04,
0.03,0.02,0.01,0.02,0.03,0.03,0.02,0.02,0.03,0.04,0.05,0.07,0.08,0.09,0.1,0.
17,0.21,0.35,0.74,1.53,3.22,3.21,
2000,6.15,10.24,23.32,34.26,36.16,24.7,13.8,8.36,4.62,2.41,1.2,0.51,0.26,0.13,
0.1,0.07,0.05,0.03,0.04,0.03,0.03,0.03,0.03,0.02,0.02,0.02,0.02,0.01,0.01,
0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.02,0.02,0.02,0.03,0.03,0.04,
0.05,0.06,0.08,0.15,0.23,0.27,0.32,0.36,


ファウンロード・ファイルには、サンプルとしてデータファイル "influeza.txt" を同梱してある。

データを読み込むユーザー関数 readData:blue の流れは、「PHP で CSV ファイルを読み込む」で解説した通りである。

0211: /**
0212:  * データを配列に読み込む
0213:  * @param string $fname   読み込むデータファイル名
0214:  * @param string $disease 感染症名を格納
0215:  * @param array  $rec     データを格納する配列
0216:  * @return int データの開始年/FALSE
0217: */
0218: function readData($fname, &$disease, &$rec) {
0219:     $infp = @fopen($fname, 'r');
0220:     if ($infp == FALSEreturn FALSE;
0221: 
0222:     $start = 0;
0223:     $disease = trim(fgets($infp));
0224:     while (($arr = fgetcsv($infp, 1000, ',')) != FALSE) {
0225:         foreach ($arr as $key=>$val) {
0226:             if ($key == 0) {
0227:                 $year = $val;
0228:                 if ($start == 0)    $start = $year;
0229:                 $week = 1;
0230:             } else {
0231:                 $rec[$year][$week] = $val;
0232:                 $week++;
0233:             }
0234:         }
0235:     }
0236:     fclose($infp);
0237: 
0238:     return $start;
0239: }

参考サイト

(この項おわり)
header