倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

commons-math3 解読 (1-1) : 度数 Frequency : math3.stat パッケージ

今回は org.apache.commons.math3.stat パッケージに定義されている Frequency クラスを見ていきます。 このクラスは度数を扱うクラスです。 もう少し一般の言葉で言うと「frequency」は回数・頻度という意味で、データの集合に対してある値のデータがいくつあるかといったことを扱うために使います。 ヒストグラムを思い浮かべるとイメージしやすいかと。

参考 URL

Frequency クラス

org.apache.commons.math3.stat.Frequency クラスに定義されているメソッドは以下の通り:

package org.apache.commons.math3.stat;

public class Frequency {

    //***** データの追加・取得・クリア *****
    // データの追加
    void addValue(int v)
    void addValue(long v)
    void addValue(char v)
    void addValue(Comparable<?> v)

    // データの取得・クリア
    int getUniqueCount()    // 異なる値の数(ヒストグラムでいうビンの個数)
    Iterator<Comparable<?>> valuesIterator()
    long getSumFreq()    // 全データ数
    void clear()

    //***** 統計の取得 *****
    // 度数 the number of values (frequency)
    long getCount(int v)
    long getCount(long v)
    long getCount(Comparable<?> v)

    // 累積度数 cumulative frequency
    long getCumFreq(int v)
    long getCumFreq(long v)
    long getCumFreq(char v)
    long getCumFreq(Comparable<?> v)

    // 相対度数 percentage
    double getPct(int v)
    double getPct(long v)
    double getPct(char v)
    double getPct(Comparable<?> v)

    // 累積相対度数 cumulative percentage
    double getCumPct(int v)
    double getCumPct(long v)
    double getCumPct(char v)
    double getCumPct(Comparable<?> v)

データの追加や統計の取得に関するメソッドでは、引数の型だけが異なる幾つかのオーバーロードがあります。 引数の型として定義されているのは

