PHPで相関係数と回帰直線を表示

(1/1)
PHPで人口を推計する」では、日本の将来推計人口をシミュレーションした。現実には、合計特殊出生率が、なかなか上昇せず、人口減少が加速している。
なぜ出生率は上昇しないのか――婚姻率の低下と離婚率の上昇が関係しているのではないかと考え、それを確かめるため、高校数学で習った相関係数を求めてみることにする。
あわせて、データ集合の散布図回帰曲線を描いてみる。

(2022年4月17日)統計表を更新,PHP8対応,リファラ・チェック改良。

目次

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

PHPで相関係数と回帰直線を表示
当初の予想通り、婚姻率と出生率の間には強い正の相関関係が、離婚率と出生率の間には負の相関関係があることが検証できた。

ここで注意してほしいことがある――婚姻率や離婚率と出生率の間に相関関係があるからといっても、それは、婚姻は離婚が原因で出生率が低下しているという結果(因果関係)になっているわけではない。つまり、相関関係と因果関係は別物であるということだ。

2つのデータ集合X, Yがあり、Xが変化すればYも変化することを相関関係という。
これに対し、X(原因)→Y(結果)という一方通行の関係が因果関係である。相関関係は因果関係の必要条件になり得るが、十分条件ではないのである。

サンプル・プログラム

圧縮ファイルの内容
correlation.phpサンプル・プログラム本体
birthrate.csv統計表
pahooStat.php統計に関わるクラス pahooStat。
使い方は「PHPで相関係数と回帰直線を表示」などを参照。include_path が通ったディレクトリに配置すること。

解説:準備

  35: //jqPlotのあるフォルダ
  36: define('JQPLOT', '../../../../common/jqplot/');
  37: 
  38: //統計表
  39: define('FILE_CSV', 'birthrate.csv');
  40: 
  41: //CSVデリミタ
  42: define('CSV_DELIMITER', ',');
  43: 
  44: //グラフの幅、高さ
  45: define('GRAPH_WIDTH',  600);
  46: define('GRAPH_HEIGHT', 350);
  47: 
  48: //婚姻率、離婚率、合計特殊出生率 推移グラフの名前
  49: define('GRAPH_PROGRESS', 'jqPlot_progress');
  50: 
  51: //合計特殊出生率=婚姻率 相関グラフの名前
  52: define('GRAPH_MARRIAGE', 'jqPlot_marriage');
  53: 
  54: //合計特殊出生率=離婚率 相関グラフの名前
  55: define('GRAPH_DIVORCE', 'jqPlot_divorce');
  56: 
  57: //合計特殊出生率=初婚年齢 相関グラフの名前
  58: define('GRAPH_1STMARRIAGE', 'jqPlot_1stmarriage');
  59: 
  60: //統計に関わるクラス:include_pathが通ったディレクトリに配置
  61: require_once('pahooStat.php');

年次推移や散布図を描くのに、jQueryプラグイン「jqPlot」を利用する。
jqPlot の入手方法、インストール方法については、「PHPでNHK政治意識月例調査をグラフ表示」を参照してほしい。

相関係数PECL の統計ライブラリを導入すれば計算できるが、今回は、自前のクラスを用意することにした。
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」で解説している。
クラスファイル "pahooStat.php" は、組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
pahooStat クラスの内容については後述する。

毎年の婚姻率、離婚率、合計特殊出生率は、厚生労働省「令和2年 人口動態統計の年間推計について」をExcelに入力し、統計表ファイル FILE_CSV(CSV形式ファイル)として保存してある。また、平均初婚年齢(妻)は、厚生労働省「令和2年 人口動態統計月報年計(概数)の概況」を利用した。
これらのデータを参照し、各種グラフを描いてゆく。

解説:データの検査

クラス pahooStat では、配列を計算することが多い。
そこで、引数として渡された変数が配列であり、かつ要素が1つ以上あることを検査するメソッド is_data を用意した。

解説:相加平均

日常、よく使う平均は「相加平均(算術平均)」と呼ばれ、次の計算式によって導かれる。
Excelの AVERAGE関数に相当する。
 mimetex 

解説:分散

