RubyでSpearman's R (順位相関係数)を計算しよう

Rubyの配列(Arrayクラス)を拡張して和,分散,標準偏差,相関係数を計算する - Loud MinorityRuby で Mann-Whitney U test をする - Loud Minority の続き.
Spearman(スピアマン)の順位相関係数を計算しよう.
http://d.hatena.ne.jp/sesejun/20070502/p1 を改良して,順位相関係数を計算できるようにしてみました.

使い方

x = [2.8,3.4,3.6,5.8,7.0,9.5,10.2,12.3,13.2,13.4]
y = [0.6,3.0,0.4,1.5,15.0,13.4,7.6,19.8,18.3,18.9]
puts x.corrcoef(y, :spearman)
#=> 0.854545454545454

  • (x[0], y[0]),(x[1],y[1]),... の点に関して,順位相関係数の計算をします.
    • サンプル数が少ない場合も,多い場合も同一の計算をしています(ので,5サンプルとかの場合は,多少誤差が出るかもしれません)
    • 計算では,与えられた値を順位に変換して,ピアソンの相関係数を計算する手法を使っています.
  • :spearman の所を,:pearson にする(x.corrcoef(y, :pearson))か,省略する(x.corrcoef(y))とピアソンの相関係数を計算します.
  • Arrayクラスの拡張になっています.

ソース

class Array
  def sum_with_number
    s = 0.0
    n = 0
    self.each do |v|
      next if v.nil?
      s += v.to_f
      n += 1
    end
    [s, n]
  end
  
  def sum
    s, n = self.sum_with_number
    s
  end
  
  def avg
    s, n = self.sum_with_number
    s / n
  end
  alias mean avg
  
  def var
    c = 0
    while self[c].nil?
      c += 1
    end
    mean = self[c].to_f
    sum = 0.0
    n = 1
    (c+1).upto(self.size-1) do |i|
      next if self[i].nil?
      sweep = n.to_f / (n + 1.0)
      delta = self[i].to_f - mean
      sum += delta * delta * sweep
      mean += delta / (n + 1.0)
      n += 1
    end
    sum / n.to_f
  end
  
  def stddev
    Math.sqrt(self.var)
  end

  def corrcoef(y, method = :pearson)
    if method == :pearson
      corrcoef_pearson(y)
    elsif method == :spearman
      corrcoef_spearman(y)
    end
  end

  def corrcoef_pearson(y)
    raise "Invalid Argument Array Size" unless self.size == y.size
    sum_sq_x = 0.0
    sum_sq_y = 0.0
    sum_coproduct = 0.0
    c = 0
    while self[c].nil? || y[c].nil?
      c += 1
    end
    mean_x = self[c].to_f
    mean_y = y[c].to_f
    n = 1
    (c+1).upto(self.size-1) do |i|
      next if self[i].nil? || y[i].nil?
      sweep = n.to_f / (n + 1.0)
      delta_x = self[i].to_f - mean_x
      delta_y = y[i].to_f - mean_y
      sum_sq_x += delta_x * delta_x * sweep
      sum_sq_y += delta_y * delta_y * sweep
      mean_x += delta_x / (n + 1.0)
      mean_y += delta_y / (n + 1.0)
      n += 1
     end
    pop_sd_x = Math.sqrt(sum_sq_x / n.to_f)
    pop_sd_y = Math.sqrt(sum_sq_y / n.to_f)
    cov_x_y = sum_coproduct / n.to_f
    cov_x_y / (pop_sd_x * pop_sd_y)
  end

  def corrcoef_spearman(y)
    xo = Array.new
    yo = Array.new
    self.size.times do |n|
      xo << [n, self[n]]
      yo << [n, y[n]]
    end
    xo = xo.sort{|a, b| a[1]<=>b[1]}
    yo = yo.sort{|a, b| a[1]<=>b[1]}
    self.size.times do |n|
      xo[n][1] = n
      yo[n][1] = n
    end
    xo = xo.sort{|a, b| a[0]<=>b[0]}
    yo = yo.sort{|a, b| a[0]<=>b[0]}
    xary = xo.map{|v| v[1]}
    yary = yo.map{|v| v[1]}
    xary.corrcoef_pearson(yary)
  end
end

おまけ

  • 場当たり的に作ったので,あまり賢くない計算方法な気がする
  • うーん,「順位相関係数」は,「位相」でキーワードリンクが張られるのか・・・