【PHP】箱ひげ図作成ツール

PHP

PHP-Boxplot

以前に作成した度数分布表作成ツール PHP-FrequencyTable

を使って分析した結果から、箱ひげ図を作成するツール

PHP-Boxplot を作りました。(PHP >= 8.1)

Githubで公開しています。Composerでインストールできます。

GitHub - macocci7/PHP-Boxplot: PHP-Boxplot creates boxplots using FrequencyTable.
PHP-Boxplot creates boxplots using FrequencyTable. - macocci7/PHP-Boxplot

PHP CLIで簡単で便利に使えるツールになっていると思います。

このような感じで箱ひげ図を簡単に作ることが

インストールの仕方

ローカルPCの作業用フォルダを作り、CLIで次のコマンドを実行するだけです。

ここでは、~/work/boxplot/ の中で実行しています。

composer require macocci7/php-boxplot

使用例1:模試の成績処理(ダミーデータ)

PHPコードの中で、データを2次元配列で渡して、create($path) するだけです。

使用例が同梱されているので、それをコピーしてくるのがイイかもしれません。

cp -ra ./vendor/macocci7/php-boxplot/examples/ ./

examples フォルダ内の BasicUsage.php を見てみましょう。

<?php

require('../vendor/autoload.php');

use Macocci7\PhpBoxplot\Boxplot;

$data = [
    '1st' => [ 75, 82, 96, 43, 78, 91, 84, 87, 93, ],
    '2nd' => [ 66, 74, 62, 100, 72, 68, 59, 76, 65, ],
    '3rd' => [ 56, 0, 45, 76, 58, 52, 13, 48, 54, 68, ],
    '4th' => [ 68, 32, 56, 92, 67, 72, 45, 76, 48, 73, ],
    '5th' => [ 70, 58, 62, 88, 62, 68, 56, 63, 64, 78, ],
];

$bp = new Boxplot();

$bp->setData($data)
   ->create('img/BasicUsage.png');

まずは実行してみましょう。examples フォルダ内で次のコマンドで実行します。

php -f BasicUsage.php

examples/img/BasicUsage.png という画像が生成されます。

$data に2次元配列でデータを格納しています。

このときのハッシュキーが横軸のラベルになります。

ハッシュキーの無い普通の2次元配列でも機能的には問題なく箱ひげ図を作れますが

横軸のラベルが配列のインデックスになります。

横軸のラベルは、後から labels($labels) で指定できます。

データ配列 $data を準備したら、

$bp = new Histogram();
$bp->setData($data)
   ->create('img/BoxplotExample.png');

だけで箱ひげ図を生成できます。次のような画像が生成されます。

模試の点数で -10点や110点は無いので、上限100点、下限0点として、

グリッド(横線)も10点毎に引くようにしたいですね。

また、ラベルとキャプションも付けたいですね。

次のように色々とプロパティ変更をしてから画像生成することができます。

$bp->setData($data)
   ->limit(0, 100) // 縦軸の表示領域:下限値、上限値の設定
   ->gridHeightPitch(20) // 縦軸のグリッド線の表示ピッチ
   ->gridVerticalOn() // 横軸のグリッド線の表示
   ->outlierOn() // 外れ値検出有効化:既定値 ON
   ->meanOn() // 平均値表示有効化:既定値 OFF
   ->labelX('Examination') // 横軸ラベル
   ->labelY('Score') // 縦軸ラベル
   ->caption('Results in 2022') // キャプション
   ->legends(['Donald Biden'])
   ->create('img/BoxplotExample.png');

limit(0, 100) では、縦軸表示領域の下限値と上限値を設定しています。

gridHeightPitch(20) では、縦軸のグリッド線(横線)を20ポイント毎に引くように設定しています。

gridVerticalOn() では、横軸のグリッド線(縦線)を引くように設定しています。

outlierOn() では、外れ値検出機能を有効にし、外れ値の表示をするように設定しています。

meanOn() では、平均値の表示をするように設定しています。

labelX() は横軸の下に表示するラベルの設定です。

labelY() は縦軸の左に表示するラベルの設定です。

