概要
ポートフォリオで体重管理アプリを作成しているのですが、過去の数値から将来の体重を予測する機能を実装中です。
最小二乗法で回帰直線の傾きと切片を導くロジックをRubyで書きました。
詳細
y = 2x となるような配列を用意しました。(実際はモデルの変数を使う予定です)
時間が空いたときに解説を書きます...(すみません)
Arrayクラスを拡張
ついでに不偏分散と標準偏差も考えました。
module ArrayStatistics
refine Array do
def average # 平均
sum.fdiv(size)
end
def variance # 分散
@average = average
inject(0) { |result,n| result + (n - @average) ** 2 }.fdiv(size)
end
def unbiasedvariance # 不偏分散
@average = average
inject(0) { |result,n| result + (n - @average) ** 2 }.fdiv(size - 1)
end
def stadiv # 標準偏差
Math.sqrt(variance)
end
def convariance # 共分散
array1 = map{|xs| xs[0]}
array2 = map{|ys| ys[1]}
@average1 = array1.average
@average2 = array2.average
inject(0) { |result,n| result + (n[0] - @average1) * (n[1] - @average2)}.fdiv(size)
end
end
end
@ average = averageにすることでメモ化され次の処理速度があがります
除算は***'/'ではなく、'fdiv'***を使用してます。
実行するクラス
class Report < ApplicationRecord
using ArrayStatistics
def ols
xs = [1,2,3,4,5]
ys = [2,4,6,8,10]
arry = xs.zip(ys)
a = array.convariance.fdiv(xs.variance) # 回帰直線の傾き(a = XとYの共分散 / Xの分散)
b = ys.average - a * xs.average # 回帰直線の切片(b = Yの平均 - 回帰直線の傾き * Xの平均)
{a: a,b: b}
end
end
実行結果
a = 2
b = 0
が表示されました。
ちなみに...
Hashクラスでもできますが、重回帰分析とかには、変更しにくそうなので、
Arrayクラスがおすすめかなと思います。
Hashを拡張
module HashStatistics
refine Hash do
class Hash
def convariance #共分散
ary1 = keys
ary2 = values
each_with_index.inject(0) { |result,(n,index)| result + (ary1[index] - ary1.average) * (ary2[index] - ary2.average)} / size
end
end
end
end
実行するクラス(Hash対応版)
class Report < ApplicationRecord
using ArrayStatistics
using HashStatistics
def ols
xs = [1,2,3,4,5]
ys = [2,4,6,8,10]
ary = [xs,ys].transpose
hsh = Hash[*ary.flatten]
a = hsh.convariance / xs.variance #回帰直線の傾き(a = XとYの共分散 / Xの分散)
b = ys.average - a * xs.average #回帰直線の切片(b = Yの平均 - 回帰直線の傾き * Xの平均)
{a: a,b: b}
end
end
参考資料
Basic Knowledge on Data Analysis 単回帰分析とは
あとがき
###追記(2019/10/28)###
scivola様より非常にありがたいアドバイスをコメントにいただき、書き直しました。