分散は、データの散らばり度合いを表す値で、偏差(各々の値と相加平均の差)を二乗し、その相加平均を求める。
Excelの VARP関数に相当する。
 mimetex 

解説:標準偏差

標準偏差は、データの散らばり度合いを表す値で、分散の平方根を求める。
Excelの STDEV.P関数に相当する。
 mimetex 

解説:共分散

共分散は、2種類のデータ集合の関係を示す指標で、2つのデータ集合の偏差の積の平均を求める。
Excelの COVAR関数に相当する。
 mimetex 

解説:相関係数

相関係数は、2種類のデータ集合の関係を示す指標で、単位の影響を受けずに関係を示すことができる。
Excelの CORREL関数に相当する。
 mimetex 

解説:相関の強さ

相関係数の値と、相関の強さの基準は下表の通りである。
相関係数 r 相関の強さ
-1.0 ≦ r < -0.7強い負の相関
-0.7 ≦ r < -0.4負の相関
-0.4 ≦ r < -0.2弱い負の相関
-0.2 ≦ r < +0.2ほとんど相関がない
+0.2 ≦ r < +0.4弱い正の相関
+0.4 ≦ r < +0.7正の相関
+0.7 ≦ r ≦ +1.0強い正の相関

解説:回帰直線

2種類のデータ集合を散布図にプロットしたとき、相関があれば、直線上にデータが並ぶ。この直線のことを回帰直線と呼び、最小自乗法(最小二乗法)によって求めることができる。
Excelの SLOPE関数INTERCEPT関数に相当する。

回帰直線を  mimetex  とするとき、a, b は次の式によって求める。
 mimetex 

 mimetex 

解説:散布図

 298: /**
 299:  * jqPlot用のスクリプト:散布図
 300:  * @param   array $x, $y データ配列
 301:  * @param   string $label_x, $label_y 軸ラベル
 302:  * @param   string $name オブジェクト名
 303:  * @return  string スクリプト/FALSE
 304: */
 305: function plotScatter($x, $y, $label_x, $label_y, $name) {
 306:     //相関係数、回帰直線
 307:     $pst = new pahooStat();
 308:     $r = $pst->correl($x, $y);              //相関係数
 309:     if ($pst->iserror())    return FALSE;
 310:     $st = $pst->correl_strength($r);
 311:     $r = sprintf('%4.3f (%s)', $r, $st);
 312:     list($a, $b) = $pst->slope($x, $y);     //回帰係数、切片
 313:     if ($pst->iserror())    return FALSE;
 314:     $x0 = min($x);
 315:     $y0 = $a * $x0 + $b;
 316:     $x1 = max($x);
 317:     $y1 = $a * $x1 + $b;
 318:     $pst = NULL;
 319: 
 320:     //系列の生成
 321:     $series = '';
 322:     $n = min(count($x), count($y));
 323:     for ($i = 0$i < $n$i++) {
 324:         $series .sprintf('[%f, %f], ', $x[$i], $y[$i]);
 325:     }
 326: 
 327:     $width  = GRAPH_WIDTH;
 328:     $height = GRAPH_HEIGHT;
 329:     $js =<<< EOT
 330: <script>
 331: jQuery(function() {
 332:     jQuery.jqplot('{$name}',
 333:     [
 334:         [ {$series} ]
 335:     ],
 336:     {
 337:         //タイトル
 338:         title: {
 339:             text: '{$label_x}={$label_y}<br /><span style="font-size:small; color:blue;">相関係数 = {$r}</span>',
 340:             show: true,
 341:             fontSize: '20px',
 342:             textAlign: 'center',
 343:             textColor: 'black'
 344:         },
 345:         //散布図
 346:         seriesDefaults: {
 347:             showLine: false,
 348:             color: 'green',
 349:             markerOptions: { size: 4 }
 350:         },
 351:         //回帰直線
 352:         canvasOverlay: {
 353:             show: true,
 354:             objects: [
 355:             {
 356:                 line: {
 357:                     lineWidth: 3,
 358:                     color: 'blue',
 359:                     shadow: false,
 360:                     lineCap: 'round',
 361:                     start: [ {$x0}, {$y0} ],
 362:                     stop:  [ {$x1}, {$y1} ]
 363:                 }
 364:             }]
 365:         },
 366:         //軸
 367:         axes: {
 368:             xaxis: {
 369:                 label: '{$label_x}',
 370:                 tickOptions: { formatString: '%.1f' }
 371:             },
 372:             yaxis: {
 373:                 label: '{$label_y}',
 374:                 tickOptions: { formatString: '%.1f' }
 375:             }
 376:         }
 377:     }
 378:     );
 379: });
 380: </script>
 381: <div id="{$name}" style="width:{$width}px; height:{$height}px;"></div>
 382: 
 383: EOT;
 384: 
 385:     return $js;
 386: }

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

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

