目次
サンプル・プログラムの実行例
系外惑星は青い点で、回帰直線は水色の直線で描画する。また、太陽系惑星は黄色でプロットしている。
後述するように、XY軸の項目、グラフのサイズや描画色などは、グラフ描画ルール(XML形式ファイル)を編集することで自在に変えられる。
サンプル・プログラム
ExtrasolarPlanets.php | サンプル・プログラム本体 |
pahooStat.php | 統計に関わるクラス pahooStat。 使い方は「PHPで相関係数と回帰直線を表示」などを参照。include_path が通ったディレクトリに配置すること。 |
ExtrasolarPlanets.xml | グラフ描画ルール |
ExtrasolarPlanets.xsd | グラフ描画ルールのスキーマ |
NASA_Exoplanet_Archive.csv | 系外惑星のデータ。 入手方法は「系外惑星データのダウンロード」参照。 |
SolarPlanets.csv | 太陽系惑星のデータ |
バージョン | 更新日 | 内容 |
---|---|---|
1.4.0 | 2024/03/24 | 系外惑星データ更新 |
1.3.0 | 2023/10/22 | 系外惑星データ更新,等 |
1.2 | 2022/04/24 | 系外惑星データ更新,等 |
1.1 | 2021/05/15 | PHP8対応,系外惑星データ更新,等 |
1.0 | 2020/06/03 | 初版 |
関数/メソッド | 機能 | 詳細 |
---|---|---|
class::pahooStat | ||
__construct | コンストラクタ | |
__destruct | デストラクタ | |
iserror | エラー状況 | |
geterror | エラーメッセージ取得 | |
isphp5over | PHP5以上かどうか検査する | |
is_data | 配列かどうか、要素が1つ以上あるかどうか検査する | |
arithmetic_mean | 相加平均 | |
variance | 分散 | |
covariance | 共分散 | |
standard_deviation | 標準偏差 | |
correl | 相関係数 | |
correl_strength | 相関の強さ | |
slope | 回帰係数、切片 | |
simple_moving_average | 単純移動平均 | |
sampling_error | 標本誤差 | |
LSM | 最小二乗法(least squares method) |
解説:系外惑星データのダウンロード
ダウンロードしたファイルは、本プログラムと同じフォルダに移動する。
解説:太陽系惑星データ
冒頭2行はコメント。コメント行は冒頭が "#" であること。自由に増やすことができる。
冒頭行の次の行(このファイルでは3行目)が列のラベル名である。ラベル名は、先にダウンロードした系外惑星のラベル名と一致していること。このラベル名を参照して散布図を作るため。
最初のラベル "pl_name" は必須。散布図にプロットするとき、このデータの1行目を文字として表示するようにしている。叔父は、日本語、英語、記号のいずれでも可。
データの内容や、データ行数は自由に変更できる。
冥王星や小惑星を加えてもいいし、仮想の惑星を追加することもできる。
解説:グラフ描画ルール
データ解析の項目セットは、ExtrasolarPlanets->rule である。
セットは自由に追加できるが、かならず id をユニーク番号(正の整数)にすること。
グラフ描画ルールはXMLファイルにしているが、異常なパラメータによってプログラムが想定外の動作をしないよう、スキーマ "ExtrasolarPlanets.xsd" を使って記述内容をチェックする。スキーマ・ファイルの内容は変更しないこと。チェック方法は後述する。
解説:プログラムの準備
jqPlot の入手方法、インストール方法については、「PHPでNHK政治意識月例調査をグラフ表示」を参照してほしい。
ダウンロードしたファイル群を配置したフォルダを、定数 JQPLOT に定義する。
相関関係、回帰直線を計算するために、「PHPで相関係数と回帰直線を表示」で作成したクラスファイル "pahooStat.php" を利用する。組み込み関数 require_once を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
ダウンロードした系外惑星のデータは定数 FILE_EXOPLANET に、太陽系惑星データは FILE_SOLARPLANET に、設定ファイルは FILE_RULE に、スキーマは FILE_RULE_SCHEMA に、設定ファイルは FILE_RULE に、それぞれ定義しておく。
これらの定数は自由に変更できる。
35: //jqPlotのあるフォルダ
36: define('JQPLOT', '../../../../common/jqplot/');
37:
38: //太陽系惑星のデータ
39: define('FILE_SOLARPLANET', 'SolarPlanets.csv');
40:
41: //系外惑星のデータ
42: // https://exoplanetarchive.ipac.caltech.edu/ からダウンロード
43: define('FILE_EXOPLANET', 'NASA_Exoplanet_Archive.csv');
44:
45: //CSVデリミタ
46: define('CSV_DELIMITER', ',');
47:
48: //グラフ描画ルール
49: define('FILE_RULE', 'ExtrasolarPlanets.xml');
50: define('FILE_RULE_SCHEMA', 'ExtrasolarPlanets.xsd');
51:
52: //表示幅(デフォルト値)
53: define('WIDTH', 600);
54:
55: //散布図
56: define('PLOT_SCATTER', 'plotScatter');
57:
58: //統計に関わるクラス:include_pathが通ったディレクトリに配置
59: require_once('pahooStat.php');
解説:グラフ描画ルールを読み込む
259: /**
260: * グラフ描画ルールを読み込む
261: * @param string $xml 描画ルール・ファイル名
262: * @param string $xsd スキーマ・ファイル名
263: * @param string $errmsg エラーメッセージ格納用
264: * @return obj XMLオブジェクト/FALSE:エラー発生
265: */
266: function readRuleFile($xml, $xsd, &$errmsg) {
267: //ファイル存在チェック
268: if (! file_exists($xml)) {
269: $errmsg = 'エラー:グラフ描画ルール "' . $xml . '" がありません.';
270: return FALSE;
271: }
272: if (! file_exists($xsd)) {
273: $errmsg = 'エラー:スキーマ・ファイル "' . $xsd . '" がありません.';
274: return FALSE;
275: }
276: //スキーマ検査
277: $obj = new DOMDocument();
278: $obj->load($xml);
279: if (! $obj->schemaValidate($xsd)) {
280: $errmsg = 'グラフ描画ルールのエラー:'.
281: $errmsg .= FLAG_RELEASE ? '' : libxml_display_error();
282: $obj = FALSE;
283: //XMLファイル読み込み
284: } else {
285: $errmsg = '';
286: $obj = @simplexml_load_file($xml);
287: if ($obj == FALSE) {
288: $errmsg = 'エラー:グラフ描画ルール "' . $xml . '" がありません.';
289: $obj = FALSE;
290: }
291: }
292: return $obj;
293: }
まず、グラフ描画ルール・ファイルと対応するスキーマ・ファイルが存在するかどうかをチェックする。
次に、スキーマ・ファイルを使ってバリデーションを行う。
まず、XMLファイルを DOMDocumentとして load し、このオブジェクトに対して schemaValidate メソッドをかける。
schemaValidate メソッドは成否判定のみ返すので、失敗した場合は、ユーザー関数 libxml_display_error を使ってバリデーション・エラーの内容を取り出す。
成功した場合は、続けて simplexml_load_file を使って情報を取り出す。
213: /**
214: * LibXMLのエラー情報取得(下請け)
215: * @param object $error LibXMLエラー・オジェクト
216: * @return string エラー・メッセージ
217: */
218: function _libxml_display_error($error) {
219: switch ($error->level) {
220: case LIBXML_ERR_WARNING:
221: $res = "Warning {$error->code}: ";
222: break;
223: case LIBXML_ERR_ERROR:
224: $res = "Error {$error->code}: ";
225: break;
226: case LIBXML_ERR_FATAL:
227: $res = "Fatal Error {$error->code}: ";
228: break;
229: }
230: $res .= trim($error->message);
231:
232: if ($error->file) {
233: $res .= " in {$error->file}";
234: }
235:
236: $res .= " on line {$error->line}";
237:
238: return $res;
239: }
241: /**
242: * LibXMLのエラー情報取得
243: * @return string エラー・メッセージ
244: */
245: function libxml_display_error() {
246: $errors = libxml_get_errors();
247: $res = '';
248: foreach ($errors as $error) {
249: $res .= _libxml_display_error($error);
250: $res .= "\n";
251: }
252: libxml_clear_errors();
253:
254: return $res;
255: }
257: if (! FLAG_RELEASE) libxml_use_internal_errors(TRUE);
258:
エラー情報は配列として格納されているので、その1つ1つの要素を取り出して、メッセージが読みやすいよう、ユーザー関数 _libxml_display_error によって加工する。
最後に、組み込み関数 libxml_clear_errors を呼び出してエラー情報をクリアしておく。
なお、サーバの設定によっては LibXML のエラーを返さないことがあるので、定数 FLAG_RELEASE がfalse(デバッグ時)には、 libxml_use_internal_errors を使ってエラーを返すように設定しておく。
XML Schema については、「XMLマスターポイントレッスン ~ ベーシック編 ~」にコンパクトにまとまっているので、ご覧いただきたい。
今回のグラフ描画ルールのように、プログラムに制御に関わるパラメータをXMLに入れた場合、想定外のパラメータが入っているとプログラムが予期しない動作をするかもしれない。そこでXMLのバリデーションチェックが必要となる。今回のプログラム開発を通じ、XML Schema がバリデーションチェックのための便利な仕組みとなることが分かった。
解説:データ・ファイルを読み込む
295: /**
296: * データ・ファイルを読み込む
297: * @param string $fname 入力ファイル名
298: * @param array $columns $itemsに格納したいカラム名(配列)
299: * @param array $items データを格納する配列
300: * @return int データ件数/FALSE
301: */
302: function readDataFile($fname, $columns, &$items) {
303: $infp = fopen($fname, 'r');
304: if (! $infp) return FALSE;
305:
306: $labels = array(); //カラム明格納用
307: $cnt = 0;
308: while (! feof($infp)) {
309: $str = trim(fgets($infp, 10000));
310: if ($str == '') continue; //空行はスキップ
311: if ($str == FALSE) break;
312: $str = mb_convert_encoding($str, INTERNAL_ENCODING, TARGET_ENCODING);
313: if (preg_match('/^#/iu', $str) > 0) continue; //コメットはスキップ
314: $arr = mb_split(CSV_DELIMITER, $str);
315: foreach ($arr as $key=>$val) {
316: //カラム名
317: if ($cnt == 0) {
318: $labels[$key] = $val;
319: //データ部
320: } else {
321: foreach ($columns as $val2) {
322: if ((string)$val2 == $labels[$key]) {
323: $items[$cnt][(string)$val2] = $val;
324: }
325: }
326: }
327: }
328: $cnt++;
329: }
330: fclose($infp);
331:
332: //すべてのカラムが揃っていないデータは破棄
333: foreach ($items as $key1=>$arr) {
334: foreach ($arr as $key2=>$val) {
335: if ($val == '') {
336: unset($items[$key1]);
337: continue;
338: }
339: }
340: }
341:
342: return ($cnt - 1);
343: }
散布図を描くために必要になるラベル名だけ配列 $items に読み込む。
ラベル名は、$columns に配列として与える。
読み込み中にコメントをスキップし、文字コードは mb_convert_encoding を使って本プログラムのエンコード方式(定数 INTERNAL_ENCODING で定義)に変換している。
$columns で指定した全てのカラム・データが揃っていないデータセットは、散布図絵を描くのに使えないので、配列から取り除く。
解説:散布図
365: /**
366: * jqPlot用のスクリプト:散布図
367: * @param string $name オブジェクト名
368: * @param object $xml グラフ描画ルール(全体)
369: * @param object $rule グラフ描画ルール(グラフID)
370: * @param int $width, $height グラフの幅・高さ(ピクセル)
371: * @param array $x, $y 系外惑星のデータ配列
372: * @param array $x2, $y2, $z3 太陽系惑星のデータ配列
373: * @return string スクリプト/FALSE
374: */
375: function plotScatter($name, $xml, $rule, $x, $y, $x2, $y2, $z2) {
376: //グラフ描画ルール(全体)
377: $width = (int)$xml->graph_width;
378: $height = (int)$xml->graph_height;
379: $ypadding = 0 - (int)((int)$xml->solar->size / 2);
380:
381: //相関係数、回帰直線
382: $pst = new pahooStat();
383: $r = $pst->correl($x, $y); //相関係数
384: if ($pst->iserror()) return FALSE;
385: $st = $pst->correl_strength($r);
386: $r = sprintf('%4.3f (%s)', $r, $st);
387: list($a, $b) = $pst->slope($x, $y); //回帰係数、切片
388: if ($pst->iserror()) return FALSE;
389: $x0 = min($x);
390: $y0 = $a * $x0 + $b;
391: $x1 = max($x);
392: $y1 = $a * $x1 + $b;
393: $pst = NULL;
394:
395: //系列の生成:系外惑星
396: $series = '';
397: $n = min(count($x), count($y));
398: for ($i = 0; $i < $n; $i++) {
399: if (($x[$i] >= $rule->xaxis->min) && ($x[$i] <= $rule->xaxis->max) &&
400: ($y[$i] >= $rule->yaxis->min) && ($y[$i] <= $rule->yaxis->max)) {
401: $series .= sprintf('[%f, %f], ', $x[$i], $y[$i]);
402: }
403: }
404: //系列の生成:太陽系の惑星
405: $series2 = '';
406: $n = min(count($x2), count($y2));
407: for ($i = 0; $i < $n; $i++) {
408: if (($x2[$i] >= $rule->xaxis->min) && ($x2[$i] <= $rule->xaxis->max) &&
409: ($y2[$i] >= $rule->yaxis->min) && ($y2[$i] <= $rule->yaxis->max)) {
410: $series2 .= sprintf("[%f, %f, '%s'], ", $x2[$i], $y2[$i], $z2[$i]);
411: }
412: }
413:
414: //対数軸にするかどうか
415: $rendere_xaxis = ($rule->xaxis->log == 'true') ? 'renderer: $.jqplot.LogAxisRenderer,' : '';
416: $rendere_yaxis = ($rule->yaxis->log == 'true') ? 'renderer: $.jqplot.LogAxisRenderer,' : '';
417:
418: //jqplot用スクリプト生成
419: $js =<<< EOT
420: <script>
421: jQuery(function() {
422: jQuery.jqplot('{$name}',
423: [
424: [ {$series} ],
425: [ {$series2} ]
426: ],
427: {
428: //タイトル
429: title: {
430: text: '<span style="font-size:{$rule->title->size}; color:{$rule->title->color};">{$rule->title->label}</span><br /><span style="font-size:{$rule->correlation->size}; color:{$rule->correlation->color};">相関係数 = {$r}</span>',
431: show: true,
432: fontSize: '20px',
433: textAlign: 'center',
434: textColor: 'black'
435: },
436: series:[
437: //散布図:系外惑星
438: {
439: showLine: false,
440: color : '{$xml->extra->color}',
441: markerOptions: { size: '{$xml->extra->size}' }
442: },
443: //散布図:太陽系惑星
444: {
445: showLine: false,
446: color : '{$xml->solar->color}',
447: markerOptions: { size: '{$xml->solar->size}' },
448: pointLabels: {
449: show: true,
450: location: 'n',
451: ypadding: '{$ypadding}',
452: }
453: }
454: ],
455: //回帰直線
456: canvasOverlay: {
457: show: true,
458: objects: [
459: {
460: line: {
461: lineWidth: '{$xml->slope->width}',
462: color: '{$xml->slope->color}',
463: shadow: false,
464: lineCap: 'round',
465: start: [ {$x0}, {$y0} ],
466: stop: [ {$x1}, {$y1} ]
467: }
468: }]
469: },
470: //軸
471: axes: {
472: xaxis: {
473: {$rendere_xaxis}
474: label: '{$rule->xaxis->label}',
475: tickOptions: { formatString: "{$rule->xaxis->format}" }
476: },
477: yaxis: {
478: {$rendere_yaxis}
479: label: '{$rule->yaxis->label}',
480: tickOptions: { formatString: "{$rule->yaxis->format}" }
481: }
482: }
483: }
484: );
485: });
486: </script>
487: <div id="{$name}" style="width:{$width}px; height:{$height}px;"></div>
488:
489: EOT;
490:
491: return $js;
492: }
回帰直線は、X軸の最小値を始点とし、同じく最大値を終点とする直線で、jqPlotプラグインの「jqplot.dateAxisRenderer.js」(グラフに描き加える線)で行う。
参考サイト
- 『地球は特別な惑星か? 地球外生命に迫る系外惑星の科学』:ぱふぅ家のホームページ
- PHPで相関係数と回帰直線を表示:ぱふぅ家のホームページ
- NASA Exoplanet Archive
- XMLマスターポイントレッスン ~ ベーシック編 ~
今回は、「PHPで相関係数と回帰直線を表示」で作成したプログラムを利用し、系外惑星のデータ解析を行い、相関関係や太陽系惑星との比較、散布図上に回帰曲線を描くプログラムを作ることにする。
また、プログラムを変更することなく、設定ファイルを変更することでデータ解析の項目やグラフ描画方式を変更できるようにする。
(2024年3月24日)系外惑星データ更新
(2023年10月22日)系外惑星データ更新