module Statsample module Test # From Wikipedia: # The Wilcoxon signed-rank test is a non-parametric statistical hypothesis test used when comparing two related samples, matched samples, or repeated measurements on a single sample to assess whether their population mean ranks differ (i.e. it is a paired difference test). It can be used as an alternative to the paired Student's t-test, t-test for matched pairs, or the t-test for dependent samples when the population cannot be assumed to be normally distributed. class WilcoxonSignedRank include Statsample::Test include Summarizable # Name of F analysis attr_accessor :name attr_reader :w attr_reader :nr attr_writer :tails # Parameters: def initialize(v1,v2, opts=Hash.new) @v1=v1 @v2=v2 opts_default={:name=>_("Wilcoxon Signed Rank Test"),:tails=>:both} @opts=opts_default.merge(opts) opts_default.keys.each {|k| send("#{k}=", @opts[k]) } calculate end def calculate df=Statsample::Dataset.new({'v1'=>@v1,'v2'=>@v2}) df["abs"]=df.collect {|row| r=(row["v2"]-row["v1"]).abs } df["sgn"]=df.collect {|row| r=row["v2"]-row["v1"] r==0 ? 0 : r/r.abs } df=df.filter {|row| row["sgn"]!=0} df["rank"]=df["abs"].ranked @nr=df.cases @w=df.collect {|row| row["sgn"]*row["rank"] #p row["sgn"]*row["rank"] }.sum end def report_building(generator) # :nodoc: generator.section(:name=>@name) do |s| s.table(:name=>_("%s results") % @name) do |t| t.row([_("W Value"), "%0.3f" % @w]) t.row([_("Z"), "%0.3f (p: %0.3f)" % [z, probability_z]]) if(nr<=10) t.row([_("Exact probability"), "p-exact: %0.3f" % [probability_exact]]) end end end end def z sigma=Math.sqrt((nr*(nr+1)*(2*nr+1))/6) (w-0.5)/sigma end # Assuming normal distribution of W, this calculate # the probability of samples with Z equal or higher than # obtained on sample def probability_z (1-Distribution::Normal.cdf(z))*(@tails==:both ? 2:1) end # Calculate exact probability. # Don't calculate for large Nr, please! def probability_exact str_format="%0#{nr}b" combinations=2**nr #p str_format total_w=combinations.times.map {|i| comb=sprintf(str_format,i) w_local=comb.length.times.inject(0) {|ac,j| sgn=comb[j]=="0" ? -1 : 1 ac+(j+1)*sgn } }.sort total_w.find_all {|v| if @tails==:both v<=-w.abs or v>=w.abs elsif @tails==:left v<=w elsif @tails==:right v>=w end }.count/(combinations.to_f) end end end end