解説:年次推移グラフ

 197: /**
 198:  * jqPlot用のスクリプト:年次推移グラフ
 199:  * @param   array  $items データ配列
 200:  * @param   array  $labels ラベル配列
 201:  * @param   string $name オブジェクト名
 202:  * @param   string $title グラフ・タイトル
 203:  * @return  string スクリプト
 204: */
 205: function plotProgress($items, $labels, $name, $title) {
 206:     //系列の生成
 207:     $series = array();
 208:     $n = count($labels);
 209:     for ($i = 1$i < $n$i++) {
 210:         $series[$i] = '';
 211:         foreach ($items[$ias $key=>$val) {
 212:             $series[$i.sprintf('[%d, %f], ', $items[0][$key], $val);
 213:         }
 214:     }
 215:     $s1 = '';
 216:     $s2 = '';
 217:     foreach ($series as $key=>$val) {
 218:         $s1 ."[ {$val} ], ";
 219:         if (max($items[$key]) > 20) {
 220:             $s2 .=<<< EOT
 221:                 {
 222:                 label: '{$labels[$key]}',
 223:                 yaxis: 'y2axis'
 224:                 },
 225: 
 226: EOT;
 227:         } else {
 228:             $s2 .=<<< EOT
 229:                 {
 230:                 label: '{$labels[$key]}'
 231:                 },
 232: 
 233: EOT;
 234:         }
 235:     }
 236: 
 237:     $width  = GRAPH_WIDTH;
 238:     $height = GRAPH_HEIGHT;
 239:     $js =<<< EOT
 240: <script>
 241: jQuery(function() {
 242:     jQuery.jqplot('{$name}',
 243:     [
 244:         {$s1}
 245:     ],
 246:     {
 247:         //タイトル
 248:         title: {
 249:             text: '{$title}',
 250:             show: true,
 251:             fontSize: '20px',
 252:             textAlign: 'center',
 253:             textColor: 'black'
 254:         },
 255:         //ラベル
 256:         series: [
 257:             {$s2}
 258:         ],
 259:         legend: {
 260:             show: true,
 261:             placement: 'inside',
 262:             location: 'ne',
 263:             renderer: $.jqplot.EnhancedLegendRenderer,
 264:             rendererOptions: { numberRows: 1 }
 265:         },
 266:         //グラフ
 267:         seriesDefaults: {
 268:             showLine: true,
 269:             rendererOptions: { smooth: false },
 270:             markerOptions: { size: 0 },
 271:         },
 272:         //軸
 273:         axes: {
 274:             xaxis: {
 275:                 label: '{$labels[0]}',
 276:                 tickOptions: { formatString: '%d' }
 277:             },
 278:             yaxis: {
 279:                 label: '率',
 280:                 tickOptions: { formatString: '%.1f' }
 281:             },
 282:             y2axis: {
 283:                 label: '年齢',
 284:                 tickOptions: { formatString: '%d' }
 285:             }
 286:         }
 287:     }
 288:     );
 289: });
 290: </script>
 291: <div id="{$name}" style="width:{$width}px; height:{$height}px;"></div>
 292: 
 293: EOT;
 294: 
 295:     return $js;
 296: }

配列に格納したデータから、合計特殊出生率、婚姻率、離婚率、平均初婚年齢(妻)の年次推移をjqPlotスクリプトへ展開し、折れ線グラフとする処理は、ユーザー関数 plotProgress で行う。

参考サイト

(この項おわり)
header