caption() は上に表示するキャプションの設定です。

legends([‘Donald Biden’])では、凡例のラベル名一覧を配列で設定しています。

▼サンプルコード

<?php

require('../vendor/autoload.php');

use Macocci7\PhpBoxplot\Boxplot;

$data = [
    '1st' => [ 75, 82, 96, 43, 78, 91, 84, 87, 93, ],
    '2nd' => [ 66, 74, 62, 100, 72, 68, 59, 76, 65, ],
    '3rd' => [ 56, 0, 45, 76, 58, 52, 13, 48, 54, 68, ],
    '4th' => [ 68, 32, 56, 92, 67, 72, 45, 76, 48, 73, ],
    '5th' => [ 70, 58, 62, 88, 62, 68, 56, 63, 64, 78, ],
];

$bp = new Boxplot();

$bp->setData($data)
   ->limit(0, 100)
   ->gridHeightPitch(20)
   ->gridVerticalOn()
   ->meanOn()
   ->jitterOn()
   ->legendOn()
   ->labels([ '#1', '#2', '#3', '#4', '#5', ])
   ->labelX('Achievement Test')
   ->labelY('Score')
   ->caption('Achievement Test Results in 2023')
   ->legends(['Donald Biden'])
   ->create('img/AdjustDisplayByMethods.png');

▼実行結果

使用例2:ジッタープロット

箱ひげ図だけでは、ザックリとしたデータの分布を見ることができますが、

では、実際はどのような分布になっているのか?

というご要望にお応えするために、ジッタープロットというものがあります。

箱ひげ図の箱の幅の範囲に、実際のデータの分布をプロットしていくものです。

では、examples 内のファイル [DetmersReidBoxplot2023.php] を開いていみましょう。

<?php

require('../vendor/autoload.php');
require('./class/CsvUtil.php');

use Macocci7\PhpBoxplot\Boxplot;
use Macocci7\CsvUtil;

$bp = new Boxplot();
$csvUtil = new CsvUtil();
$csvFileName = 'csv/672282_data.csv';
$dailyData = $csvUtil->getDailyData($csvFileName);
if (!$dailyData) {
    echo "Failed to load CSV data.\n\n";
}
$labels = [];
foreach (array_keys($dailyData) as $datestring) {
    $labels[] = preg_replace('/^\d+\-(\d+)\-(\d+)$/', '$1/$2', $datestring);
}

$filePath01 = 'img/BoxplotDetmersReid2023_01.png';
$filePath02 = 'img/BoxplotDetmersReid2023_02.png';

$bp
   ->setData($dailyData)
   ->labels($labels)
   ->labelX('Game Date')
   ->labelY('MPH')
   ->caption('Release Speed: Detmers, Reid')
   ->meanOn()
   ->outlierOn()
   ->jitterOn()
   ->create($filePath01)
   ->outlierOff()
   ->jitterOff()
   ->create($filePath02);

さっそく実行してみます。

php -f DetmersReidBoxplot2023.php

examples/img/ 内に、BoxplotDetmersReid2023_01.png

が出来ていると思います。

MLBエンジェルスのリード・デトマーズ投手の投球スピードの試合毎のデータです。

データはMLB公式のデータ分析サイト「savant」からCSVダウンロードしています。

Baseball Savant: Statcast, Trending MLB Players and Visualizations
Baseball Savant

赤色の点が外れ値です。緑色の点がジッタープロットです。

こうしてみると、リードデトマーズ投手が速球とスローボールを

極端に投げ分けていることが見て取れます。

バッターも打ちにくいのではないでしょうか。

試しに、ジッタープロット無しの箱ひげ図も出してみましょう。

サンプルプログラムの ->jitterOn() の箇所をコメントアウトして実行してみましょう。

   //->jitterOn()

このような画像ができると思います。

赤い点の外れ値がやたら目立ちますが、

ぱっと見、ばらつきが大きすぎて、調子悪いのかな?と思ってしまいます。

が、ジッタープロットと併せて見れば、意識的に投げ分けていることが見て取れます。

使用例3:データセットと凡例表示

同じタイミングにおける、複数対象のデータをデータセットとして渡して

