PHPで系外惑星の特性解析

(1/1)
地球は特別な惑星か? 地球外生命に迫る系外惑星の科学』(成田憲保=著,講談社,2020年3月)を読んで、地球は特別な惑星か調べたくなった。2019年(平成31年)のノーベル物理学賞に関わるテーマでもある。そこで本書が引用している NASA Exoplanet Archive にアクセスしたところ、これまで発見されている4千を越える系外惑星のデータをダウンロードできることが分かった。

今回は、「PHPで相関係数と回帰直線を表示」で作成したプログラムを利用し、系外惑星のデータ解析を行い、相関関係や太陽系惑星との比較、散布図上に回帰曲線を描くプログラムを作ることにする。
また、プログラムを変更することなく、設定ファイルを変更することでデータ解析の項目やグラフ描画方式を変更できるようにする。

(2024年3月24日)系外惑星データ更新
(2023年10月22日)系外惑星データ更新

目次

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

PHPで系外惑星の特性解析
描画したいグラフをラジオボタンで選択し、描画ボタンを押下すると、グラフを描画する。
系外惑星は青い点で、回帰直線は水色の直線で描画する。また、太陽系惑星は黄色でプロットしている。
後述するように、XY軸の項目、グラフのサイズや描画色などは、グラフ描画ルール(XML形式ファイル)を編集することで自在に変えられる。

サンプル・プログラム

圧縮ファイルの内容
ExtrasolarPlanets.phpサンプル・プログラム本体
pahooStat.php統計に関わるクラス pahooStat。
使い方は「PHPで相関係数と回帰直線を表示」などを参照。include_path が通ったディレクトリに配置すること。
ExtrasolarPlanets.xmlグラフ描画ルール
ExtrasolarPlanets.xsdグラフ描画ルールのスキーマ
NASA_Exoplanet_Archive.csv系外惑星のデータ。
入手方法は「系外惑星データのダウンロード」参照。
SolarPlanets.csv太陽系惑星のデータ
ExtrasolarPlanets.php 更新履歴
バージョン 更新日 内容
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 初版
pahooStat.php 関数/メソッド一覧
関数/メソッド 機能 詳細
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)

解説:系外惑星データのダウンロード

NASA Exoplanet Archive
まず、NASA Exoplanet Archive から系外惑星のデータをダウンロードする。

①をクリックすると、すべての系外惑星データを一覧表示する画面に移行する。
NASA Exoplanet Archive
次に「②Download Table」「③CSV Format」「④Download Table」の順に選ぶと、すべての系外惑星データをCSV形式ファイルでダウンロードする。
ダウンロードしたファイルは、本プログラムと同じフォルダに移動する。
NASA Exoplanet Archive
ダウンロードしたCSVファイルを開くと、冒頭350行目くらいまでは、タイトルと列の説明をしたコメント文で埋まっている。
NASA Exoplanet Archive
360行目前後に列のラベル名があり、それ以降最終行までがデータ部分になっている。

解説:太陽系惑星データ

太陽系惑星データ
圧縮ファイルに同梱している "SolarPlanets.csv" は、系外惑星と比較するときに使う太陽系惑星のデータである。
冒頭2行はコメント。コメント行は冒頭が "#" であること。自由に増やすことができる。
冒頭行の次の行(このファイルでは3行目)が列のラベル名である。ラベル名は、先にダウンロードした系外惑星のラベル名と一致していること。このラベル名を参照して散布図を作るため。

最初のラベル "pl_name" は必須。散布図にプロットするとき、このデータの1行目を文字として表示するようにしている。叔父は、日本語、英語、記号のいずれでも可。

データの内容や、データ行数は自由に変更できる。
冥王星や小惑星を加えてもいいし、仮想の惑星を追加することもできる。

解説:グラフ描画ルール

データ解析の項目やグラフ描画色などは、同梱しているXMLファイル "ExtrasolarPlanets.xml" で定義している。定義内容は下図の通りで、内容はスキーマ・ファイルで定義した範囲内で自由に追加・変更できる。
グラフ描画ルール(xml) ExtrasolarPlanets graph_width グラフの幅(ピクセル) graph_height グラフの高さ(ピクセル) extra:系外惑星プロット・パラメータ  color カラー size マーカーサイズ solar:太陽系惑星プロット・パラメータ  color カラー size マーカーサイズ slope:回帰直線  color カラー width 太さ rule:グラフ描画ルール(複数)  id グラフID(ユニークキー) columns プロットするカラム名(pl_name固定) columns プロットするカラム名(X軸) columns プロットするカラム名(Y軸) title:グラフ・タイトル  label タイトル・ラベル size フォント・サイズ color フォント・カラー correlation:相関係数表示  size フォント・サイズ color フォント・カラー xaxis:X軸パラメータ  label 軸ラベル log 対数軸 true|false format 数値フォーマット min 最小値 max 最大値 yaxis:Y軸パラメータ  label 軸ラベル log 対数軸 true|false format 数値フォーマット min 最小値 max 最大値
たとえば、系外惑星のプロット色を緑したければ ExtrasolarPlanets->extra->color を green に変更して保存する。Webカラー名(英語)または#ではじまるRGBコードを指定できる。(カラー指定については、他所も同様)

データ解析の項目セットは、ExtrasolarPlanets->rule である。
セットは自由に追加できるが、かならず id をユニーク番号(正の整数)にすること。

グラフ描画ルールはXMLファイルにしているが、異常なパラメータによってプログラムが想定外の動作をしないよう、スキーマ "ExtrasolarPlanets.xsd" を使って記述内容をチェックする。スキーマ・ファイルの内容は変更しないこと。チェック方法は後述する。

解説:プログラムの準備

グラフ描画には jQueryプラグイン「jqPlot」を利用する。
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: }

グラフ描画ルールを読み込むユーザー関数が readRuleFile である。

まず、グラフ描画ルール・ファイルと対応するスキーマ・ファイルが存在するかどうかをチェックする。

次に、スキーマ・ファイルを使ってバリデーションを行う。
まず、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: 

バリデーション・エラーは、 LibXML のエラーとして蓄積されていることから、ユーザー関数 libxml_display_error の中で組み込み関数  libxml_get_errors  を使ってエラーを取り出す。
エラー情報は配列として格納されているので、その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> 0continue;   //コメットはスキップ
 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: }

系外惑星データや太陽系枠データを読み込むユーザー関数が readDataFile である。

散布図を描くために必要になるラベル名だけ配列 $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: }

配列に格納したデータを、jqPlotスクリプトへ展開し散布図とする処理は、ユーザー関数 plotScatter で行う。

回帰直線は、X軸の最小値を始点とし、同じく最大値を終点とする直線で、jqPlotプラグインの「jqplot.dateAxisRenderer.js」(グラフに描き加える線)で行う。

参考サイト

(この項おわり)
header