rust 0.4 → 0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,384 @@
1
+ require_relative '../core'
2
+
3
+ ##
4
+ # Module with utilities for running statistical hypothesis tests.
5
+
6
+ module Rust::StatisticalTests
7
+
8
+ ##
9
+ # Represents the result of a statistical hypothesis test.
10
+
11
+ class Result
12
+ attr_accessor :name
13
+ attr_accessor :statistics
14
+ attr_accessor :pvalue
15
+ attr_accessor :exact
16
+ attr_accessor :alpha
17
+ attr_accessor :hypothesis
18
+
19
+ def initialize
20
+ @statistics = {}
21
+ end
22
+
23
+ def [](name)
24
+ return @statistics[name.to_sym]
25
+ end
26
+
27
+ def []=(name, value)
28
+ @statistics[name.to_sym] = value
29
+ end
30
+
31
+ ##
32
+ # If a hypothesis is available, returns the adjusted p-value with respect to all the other results obtained for
33
+ # the same hypothesis. Otherwise, simply returns the p-value for this result.
34
+ # The +method+ for adjustment can be optionally specified (Bonferroni, by default).
35
+
36
+ def adjusted_pvalue(method='bonferroni')
37
+ return @pvalue unless @hypothesis
38
+ @hypothesis.adjusted_pvalue_for(self, method)
39
+ end
40
+
41
+ ##
42
+ # Sets the underlying hypothesis for the test. The p-values of the results belonging to the same hypothesis can
43
+ # be adjusted through the adjusted_pvalue method.
44
+
45
+ def hypothesis=(value)
46
+ @hypothesis = value
47
+ @hypothesis.add(self)
48
+ end
49
+
50
+ ##
51
+ # Returns true if the results are significant according to the specified alpha.
52
+
53
+ def significant
54
+ pvalue < alpha
55
+ end
56
+
57
+ def to_s
58
+ return "#{name}. P-value = #{pvalue} " +
59
+ "(#{significant ? "significant" : "not significant"} w/ alpha = #{alpha}); " +
60
+ "#{ statistics.map { |k, v| k.to_s + " -> " + v.to_s }.join(", ") }." +
61
+ (!exact ? " P-value is not exact." : "")
62
+ end
63
+ end
64
+
65
+ ##
66
+ # Represents a hypothesis behind one or more results.
67
+
68
+ class Hypothesis
69
+ ##
70
+ # Returns the hypothesis with the given +title_or_instance+ as title (if String).
71
+
72
+ def self.find(title_or_instance)
73
+ return Hypothesis.new(nil) if title_or_instance == nil
74
+
75
+ if title_or_instance.is_a?(String)
76
+ ObjectSpace.each_object(Hypothesis) do |instance|
77
+ return instance if instance.title == title_or_instance
78
+ end
79
+
80
+ return Hypothesis.new(title_or_instance)
81
+ elsif title_or_instance.is_a?(Hypothesis)
82
+ return title_or_instance
83
+ end
84
+
85
+ raise TypeError, "Expected nil, String or Hypothesis"
86
+ end
87
+
88
+ attr_reader :results
89
+ attr_reader :title
90
+
91
+ ##
92
+ # Creates a new hypothesis with a given +title+.
93
+
94
+ def initialize(title)
95
+ @title = title
96
+ @results = []
97
+ end
98
+
99
+ ##
100
+ # Registers a +result+ for this hypothesis.
101
+
102
+ def add(result)
103
+ @results << result
104
+ end
105
+
106
+ ##
107
+ # Returns the adjusted p-value for a specific +result+ with respect to all the other results obtained under this
108
+ # same hypothesis, using the specified +method+.
109
+
110
+ def adjusted_pvalue_for(result, method)
111
+ p_values = @results.map { |r| r.pvalue }
112
+ index = @results.index(result)
113
+
114
+ adjusted_pvalues = Rust::StatisticalTests::PValueAdjustment.method(method).adjust(*p_values)
115
+
116
+ if adjusted_pvalues.is_a?(Numeric)
117
+ return adjusted_pvalues
118
+ else
119
+ return adjusted_pvalues[index]
120
+ end
121
+ end
122
+ end
123
+
124
+ ##
125
+ # Class with utilities for running Wilcoxon Signed-Rank test and Ranked-Sum test (a.k.a. Mann-Whitney U test).
126
+
127
+ class Wilcoxon
128
+
129
+ ##
130
+ # Runs a Wilxoson Signed-Rank test for +d1+ and +d2+, with a given +alpha+ (0.05, by default).
131
+ # +options+ can be specified and directly passed to the R function.
132
+
133
+ def self.paired(d1, d2, alpha = 0.05, **options)
134
+ raise TypeError, "Expecting Array of numerics" if !d1.is_a?(Array) || !d1.all? { |e| e.is_a?(Numeric) }
135
+ raise TypeError, "Expecting Array of numerics" if !d2.is_a?(Array) || !d2.all? { |e| e.is_a?(Numeric) }
136
+ raise "The two distributions have different size" if d1.size != d2.size
137
+
138
+ Rust.exclusive do
139
+ Rust["wilcox.a"] = d1
140
+ Rust["wilcox.b"] = d2
141
+
142
+ _, warnings = Rust._eval("wilcox.result = wilcox.test(wilcox.a, wilcox.b, alternative='two.sided', paired=T)", true)
143
+ result = Rust::StatisticalTests::Result.new
144
+ result.name = "Wilcoxon Signed-Rank test"
145
+ result.pvalue = Rust._pull("wilcox.result$p.value")
146
+ result[:w] = Rust._pull("wilcox.result$statistic")
147
+ result.exact = !warnings.include?("cannot compute exact p-value with zeroes")
148
+ result.alpha = alpha
149
+ result.hypothesis = Rust::StatisticalTests::Hypothesis.find(options[:hypothesis])
150
+
151
+ return result
152
+ end
153
+ end
154
+
155
+ ##
156
+ # Runs a Wilxoson Ranked-Sum (a.k.a. Mann-Whitney U) test for +d1+ and +d2+, with a given +alpha+ (0.05, by default).
157
+ # +options+ can be specified and directly passed to the R function.
158
+
159
+ def self.unpaired(d1, d2, alpha = 0.05, **options)
160
+ raise TypeError, "Expecting Array of numerics" if !d1.is_a?(Array) || !d1.all? { |e| e.is_a?(Numeric) }
161
+ raise TypeError, "Expecting Array of numerics" if !d2.is_a?(Array) || !d2.all? { |e| e.is_a?(Numeric) }
162
+
163
+ Rust.exclusive do
164
+ Rust["wilcox.a"] = d1
165
+ Rust["wilcox.b"] = d2
166
+
167
+ _, warnings = Rust._eval("wilcox.result = wilcox.test(wilcox.a, wilcox.b, alternative='two.sided', paired=F)", true)
168
+ result = Rust::StatisticalTests::Result.new
169
+ result.name = "Wilcoxon Ranked-Sum test (a.k.a. Mann–Whitney U test)"
170
+ result.pvalue = Rust._pull("wilcox.result$p.value")
171
+ result[:w] = Rust._pull("wilcox.result$statistic")
172
+ result.exact = !warnings.include?("cannot compute exact p-value with ties")
173
+ result.alpha = alpha
174
+ result.hypothesis = Rust::StatisticalTests::Hypothesis.find(options[:hypothesis])
175
+
176
+ return result
177
+ end
178
+ end
179
+ end
180
+
181
+ ##
182
+ # Class with utilities for running the T test.
183
+
184
+ class T
185
+
186
+ ##
187
+ # Runs a paired T test for +d1+ and +d2+, with a given +alpha+ (0.05, by default).
188
+ # +options+ can be specified and directly passed to the R function.
189
+
190
+ def self.paired(d1, d2, alpha = 0.05, **options)
191
+ raise TypeError, "Expecting Array of numerics" if !d1.is_a?(Array) || !d1.all? { |e| e.is_a?(Numeric) }
192
+ raise TypeError, "Expecting Array of numerics" if !d2.is_a?(Array) || !d2.all? { |e| e.is_a?(Numeric) }
193
+ raise "The two distributions have different size" if d1.size != d2.size
194
+
195
+ Rust.exclusive do
196
+ Rust["t.a"] = d1
197
+ Rust["t.b"] = d2
198
+
199
+ warnings = Rust._eval("t.result = t.test(t.a, t.b, alternative='two.sided', paired=T)")
200
+ result = Rust::StatisticalTests::Result.new
201
+ result.name = "Paired t-test"
202
+ result.pvalue = Rust._pull("t.result$p.value")
203
+ result[:t] = Rust._pull("t.result$statistic")
204
+ result.exact = true
205
+ result.alpha = alpha
206
+ result.hypothesis = Rust::StatisticalTests::Hypothesis.find(options[:hypothesis])
207
+
208
+ return result
209
+ end
210
+ end
211
+
212
+ ##
213
+ # Runs an unpaired T test for +d1+ and +d2+, with a given +alpha+ (0.05, by default).
214
+ # +options+ can be specified and directly passed to the R function.
215
+
216
+ def self.unpaired(d1, d2, alpha = 0.05, **options)
217
+ raise TypeError, "Expecting Array of numerics" if !d1.is_a?(Array) || !d1.all? { |e| e.is_a?(Numeric) }
218
+ raise TypeError, "Expecting Array of numerics" if !d2.is_a?(Array) || !d2.all? { |e| e.is_a?(Numeric) }
219
+
220
+ Rust.exclusive do
221
+ Rust["t.a"] = d1
222
+ Rust["t.b"] = d2
223
+
224
+ Rust._eval("t.result = t.test(t.a, t.b, alternative='two.sided', paired=F)")
225
+ result = Rust::StatisticalTests::Result.new
226
+ result.name = "Welch Two Sample t-test"
227
+ result.pvalue = Rust._pull("t.result$p.value")
228
+ result[:t] = Rust._pull("t.result$statistic")
229
+ result.exact = true
230
+ result.alpha = alpha
231
+ result.hypothesis = Rust::StatisticalTests::Hypothesis.find(options[:hypothesis])
232
+
233
+ return result
234
+ end
235
+ end
236
+ end
237
+
238
+ ##
239
+ # Utilities for the Shapiro normality test.
240
+
241
+ class Shapiro
242
+
243
+ ##
244
+ # Runs the Shapiro normality test for +vector+ and a given +alpha+ (0.05, by default).
245
+ # +options+ can be specified and directly passed to the R function.
246
+
247
+ def self.compute(vector, alpha = 0.05, **options)
248
+ raise TypeError, "Expecting Array of numerics" if !vector.is_a?(Array) || !vector.all? { |e| e.is_a?(Numeric) }
249
+ Rust.exclusive do
250
+ Rust['shapiro.v'] = vector
251
+
252
+ Rust._eval("shapiro.result = shapiro.test(shapiro.v)")
253
+ result = Rust::StatisticalTests::Result.new
254
+ result.name = "Shapiro-Wilk normality test"
255
+ result.pvalue = Rust._pull("shapiro.result$p.value")
256
+ result[:W] = Rust._pull("shapiro.result$statistic")
257
+ result.exact = true
258
+ result.alpha = alpha
259
+ result.hypothesis = Rust::StatisticalTests::Hypothesis.find(options[:hypothesis])
260
+
261
+ return result
262
+ end
263
+ end
264
+ end
265
+
266
+ ##
267
+ # Module with utilities for adjusting the p-values.
268
+
269
+ module PValueAdjustment
270
+
271
+ ##
272
+ # Returns the Ruby class given the R name of the p-value adjustment method.
273
+
274
+ def self.method(name)
275
+ name = name.to_s
276
+ case name.downcase
277
+ when "bonferroni", "b"
278
+ return Bonferroni
279
+ when "holm", "h"
280
+ return Holm
281
+ when "hochberg"
282
+ return Hochberg
283
+ when "hommel"
284
+ return Hommel
285
+ when "benjaminihochberg", "bh"
286
+ return BenjaminiHochberg
287
+ when "benjaminiyekutieli", "by"
288
+ return BenjaminiYekutieli
289
+ end
290
+ end
291
+
292
+ ##
293
+ # Bonferroni p-value adjustment method.
294
+
295
+ class Bonferroni
296
+ def self.adjust(*p_values)
297
+ Rust.exclusive do
298
+ Rust['adjustment.p'] = p_values
299
+ return Rust._pull("p.adjust(adjustment.p, method=\"bonferroni\")")
300
+ end
301
+ end
302
+ end
303
+
304
+ ##
305
+ # Holm p-value adjustment method.
306
+
307
+ class Holm
308
+ def self.adjust(*p_values)
309
+ Rust.exclusive do
310
+ Rust['adjustment.p'] = p_values
311
+ return Rust._pull("p.adjust(adjustment.p, method=\"holm\")")
312
+ end
313
+ end
314
+ end
315
+
316
+ ##
317
+ # Hochberg p-value adjustment method.
318
+
319
+ class Hochberg
320
+ def self.adjust(*p_values)
321
+ Rust.exclusive do
322
+ Rust['adjustment.p'] = p_values
323
+ return Rust._pull("p.adjust(adjustment.p, method=\"hochberg\")")
324
+ end
325
+ end
326
+ end
327
+
328
+ ##
329
+ # Hommel p-value adjustment method.
330
+
331
+ class Hommel
332
+ def self.adjust(*p_values)
333
+ Rust.exclusive do
334
+ Rust['adjustment.p'] = p_values
335
+ return Rust._pull("p.adjust(adjustment.p, method=\"hommel\")")
336
+ end
337
+ end
338
+ end
339
+
340
+ ##
341
+ # Benjamini-Hochberg p-value adjustment method.
342
+
343
+ class BenjaminiHochberg
344
+ def self.adjust(*p_values)
345
+ Rust.exclusive do
346
+ Rust['adjustment.p'] = p_values
347
+ return Rust._pull("p.adjust(adjustment.p, method=\"BH\")")
348
+ end
349
+ end
350
+ end
351
+
352
+ ##
353
+ # Benjamini-Yekutieli p-value adjustment method.
354
+
355
+ class BenjaminiYekutieli
356
+ def self.adjust(*p_values)
357
+ Rust.exclusive do
358
+ Rust['adjustment.p'] = p_values
359
+ return Rust._pull("p.adjust(adjustment.p, method=\"BY\")")
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ module Rust::RBindings
367
+ def wilcox_test(d1, d2, **args)
368
+ paired = args[:paired] || false
369
+ if paired
370
+ return Rust::StatisticalTests::Wilcoxon.paired(d1, d2)
371
+ else
372
+ return Rust::StatisticalTests::Wilcoxon.unpaired(d1, d2)
373
+ end
374
+ end
375
+
376
+ def t_test(d1, d2, **args)
377
+ paired = args[:paired] || false
378
+ if paired
379
+ return Rust::StatisticalTests::T.paired(d1, d2)
380
+ else
381
+ return Rust::StatisticalTests::T.unpaired(d1, d2)
382
+ end
383
+ end
384
+ end
data/lib/rust.rb CHANGED
@@ -1,8 +1,4 @@
1
- require_relative 'rust-core'
2
- require_relative 'rust-basics'
3
- require_relative 'rust-csv'
4
- require_relative 'rust-tests'
5
- require_relative 'rust-effsize'
6
- require_relative 'rust-descriptive'
7
- require_relative 'rust-plots'
8
- require_relative 'rust-calls'
1
+ require_relative 'rust/core'
2
+ require_relative 'rust/models/all'
3
+ require_relative 'rust/plots/all'
4
+ require_relative 'rust/stats/all'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rust
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.10'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simone Scalabrino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-15 00:00:00.000000000 Z
11
+ date: 2022-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rinruby
@@ -52,19 +52,38 @@ dependencies:
52
52
  version: 1.1.2
