倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Dataset で分類する Chart あれこれ (6) : BoxAndWhiskerCategoryDataset と BoxAndWhiskerChart

今回は CategoryDataste のサブタイプ BoxAndWhiskerCategoryDataset とそれを要求するチャート BoxAndWhiskerChart を見ていきます*1

「Whisker」はヒゲのことで、BoxAndWhisker 図は誤差を含めてチャートにする際によく用いられます。

BoxAndWhiskerCategoryDataset のインスタンスを取得する


BoxAndWhiskerCategoryDataset には、1つのカテゴリごとに次の統計値が設定できます(日本語が正しいかどうか不明):

  • 平均値 (mean)
  • 中間値 (median)
  • Q1 値 (lower quartile)
  • Q3 値 (upper quartile)
  • 最小正則値 (min regular value)
  • 最大正則値 (max regular value)
  • 最小の外れ値 (min outlier)
  • 最大の外れ値 (max outlier)

ただし、これら全ての値を設定する必要はないようです。 というより、データさえ用意すれば、これらの値を計算してくれるユーティリティがあります(知らずに自分で平均値とか計算しちゃったヨwww)

統計値を計算するユーティリティ・メソッドは org.jfree.data.statistics.BoxAndWhiskerCalculator#calculateBoxAndWhiskerStatistics() です。 これを使用したサンプルコードを見てみましょう。

このサンプルでは

  1. 100個の乱数を発生して(rand.nextDouble())、それを data1 に格納
  2. BoxAndWhiskerCategoryDataset のインスタンス DefaultBoxAndWhiskerCategoryDataset のインスタンスを生成
  3. BoxAndWhiskerCalculator.calculateBoxAndWhiskerStatistics() メソッドに data1 を渡して統計値を計算
  4. 計算した統計値(正確には BoxAndWhiskerItem オブジェクト)を BoxAndWhiskerCategoryDataset オブジェクトへセット

という手順でデータセットを作成しています:

def rand = new Random(1L)
int n = 100

def data1 = []
n.times{ data1 << rand.nextDouble() }

def dataset = new DefaultBoxAndWhiskerCategoryDataset()
dataset.add(BoxAndWhiskerCalculator.calculateBoxAndWhiskerStatistics(data1), 'row key', '1')

データセットの作成はコレで完了です。 他に統計値に関するユーティリティが必要な場合は、org.jfree.data.statistics パッケージにあるクラスのドキュメントを見てみるとよいかと思います。

BoxAndWhiskerChart

ChartFactory.createBoxAndWhiskerChart('Box and Whisker Chart (Category Dataset)', 'n', 'r^n', dataset, true)

  • 上記のサンプルの作成に、さらにあれこれデータを加えてます。

統計値を Groovy で計算してみる


最初、BoxAndWhiskerCalculator#calculateBoxAndWhiskerStatistics() メソッドなんて便利なものがあるのを知らなかったので、自分で統計値を計算してしまった。 そのまま削除するのも気が引けるので載っけておきます。 Groovy の練習程度に思っておこうかな。

データは Double の配列で作成し、乱数で初期化。 それを用いて (Defaut)BoxAndWhiskerCategoryDataset のデータを設定しています。 createBoxAndWhiskerItem() メソッドは後で実装。

def rand = new Random(1L)
int n = 100
assert n % 4 == 0    // Q1, Q3 を取得するのに面倒になるので n は4の倍数に制限

def data1 = []
for(i in 0..<n){ data1 << rand.nextDouble() }
data.sort()    // データの順序が変わってしまうけど、えいやと sort

def dataset = new DefaultBoxAndWhiskerCategoryDataset()
dataset.add(createBoxAndWhiskerItem(data), 'row key', 'r')

平均値の計算

まずは平均値。 sum() メソッドを使えば何てことなし。

def mean(List data){
    return data.sum() / data.size()
}

中間値

次は中間値。 これらは、データをソートさえしていれば超w簡単。

def median(List data){
    // data は sort 済み
    int half = data.size().intdiv(2) - 1
    return data[half]
}

Q1, Q3

Q1, Q3 値も中間値と大差なし。 返り値は [Q1, Q3] のリスト

def quartiles(List data){
    // data は sort 済み
    int quarter = data.size().intdiv(4)
    return [data[quarter-1], data[quarter*3-1]]
}

最小値、最大値

同じく最大値、最小値。

def boundaries(List] data){
    // data は sort 済み
    return [data[0], data[data.size()-1]]
}

BoxAndWhiskerItem

さーて、以上のメソッドを使って BoxAndWhiskerItem インスタンスを作成するためのデータのリストを作成:

def createBoxAndWhiskerItem(List data){
    def mean = mean(data)
    def median = median(data)
    def (q1, q3) = quartiles(data)
    def (min, max) = boundaries(data)

    return [mean, median, q1, q3, min, max, null, null]
}

リストの最後に指定している2つの null は外れ値の最小値と最大値。 指定しなくても例外が投げられたりはしない模様。

結構大変そうな統計値の計算も、それぞれが数行(ものによっては1行!)で書けてしまうという素晴らしさ。 天晴れ Groovy!

Groovyイン・アクション

Groovyイン・アクション

*1:BoxAndWhiskerChart は、BoxAndWhiskerCategoryDataset の他にも BoxAndWhiskerXYDataset から作成することもできます。 これは後ほど。