■ ちょっと統計的なクラス ss

日頃、ちょっとした結果とかデータを集めていたりしませんか? それが、条件を変更したときなどにどんな傾向を示すか興味がありませんか? こういうときにちょっとだけ手助けになるかもしれないクラスです。 私は仕事でベンチマークとかをしていたりするので、レスポンスタイムのようなものの傾向をみるのに使ったりします。 「平均」だけだと、大きくハズレた値に引っ張られることもあります。 こういうとき、「件数」「合計値」「最大値」「最小値」「平均値」「標準偏差」「中央値」などを手軽に確認できると便利だと思いませんか? もちろん、ちゃんとレポートを書くときには、それなりのツールをあとから使えばいいと思うのです。 でも、取り急ぎ次の手を打つためにデータの傾向をしりたいときに役立つツールがあれば! ここで紹介するものは、そういう手軽なツールです。 出力データに合わせてちょっとした修正で使えるはずのものです。

結果を出力する「プログラム」って、後処理することを考えていないものが多いですよね。 そういうとき、スクリプト言語の柔軟性が役立ちます。 また、簡単に使えるクラスを用意していますので、処理したい項目事にオブジェクトを用意すれば項目がたくさんあっても扱いは簡単です。 ああ、Ruby のおかげ。

■ 使い方

クラスの使い方ということで...

●メソッド

initialize

オブジェクトの初期化を行います。

additem(arg)

引数 arg をデータとして追加していきます。 確認したいデータを、このメソッドでどんどん追加していきます。

count

項目数を返します。

max

最大値を返します。

min

最小値を返します。

sum

合計値を返します。

ave

平均値を返します。

s

標準偏差を返します。

m

中央値を返します。

info

統計的情報を print します。

fmt

表示フォーマットを返します。 これは、任意に設定できた方がいいかもしれませんね。

簡単なサンプルとして、乱数を使用してその結果を確認してみます。

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/junk/ss1.rb
# Created: August 08,1999 Sunday 01:23:23
# Author: tetsu(WATANABE Tetsuya)
# $Id$
# usage:

require 'ss'

ss = Ss.new

1.upto(10000) do
  ss.additem(rand(0))
end

ss.info

exit

$ ruby ss1.rb
count: 10000
sum:   4988.30
max:      1.00
min:      0.00
ave:      0.50
s1 ave:   0.50
median:   0.50
s:        0.29
s2 s:     0.29
これは、10000 回ループして、出力される乱数で確認したものです。

次は私のログイン時間を確認するものです。 あんまり意味がありませんが...

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/junk/ss2.rb
# Created: August 08,1999 Sunday 01:36:07
# Author: tetsu(WATANABE Tetsuya)
# $Id$
# usage:

def timestr2sec(arg)
  sec = 0
  w = arg.split('+')
  w.each do |v|
    if v =~ /:/
      h_str, m_str = v.split(':')
      sec += h_str.to_i * 3600
      sec += m_str.to_i * 60
    else
      sec += v.to_i * 24 * 3600
    end
  end
  sec.to_f / 3600.0
end

require 'ss'

ss = Ss.new

last_cmd = File.popen('last')

while last_cmd.gets
  if /\((.*?)\)/
    time_str = $1
    next if time_str == '00:00'
    ss.additem(timestr2sec(time_str))
  end
end

last_cmd.close

ss.info

exit

$ ruby ss2.rb
count: 143
sum:   840.85
max:    63.83
min:     0.02
ave:     5.88
s1 ave:  5.88
median:  0.48
s:      12.75
s2 s:   12.75
コマンド last の出力から、時間の部分を切りだして、それを「時間」にしています。 最初「秒」でやったんですが、感じがつかめないので時間にしました。

■ ソースコード

Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/class/ss.rb
# Created: August 05,1999 Thursday 10:19:48
# Author: tetsu(WATANABE Tetsuya)
# $Id: ss.rb,v 1.3 1999/10/18 06:42:38 tetsu Exp tetsu $
# usage:

# 参考文献
# 奥村晴彦著
# C 言語による最新アルゴリズム辞典
# 技術評論社 ISBN4-87408-414-1

class Ss
  def initialize
    @max = nil
    @min = nil
    @sum = 0.0
    @count = 0
    @data = []
    @s1 = 0.0
    @s2 = 0.0
  end
  attr_reader :count, :max, :min, :sum  

  def additem(arg)
    f = arg.to_f

    @sum += f
    if @max == nil
      @max = f
    elsif f > @max
      @max = f
    end
    if @min == nil
      @min = f
    elsif f < @min
      @min = f
    end

    @count += 1
    @data.push(f)

    f -= @s1
    @s1 += f / @count.to_f
    @s2 += (@count - 1).to_f * f * f / @count.to_f

  end

  def ave
    @sum / @count
  end

  def ave90
    n = (@count - (@count * 0.9)).to_i
    sum = @sum
    data = @data.sort
    while n > 0
      sum -= data[-n]
      n -= 1
      break if n == 0
      sum -= data[n - 1]
      n -= 1
    end
    sum / (@count - n)
  end

  def s
    ave = self.ave
    s = 0.0
    @data.each do |v|
      x = v - ave
      s += x * x
    end
    Math.sqrt(s / @count)
  end
  
  def m
    @data.sort[@count / 2]
  end

  def fmt
    len = self.sum.to_s.sub(/\.\d+$/, '').length + 3
    sprintf("%%%d.3f", len)
  end

  def info
    ave = self.ave

    fmt = self.fmt + "\n"

    print 'count: ', self.count, "\n"

    print 'sum:   '
    printf fmt, self.sum

    print 'max:   '
    printf fmt, self.max

    print 'min:   '
    printf fmt, self.min

    print 'ave:   '
    printf fmt, ave

#     print 's1 ave:'
#     printf fmt, @s1

    print 'median:'
    printf fmt, self.m

    print 's:     '
    printf fmt, self.s

#    print 's2 s:  '
#    printf fmt, Math.sqrt(@s2 / (@count - 1).to_f)
  end
end

if $0 == __FILE__

  ss = Ss.new

  while gets
    if ~/([\d.]+)/
      ss.additem($1)
    end
  end
  ss.info

  exit

end

■ 解説

アルゴリズムについては、参考文献によるものです。

奥村晴彦著
C 言語による最新アルゴリズム辞典
技術評論社
ISBN4-87408-414-1

現状では、データの入力項目を一つだけ扱っています。 ですが、複数あった場合は、必要な数だけオブジェクトを作成すれば OK です。 入力項目を自動判断して、必要な数の入力項目を作成し結果を表示するようなことも可能です。

現状では、結果の出力部分は「とりあえず」という形になっています。 必要に合わせて修正してください。


渡辺哲也(WATANABE Tetsuya): Tetsuya.WATANABE atmark nifty.com