並べて表示することができます。また、凡例表示もできます。

examples 内の [BoxplotExampleCase.php] を見てみましょう。

<?php

require('../vendor/autoload.php');

use Macocci7\PhpBoxplot\Boxplot;

$faker = Faker\Factory::create();
$bp = new Boxplot();
$filePath = 'img/BoxplotExampleCase.png';

$keys = [
    '5/21',
    '5/22',
    '5/23',
    '5/24',
    '5/25',
];
$players = [
    'John',
    'Jake',
    'Hugo',
];
$dataset = [];
foreach ($players as $playre => $name) {
    $waightP = $faker->numberBetween(7, 13) / 10;
    $data = [];
    foreach ($keys as $index => $key) {
        $waightD = $faker->numberBetween(7, 13) / 10;
        $data[$index] = [];
        for ($i = 0; $i < $faker->numberBetween(50, 600); $i++) {
            $data[$index][] = $waightD * $waightP * $faker->numberBetween(600, 1100) / 100;
        }
    }
    $dataset[] = $data;
}
$bp->setDataset($dataset)
   ->resize(600, 400)
   ->boxWidth(20)
   ->gridVerticalOn()
   ->outlierOn()
   ->jitterOff()
   ->meanOn()
   ->legendOn()
   ->gridHeightPitch(2)
   ->labels($keys)
   ->labelX('Index')
   ->labelY('Value')
   ->caption('Random Data')
   ->legends($players)
   ->create($filePath);

ちょっと長めのコードですが、実行してみましょう。

php -f BoxplotExampleCase.php

examples/img/BoxplotExampleCase.png という画像ができていると思います。

John, Jake, Hugo の3人のダミーデータをランダム生成して5日分表示しています。

データセットの内容は次のような3次元配列になっています。

array(3) [
  [0] => array(5) [
    [0] => array(84) [5.436, 4.6512, 7.2216, 6.1848, 5.6016, ... ],
    [1] => array(77) [6.1544, 3.6624, 5.9304, 5.9752, 3.7632, ... ],
    [2] => array(82) [6.2424, 6.7752, 7.0128, 6.4872, 5.5152, ... ],
    [3] => array(84) [7.0848, 6.672, 10.5216, 9.7632, 6.8928, ... ],
    [4] => array(73) [5.9112, 7.5744, 4.6152, 5.9544, 4.536, ... ],
  ],
  [1] => array(5) [
    [0] => array(76) [6.1398, 5.4837, 8.2215, 5.6133, 5.0463, ... ],
    [1] => array(71) [5.4432, 8.7966, 6.0669, 7.5654, 5.2974, ... ],
    [2] => array(112) [5.9211, 5.7915, 5.9697, 8.019, 7.6545, ... ],
    [3] => array(78) [7.848, 6.768, 9.792, 6.723, 6.552, ... ],
    [4] => array(72) [4.3533, 6.0039, 4.3155, 4.4415, 6.0165, ... ],
  ],
  [2] => array(5) [
    [0] => array(86) [15.3348, 13.2444, 15.4596, 13.2132, 16.4268, ... ],
    [1] => array(72) [12.504, 12.468, 12.636, 12.108, 8.712, ... ],
    [2] => array(77) [9.156, 5.7708, 5.208, 5.082, 5.7036, ... ],
    [3] => array(70) [7.8732, 7.56, 8.5104, 8.0028, 7.8408, ... ],
    [4] => array(121) [11.4036, 12.2928, 10.3584, 9.9372, 12.7764, ... ],
  ],
]

対象のプレイヤー毎の日毎のデータを格納しています。

$dataset[$player][$day][$index]

のような構造です。ここでいう$player が凡例で表示される対象です。

このデータセットを渡すには、setDataset($dataset) を使います。

labels($keys) で横軸のラベル(日付)を1次元配列で渡しています。

legendOn() で凡例表示をONにし、

legends($players) で凡例を1次元配列で渡しています。

以上です。

(2024/03/10追記)Version 1.1.0 にバージョンアップしました。

詳細はこちらの記事をご覧ください。

コメント

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