  • int
  • long
  • char
  • Comparable

です(getCount() メソッドだけ char を引数にとれないようですが)。 char 型は何のために入れられているのか不明(符号なし整数?)。 整数型の int, long 以外でも、Comparable インターフェースを実装しているクラスのインスタンス(例えば String オブジェクトなど)なら Frequency によって度数を計算することができます。 各データの比較は、通常自然な順序(Comparable#compareTo() メソッド)で行われますが、別の比較アルゴリズムを使いたい場合は、Frequency オブジェクトを生成する際にコンストラクタに Comparator オブジェクトを渡します。

ちょっと小難しく言うと、Frequency クラスはデータをある同値関係によってグループ分けして、そのグループの個数を返すということをしています。 ある同値関係とは、Comparator オブジェクトを指定しなければ自然な順序です。 ただし、度数や相対度数を計算するだけなら同値関係だけでも可能ですが、累積度数や累積相対度数を計算するためには順序関係(大小関係)も必要なので、Frequency クラスは Comparator, Comparable オブジェクトを要求しています(そもそも Java に同値関係を抽象化したインターフェースとかないし)。

ちょっと気になるのは、この Frequency クラスの定義だと、1つの Frequency オブジェクトに対して異なる Comparable オブジェクトを加えようとしても実行時まで気づかない点です。 例えば1つの Frequency オブジェクトに String や Integer や Date などのオブジェクトを追加していてもコンパイルは通りますが、実行時に例外が投げられてしまいます。 こういうのは Generics を使って

public class Frequency<T extends Comparable<T>>{
    public void addValue(T value);
    public long getCount(T value);
    ...

   public static Frequency<Integer> createIntegerFrequency();
}

と定義した方がいいと思いますが(必要なら static ファクトリメソッドも付けて)、どうでしょう?

まぁあと、メソッド名が読みにくくて、慣れてないと困りますね。

  • Freq : frequency 「度数、回数、頻度」
  • Pct : percentage 「パーセント、割合、相対度数」
  • Cum : cumulative 「累積」

きちんと書くとメソッド名が長くなるってのもわかりますけどね。

使用手順

Frequency クラスを使用する手順は以下のようになります:

  1. Frequency オブジェクトを生成
  2. Frequency オブジェクトにデータを追加
  3. Frequency オブジェクトから統計を取得

Frequency オブジェクトから取得できる統計は以下の4つがあります:

統計 メソッド 説明
度数 getCount() 指定した値をとるデータの個数
累積度数 getCumFreq() 指定した値以下の値を取とるデータ
相対度数 getPct() 全データ数を1としたときの度数の割合
累積相対度数 getCumPct() 全データ数を1としたときの累積度数の割合

サンプル・コード

ではサンプル・コード。 といっても、簡単なサンプルは commons-math3 のドキュメント「1. Statistics」に書いてあるので、ちょっと込み入ったサンプルを。

ここでは、自分が最近したツイートの文字数の分布を取得してグラフにするサンプルを見てみましょう。 コード自体よりも準備のハードルがかなり高いので、サンプルの動作にご興味ない方は下のグラフのあたりまで飛ばしてください。 必要な準備は

  • Java のインストール
  • JavaFX のインストール(環境変数 JAVAFX_HOME の設定も確か必要*1
  • Groovy のインストール
  • twitter4j の OAuth 認証の設定(こちら参照)

です。 使用するライブラリは

  • commons-math3
  • twitter4j
  • groovyfx

ですが、これらは @Grab によって取得するので自前でダウンロードする必要はありません。 これらの設定ができれば、以下のサンプルが動作するはずです(コードは Groovy):

@Grab('org.apache.commons:commons-math3:3.0')
@Grab('org.twitter4j:twitter4j-core:2.2.5')
@Grab('org.codehaus.groovyfx:groovyfx:0.1')

import org.apache.commons.math3.stat.Frequency
import twitter4j.*
import groovyx.javafx.GroovyFX
import groovyx.javafx.SceneGraphBuilder

n = 100
twitter = new TwitterFactory().instance
total = 1000
assert total % n == 0

def freq = new Frequency()    // 1. Frequency オブジェクトを生成

eachStatus{ status ->    // 各ツイートに対して処理
    int size = status.text.size().intdiv(10)    // 一の位を切り捨て
    freq.addValue(size)    // 2. Frequency オブジェクトにデータを追加
}

def data = []
(0..14).each{    // JavaFX のためのデータ作成
    data << [
        createLabel(it), 
        freq.getCount(it)    // 3. Frequency オブジェクトから統計を取得
    ]
}

def title = 'Frequency of tweet text size'
GroovyFX.start {    // GroovyFX でチャート(グラフ)を作成
    new SceneGraphBuilder().stage(title:title, width:500, height:500, visible:true) {
        scene {
            stackPane {
                barChart(title:title, barGap:0, categoryGap:0) {
                    series(name:'Tweet text size', data:data)
                }
            }
        }
    }
}

def String createLabel(int i){
    int i10 = i*10
    return "$i10 - ${i10+9}"
}

def eachStatus(Closure c){
    (total/n).times{ int i ->
        twitter.getUserTimeline(new Paging(i+1, n)).each(c)
    }
}
  • 最近1000件のツイートに対して文字数の統計をとっています。
  • Frequency クラスのサンプルなんですが、Frequency を使ってるのは5行くらい ^^;)
  • 文字数での分類は一の位を切り捨てて行っています(「int size = status.text.size().intdiv(10)」)。 コードとしては雑ですが。
  • このサンプルでは getCount() メソッドによって度数を取得してグラフにしています。 他の統計を取得する場合は getCount() を getPct() などに変更するだけです(ただし、後述のサンプルではラベルも少し変えてます)。

サンプル書いてて思ったんですが、Groovy 1.8 から Collection に導入された count() メソッドを使えば Frequency クラスを使わなくても同じことができてしまいますが、まぁ commons-math3 なら Java ででも使えるしメリットはあるかと。

度数 getCount()

度数は、ある値をとるデータの個数です。 上記のサンプルでは「ツイート文字数が20 〜 29のツイートは50件」とかです(下表も参照)。

30〜50文字くらいのツイートが多いですね。

累積度数 getCumFreq()

累積度数は、指定した値以下の値をとるデータの個数です。 サンプルでは「ツイート文字数が59文字以下のツイートは820件(くらい)」とかです。 49文字とかは、文字数の切り下げを雑にしたせいで、Frequency クラスには関係ありません。 

このサンプルでは1000件のツイートを調べたので、累積度数の一番右の(一番大きい)値は1000になります。

相対度数 getPct()

相対度数は、全データ数を1としたときに、指定指定した値の度数の割合です。 上記のサンプルでは「ツイート文字数が20 〜 29のツイートは全体の0.05(つまり5%)」とかです。

グラフの形は度数のグラフと同じですが、縦軸の目盛りが異なります。

累積相対度数 getCumPct()

相対度数は、全データ数を1としたときに、指定指定した値の累積度数の割合です。 上記のサンプルでは「ツイート文字数が29文字以下のツイートは全体の0.9(つまり90%)」とかです。

このグラフも累積度数のグラフと形の上では同じで、縦軸の目盛りが異なります。 また、累積相対度数の一番右の(一番大きい)値は常に1になります。
プログラミングGROOVY

プログラミングGROOVY

Twitter API ポケットリファレンス (POCKET REFERENCE)

Twitter API ポケットリファレンス (POCKET REFERENCE)

*1:GroovyFX のために。