53
53
  description: Ruby advanced statistical library based on RinRuby
54
54
  email: s.scalabrino9@gmail.com
55
- executables: []
55
+ executables:
56
+ - ruby-rust
56
57
  extensions: []
57
58
  extra_rdoc_files: []
58
59
  files:
59
- - lib/rust-basics.rb
60
- - lib/rust-calls.rb
61
- - lib/rust-core.rb
62
- - lib/rust-csv.rb
63
- - lib/rust-descriptive.rb
64
- - lib/rust-effsize.rb
65
- - lib/rust-plots.rb
66
- - lib/rust-tests.rb
60
+ - bin/ruby-rust
67
61
  - lib/rust.rb
62
+ - lib/rust/core.rb
63
+ - lib/rust/core/csv.rb
64
+ - lib/rust/core/rust.rb
65
+ - lib/rust/core/types/all.rb
66
+ - lib/rust/core/types/dataframe.rb
67
+ - lib/rust/core/types/datatype.rb
68
+ - lib/rust/core/types/factor.rb
69
+ - lib/rust/core/types/language.rb
70
+ - lib/rust/core/types/list.rb
71
+ - lib/rust/core/types/matrix.rb
72
+ - lib/rust/core/types/s4class.rb
73
+ - lib/rust/core/types/utils.rb
74
+ - lib/rust/models/all.rb
75
+ - lib/rust/models/anova.rb
76
+ - lib/rust/models/regression.rb
77
+ - lib/rust/plots/all.rb
78
+ - lib/rust/plots/basic-plots.rb
79
+ - lib/rust/plots/core.rb
80
+ - lib/rust/plots/distribution-plots.rb
81
+ - lib/rust/stats/all.rb
82
+ - lib/rust/stats/correlation.rb
83
+ - lib/rust/stats/descriptive.rb
84
+ - lib/rust/stats/effsize.rb
85
+ - lib/rust/stats/probabilities.rb
86
+ - lib/rust/stats/tests.rb
68
87
  homepage: https://github.com/intersimone999/ruby-rust
69
88
  licenses:
70
89
  - GPL-3.0-only
@@ -84,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
103
  - !ruby/object:Gem::Version
85
104
  version: '0'
86
105
  requirements: []
87
- rubygems_version: 3.1.4
106
+ rubygems_version: 3.3.15
88
107
  signing_key:
89
108
  specification_version: 4
90
109
  summary: Ruby advanced statistical library
data/lib/rust-calls.rb DELETED
@@ -1,69 +0,0 @@
1
- require_relative 'rust-core'
2
-
3
- module Rust
4
- class Function
5
- attr_reader :name
6
- attr_reader :arguments
7
- attr_reader :options
8
-
9
- def initialize(name)
10
- @function = name
11
- @arguments = Arguments.new
12
- @options = Options.new
13
- end
14
-
15
- def options=(options)
16
- raise TypeError, "Expected Options" unless options.is_a?(Options)
17
-
18
- @options = options
19
- end
20
-
21
- def arguments=(arguments)
22
- raise TypeError, "Expected Arguments" unless options.is_a?(Arguments)
23
-
24
- @arguments = arguments
25
- end
26
-
27
- def to_R
28
- params = [@arguments.to_R, @options.to_R].select { |v| v != "" }.join(",")
29
- return "#@function(#{params})"
30
- end
31
-
32
- def call
33
- Rust._eval(self.to_R)
34
- end
35
- end
36
-
37
- class Variable
38
- def initialize(name)
39
- @name = name
40
- end
41
-
42
- def to_R
43
- @name
44
- end
45
- end
46
-
47
- class Arguments < Array
48
- def to_R
49
- return self.map { |v| v.to_R }.join(", ")
50
- end
51
- end
52
-
53
- class Options < Hash
54
- def to_R
55
- return self.map { |k, v| "#{k}=#{v.to_R}" }.join(", ")
56
- end
57
-
58
- def self.from_hash(hash)
59
- options = Options.new
60
- hash.each do |key, value|
61
- options[key.to_s] = value
62
- end
63
- return options
64
- end
65
- end
66
- end
67
-
68
- module Rust::RBindings
69
- end
@@ -1,67 +0,0 @@
1
- require 'code-assertions'
2
-
3
- require_relative 'rust-core'
4
-
5
- module Rust::Descriptive
6
- class << self
7
- def mean(data)
8
- raise TypeError, "Expecting Array of numerics" if !data.is_a?(Array) || !data.all? { |e| e.is_a?(Numeric) }
9
-
10
- return data.sum.to_f / data.size
11
- end
12
-
13
- def standard_deviation(data)
14
- raise TypeError, "Expecting Array of numerics" if !data.is_a?(Array) || !data.all? { |e| e.is_a?(Numeric) }
15
-
16
- return Math.sqrt(variance(data))
17
- end
18
- alias :sd :standard_deviation
19
- alias :stddev :standard_deviation
20
-
21
- def variance(data)
22
- raise TypeError, "Expecting Array of numerics" if !data.is_a?(Array) || !data.all? { |e| e.is_a?(Numeric) }
23
- return Float::NAN if data.size < 2
24
-
25
- mean = mean(data)
26
- return data.map { |v| (v - mean) ** 2 }.sum.to_f / (data.size - 1)
27
- end
28
- alias :var :variance
29
-
30
- def median(data)
31
- raise TypeError, "Expecting Array of numerics" if !data.is_a?(Array) || !data.all? { |e| e.is_a?(Numeric) }
32
-
33
- sorted = data.sort
34
- if data.size == 0
35
- return Float::NAN
36
- elsif data.size.odd?
37
- return sorted[data.size / 2]
38
- else
39
- i = (data.size / 2)
40
- return (sorted[i - 1] + sorted[i]) / 2.0
41
- end
42
- end
43
-
44
- def sum(data)
45
- raise TypeError, "Expecting Array of numerics" if !data.is_a?(Array) || !data.all? { |e| e.is_a?(Numeric) }
46
-
47
- return data.sum
48
- end
49
-
50
- def quantile(data, percentiles=[0.0, 0.25, 0.5, 0.75, 1.0])
51
- raise TypeError, "Expecting Array of numerics" if !data.is_a?(Array) || !data.all? { |e| e.is_a?(Numeric) }
52
- raise TypeError, "Expecting Array of numerics" if !percentiles.is_a?(Array) || !percentiles.all? { |e| e.is_a?(Numeric) }
53
- raise "Percentiles outside the range: #{percentiles}" if percentiles.any? { |e| !e.between?(0, 1) }
54
-
55
- Rust.exclusive do
56
- Rust['descriptive.data'] = data
57
- Rust['descriptive.percs'] = percentiles
58
-
59
- call_result = Rust._pull("quantile(descriptive.data, descriptive.percs)")
60
- assert { call_result.is_a?(Array) }
61
- assert { call_result.size == percentiles.size }
62
-
63
- return percentiles.zip(call_result).to_h
64
- end
65
- end
66
- end
67
- end