rust 0.9 → 0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,435 @@
1
+ require 'json'
2
+ require_relative 'core'
3
+
4
+ module Rust::Plots::GGPlot
5
+ class Theme < Layer
6
+ def self.from_h(options)
7
+ starting = options.delete('_starting')
8
+ options = options.map do |key, value|
9
+ if value.is_a?(Hash)
10
+ case value.delete('_type').split("::").last
11
+ when 'TextElement'
12
+ [key, TextElement.new(**value)]
13
+ when 'RectElement'
14
+ [key, RectElement.new(**value)]
15
+ when 'TextElement'
16
+ [key, TextElement.new(**value)]
17
+ when 'LineElement'
18
+ [key, LineElement.new(**value)]
19
+ end
20
+ else
21
+ [key, value]
22
+ end
23
+ end.to_h
24
+
25
+ return Theme.new(starting, **options)
26
+ end
27
+
28
+ def self.load(filename)
29
+ json = JSON.parse(File.read(filename))
30
+ return self.from_h(json.to_h)
31
+ end
32
+
33
+ def initialize(starting, **options)
34
+ super("theme", **options)
35
+ if starting
36
+ @starting = "theme_" + starting
37
+ end
38
+ end
39
+
40
+ def to_R
41
+ result = super do |options, arguments|
42
+ [
43
+ options.map { |k, v| [k.to_s.gsub("_", "."), v] }.to_h,
44
+ arguments
45
+ ]
46
+ end
47
+
48
+ result = Rust::Function.new(@starting).to_R + " + " + result if @starting
49
+
50
+ return result
51
+ end
52
+
53
+ def to_h
54
+ options = @options.clone
55
+
56
+ options['_starting'] = @starting.sub("theme_", "")
57
+ options = options.map do |key, value|
58
+ [key, value.is_a?(Theme::Element) ? value.to_h : value]
59
+ end.to_h
60
+
61
+ return options
62
+ end
63
+
64
+ def save(path, force = false)
65
+ if !force && FileTest.exist?(path)
66
+ raise "File already existing."
67
+ end
68
+
69
+ File.open(path, "w") do |f|
70
+ f.write(
71
+ JSON.pretty_generate(
72
+ self.to_h
73
+ )
74
+ )
75
+ end
76
+
77
+ return true
78
+ end
79
+ end
80
+
81
+ class Theme::Element
82
+ attr_reader :options
83
+
84
+ def initialize(**options)
85
+ @options = options
86
+ end
87
+
88
+ def r_function
89
+ raise "Not implemented for generic theme element"
90
+ end
91
+
92
+ def to_R
93
+ options = @options.map { |k, v| [k.to_s.gsub("_", "."), v] }.to_h
94
+
95
+ function = Rust::Function.new(self.r_function)
96
+ function.options = Rust::Options.from_hash(options)
97
+
98
+ return function.to_R
99
+ end
100
+
101
+ def to_h
102
+ hash = @options.clone
103
+ hash['_type'] = self.class.name
104
+ return hash
105
+ end
106
+ end
107
+
108
+ class Theme::TextElement < Theme::Element
109
+ def r_function
110
+ return "element_text"
111
+ end
112
+ end
113
+
114
+ class Theme::LineElement < Theme::Element
115
+ def r_function
116
+ return "element_line"
117
+ end
118
+ end
119
+
120
+ class Theme::RectElement < Theme::Element
121
+ def r_function
122
+ return "element_rect"
123
+ end
124
+ end
125
+
126
+ class Theme::BlankElement < Theme::Element
127
+ def r_function
128
+ return "element_blank"
129
+ end
130
+ end
131
+
132
+ class ThemeComponentBuilder
133
+ def initialize(namespace=nil)
134
+ @namespace = namespace
135
+ @options = {}
136
+ end
137
+
138
+ def option(key, value)
139
+ key = "#@namespace.#{key}" if @namespace
140
+ @options[key] = value
141
+
142
+ return self
143
+ end
144
+
145
+ def [](key)
146
+ key = "#@namespace.#{key}" if @namespace
147
+ return @options[key]
148
+ end
149
+
150
+ def line_el(value)
151
+ if value.is_a?(Theme::LineElement) || value.is_a?(Theme::BlankElement)
152
+ return value
153
+ elsif value.is_a?(Hash)
154
+ return Theme::LineElement.new(**value)
155
+ else
156
+ raise "Expected line or hash"
157
+ end
158
+ end
159
+
160
+ def rect_el(value)
161
+ if value.is_a?(Theme::RectElement) || value.is_a?(Theme::BlankElement)
162
+ return value
163
+ elsif value.is_a?(Hash)
164
+ return Theme::RectElement.new(**value)
165
+ else
166
+ raise "Expected rect or hash"
167
+ end
168
+ end
169
+
170
+ def text_el(value)
171
+ if value.is_a?(Theme::TextElement) || value.is_a?(Theme::BlankElement)
172
+ return value
173
+ elsif value.is_a?(Hash)
174
+ return Theme::TextElement.new(**value)
175
+ else
176
+ raise "Expected text or hash"
177
+ end
178
+ end
179
+
180
+ def unit_el(value)
181
+ numeric = nil
182
+ unit = nil
183
+
184
+ if input.is_a?(String)
185
+ numeric, unit = *input.scan(/^([0-9.]+)([A-Za-z]+)/).flatten
186
+
187
+ raise "Unclear numeric part in #{input}" unless numeric
188
+ raise "Unclear unit part in #{input}" unless unit
189
+ elsif input.is_a?(Numeric)
190
+ numeric = input
191
+ unit = "npc"
192
+ end
193
+
194
+ raise "Unable to handle #{input}" unless numeric && unit
195
+
196
+ function = Rust::Function.new("units")
197
+ function.arguments = Rust::Arguments.new([numeric, unit])
198
+
199
+ return function.to_R
200
+ end
201
+
202
+ def alignment_el(value)
203
+ if value.is_a?(String) || value.is_a?(Symbol)
204
+ case value.to_s.downcase
205
+ when 'left'
206
+ value = 1
207
+ when 'right'
208
+ value = 0
209
+ else
210
+ value = 1
211
+ end
212
+ end
213
+
214
+ return value
215
+ end
216
+
217
+ def numeric_el(value)
218
+ raise "Expected number" unless value.is_a?(Numeric)
219
+ return value
220
+ end
221
+
222
+ def build
223
+ @options
224
+ end
225
+ end
226
+
227
+ class ThemeBuilder < ThemeComponentBuilder
228
+ def initialize(starting = 'bw')
229
+ super("plot")
230
+ @starting = starting
231
+ end
232
+
233
+ def background(value)
234
+ self.option('background', rect_el(value))
235
+ end
236
+
237
+ def title(value)
238
+ self.option('title', text_el(value))
239
+ end
240
+
241
+ def axis
242
+ builder = ThemeAxisBuilder.new
243
+ yield builder
244
+
245
+ @options.merge!(builder.build)
246
+ return self
247
+ end
248
+
249
+ def legend
250
+ builder = ThemeLegendBuilder.new
251
+ yield builder
252
+
253
+ @options.merge!(builder.build)
254
+ return self
255
+ end
256
+
257
+ def panel
258
+ builder = ThemePanelBuilder.new
259
+ yield builder
260
+
261
+ @options.merge!(builder.build)
262
+ return self
263
+ end
264
+
265
+ def build
266
+ return Theme.new(@starting, **@options)
267
+ end
268
+ end
269
+
270
+ class ThemeAxisBuilder < ThemeComponentBuilder
271
+ def initialize
272
+ super("axis")
273
+ end
274
+
275
+ def line(value)
276
+ self.option('line', line_el(value))
277
+ end
278
+
279
+ def text(value)
280
+ self.option('text', text_el(value))
281
+ end
282
+
283
+ def text_x(value)
284
+ self.option('text.x', text_el(value))
285
+ end
286
+
287
+ def text_y(value)
288
+ self.option('text.y', text_el(value))
289
+ end
290
+
291
+ def title(value)
292
+ self.option('title', text_el(value))
293
+ end
294
+
295
+ def title_x(value)
296
+ self.option('title.x', text_el(value))
297
+ end
298
+
299
+ def title_y(value)
300
+ self.option('title.y', text_el(value))
301
+ end
302
+
303
+ def ticks(value)
304
+ self.option('ticks', line_el(value))
305
+ end
306
+
307
+ def ticks_length(value)
308
+ self.option('ticks.length', unit_el(value))
309
+ end
310
+ end
311
+
312
+ class ThemeLegendBuilder < ThemeComponentBuilder
313
+ def initialize
314
+ super("legend")
315
+ end
316
+
317
+ def position(value)
318
+ self.option('position', value)
319
+ end
320
+
321
+ def justification(value)
322
+ self.option('justification', value)
323
+ end
324
+
325
+ def background(value)
326
+ self.option('background', rect_el(value))
327
+ end
328
+
329
+ def key_background(value)
330
+ self.option('key', rect_el(value))
331
+ end
332
+
333
+ def key_size(value)
334
+ self.option('key.size', unit_el(value))
335
+ end
336
+
337
+ def key_height(value)
338
+ self.option('key.height', unit_el(value))
339
+ end
340
+
341
+ def key_width(value)
342
+ self.option('key.width', unit_el(value))
343
+ end
344
+
345
+ def margin(value)
346
+ self.option('margin', unit_el(value))
347
+ end
348
+
349
+ def text(value)
350
+ self.option('text', text_el(value))
351
+ end
352
+
353
+ def text_align(value)
354
+ self.option('text.align', alignment_el(value))
355
+ end
356
+
357
+ def title(value)
358
+ self.option('title', text_el(value))
359
+ end
360
+
361
+ def title_align(value)
362
+ self.option('key.size', alignment_el(value))
363
+ end
364
+ end
365
+
366
+ class ThemePanelBuilder < ThemeComponentBuilder
367
+ def initialize
368
+ super("panel")
369
+ end
370
+
371
+ def background(value)
372
+ self.option('background', rect_el(value))
373
+ end
374
+
375
+ def border(value)
376
+ self.option('border', rect_el(value))
377
+ end
378
+
379
+ def grid_major(value)
380
+ self.option('grid.major', line_el(value))
381
+ end
382
+
383
+ def grid_major_x(value)
384
+ self.option('grid.major.x', line_el(value))
385
+ end
386
+
387
+ def grid_major_y(value)
388
+ self.option('grid.major.y', line_el(value))
389
+ end
390
+
391
+ def grid_minor(value)
392
+ self.option('grid.minor', line_el(value))
393
+ end
394
+
395
+ def grid_minor_x(value)
396
+ self.option('grid.minor.x', line_el(value))
397
+ end
398
+
399
+ def grid_minor_y(value)
400
+ self.option('grid.minor.y', line_el(value))
401
+ end
402
+
403
+ def aspect_ratio(value)
404
+ self.option('aspect.ratio', numeric_el(value))
405
+ end
406
+
407
+ def margin(value)
408
+ self.option('margin', unit_el(value))
409
+ end
410
+
411
+ def margin_x(value)
412
+ self.option('margin.x', unit_el(value))
413
+ end
414
+
415
+ def margin_y(value)
416
+ self.option('margin.y', unit_el(value))
417
+ end
418
+ end
419
+
420
+ self.default_theme = ThemeBuilder.new.
421
+ title(face: 'bold', size: 12).
422
+ legend do |legend|
423
+ legend.background(fill: 'white', size: 4, colour: 'white')
424
+ legend.position([0, 1])
425
+ legend.justification([0, 1])
426
+ end.
427
+ axis do |axis|
428
+ axis.ticks(colour: 'grey70', size: 0.2)
429
+ end.
430
+ panel do |panel|
431
+ panel.grid_major(colour: 'grey70', size: 0.2)
432
+ panel.grid_minor(Theme::BlankElement.new)
433
+ end.
434
+ build
435
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'ggplot2/core'
2
+ require_relative 'ggplot2/geoms'
3
+ require_relative 'ggplot2/themes'
4
+ require_relative 'ggplot2/plot_builder'
5
+ require_relative 'ggplot2/helper'
@@ -0,0 +1,44 @@
1
+ require 'rust'
2
+
3
+ Rust.prerequisite('robustbase')
4
+
5
+ module Rust::Plots
6
+ class AdjustedBoxplot < DistributionPlot
7
+ protected
8
+ def _show()
9
+ function = Rust::Function.new("adjbox")
10
+
11
+ names = []
12
+ @series.each_with_index do |data, i|
13
+ series, options = *data
14
+ varname = "plotter.series#{i}"
15
+ Rust[varname] = series
16
+ function.arguments << Rust::Variable.new(varname)
17
+ names << (options[:name] || (i+1).to_s)
18
+ end
19
+
20
+ function.options = self._augmented_options({'names' => names})
21
+
22
+ function.call
23
+
24
+ return self
25
+ end
26
+ end
27
+ end
28
+
29
+ module Rust::RBindings
30
+ def adjbox(*args, **options)
31
+ result = Rust::Plots::AdjustedBoxplot.new
32
+ options.each do |k, v|
33
+ result[k] = v
34
+ end
35
+
36
+ result._do_not_override_options!
37
+
38
+ args.each do |s|
39
+ result.series(s)
40
+ end
41
+
42
+ result.show
43
+ end
44
+ end
@@ -1,6 +1,10 @@
1
1
  require_relative '../core'
2
2
 
3
3
  module Rust
4
+
5
+ ##
6
+ # Mirror for an ANOVA model type in R. To create a new ANOVA model (aov), call the #generate method.
7
+
4
8
  class ANOVAModel < RustDatatype
5
9
  def self.can_pull?(type, klass)
6
10
  return type == "list" && [klass].flatten.include?("aov")
@@ -16,6 +20,10 @@ module Rust
16
20
  @model.load_in_r_as(variable_name)
17
21
  end
18
22
 
23
+ ##
24
+ # Generates a new ANOVA model with a given +formula+, +data+. +options+ can be specified and directly passed
25
+ # to the aov function in R.
26
+
19
27
  def self.generate(formula, data, **options)
20
28
  mapped = ""
21
29
  if options.size > 0
@@ -32,14 +40,23 @@ module Rust
32
40
  end
33
41
  end
34
42
 
43
+ ##
44
+ # Creates a new +model+.
45
+
35
46
  def initialize(model)
36
47
  @model = model
37
48
  end
38
49
 
50
+ ##
51
+ # Returns the model.
52
+
39
53
  def model
40
54
  @model
41
55
  end
42
56
 
57
+ ##
58
+ # Returns a summary of the ANOVA model through the summary function in R.
59
+
43
60
  def summary
44
61
  unless @summary
45
62
  Rust.exclusive do
@@ -5,7 +5,14 @@ require_relative '../stats/correlation'
5
5
  module Rust::Models
6
6
  end
7
7
 
8
+ ##
9
+ # Contains classes that allow to run regression models.
10
+
8
11
  module Rust::Models::Regression
12
+
13
+ ##
14
+ # Generic regression model in R.
15
+
9
16
  class RegressionModel < Rust::RustDatatype
10
17
  def self.can_pull?(type, klass)
11
18
  # Can only pull specific sub-types
@@ -16,6 +23,11 @@ module Rust::Models::Regression
16
23
  @model.load_in_r_as(variable_name)
17
24
  end
18
25
 
26
+ ##
27
+ # Generates a new regression model. +object_type+ is the Ruby class of the model object; +model_type+ represents
28
+ # the type of model at hand; +dependent_variable+ and +independent_variables+ are directly used as part of the
29
+ # model formula. +data+ represents the dataset to be used. +options+ can be specified and directly passed to the
30
+ # model.
19
31
 
20
32
  def self.generate(object_type, model_type, dependent_variable, independent_variables, data, **options)
21
33
  mapped = ""
@@ -36,6 +48,9 @@ module Rust::Models::Regression
36
48
  return result
37
49
  end
38
50
  end
51
+
52
+ ##
53
+ # Creates a new +model+.
39
54
 
40
55
  def initialize(model)
41
56
  raise StandardError if model.is_a?(RegressionModel)
@@ -46,6 +61,9 @@ module Rust::Models::Regression
46
61
  @model
47
62
  end
48
63
 
64
+ ##
65
+ # Returns the residuals of the model.
66
+
49
67
  def residuals
50
68
  Rust.exclusive do
51
69
  @residuals = Rust["residuals(#{self.r_mirror})"] unless @residuals
@@ -54,6 +72,9 @@ module Rust::Models::Regression
54
72
  return @residuals
55
73
  end
56
74
 
75
+ ##
76
+ # Returns the fitted values of the model.
77
+
57
78
  def fitted
58
79
  Rust.exclusive do
59
80
  @fitted = Rust["fitted(#{self.r_mirror})"] unless @fitted
@@ -62,22 +83,37 @@ module Rust::Models::Regression
62
83
  return @fitted
63
84
  end
64
85
 
86
+ ##
87
+ # Returns the actual values in the dataset.
88
+
65
89
  def actuals
66
90
  return self.fitted.zip(self.residuals).map { |couple| couple.sum }
67
91
  end
68
92
 
93
+ ##
94
+ # Returns the r-squared of the model.
95
+
69
96
  def r_2
70
97
  return self.summary|"r.squared"
71
98
  end
72
99
 
100
+ ##
101
+ # Returns the adjusted r-squared of the model.
102
+
73
103
  def r_2_adjusted
74
104
  return self.summary|"adj.r.squared"
75
105
  end
76
106
 
107
+ ##
108
+ # Returns the mean squared error of the model.
109
+
77
110
  def mse
78
111
  Rust::Descriptive.variance(self.residuals)
79
112
  end
80
113
 
114
+ ##
115
+ # Returns the coefficients of the model.
116
+
81
117
  def coefficients
82
118
  a = self.summary|"coefficients"
83
119
  end
@@ -86,6 +122,9 @@ module Rust::Models::Regression
86
122
  return model|name.to_s
87
123
  end
88
124
 
125
+ ##
126
+ # Returns a summary for the model using the summary function in R.
127
+
89
128
  def summary
90
129
  unless @summary
91
130
  Rust.exclusive do
@@ -101,6 +140,9 @@ module Rust::Models::Regression
101
140
  end
102
141
  end
103
142
 
143
+ ##
144
+ # Represents a linear regression model in R.
145
+
104
146
  class LinearRegressionModel < RegressionModel
105
147
  def self.can_pull?(type, klass)
106
148
  return type == "list" && klass == "lm"
@@ -112,6 +154,10 @@ module Rust::Models::Regression
112
154
  return LinearRegressionModel.new(model)
113
155
  end
114
156
 
157
+ ##
158
+ # Generates a linear regression model, given its +dependent_variable+ and +independent_variables+ and its +data+.
159
+ # +options+ can be specified and directly passed to the model.
160
+
115
161
  def self.generate(dependent_variable, independent_variables, data, **options)
116
162
  RegressionModel.generate(
117
163
  LinearRegressionModel,
@@ -124,6 +170,9 @@ module Rust::Models::Regression
124
170
  end
125
171
  end
126
172
 
173
+ ##
174
+ # Represents a linear mixed effects model in R.
175
+
127
176
  class LinearMixedEffectsModel < RegressionModel
128
177
  def self.can_pull?(type, klass)
129
178
  return type == "S4" && klass == "lmerModLmerTest"
@@ -152,6 +201,10 @@ module Rust::Models::Regression
152
201
  return @summary
153
202
  end
154
203
 
204
+ ##
205
+ # Generates a linear mixed effects model, given its +dependent_variable+ and +independent_variables+ and its +data+.
206
+ # +options+ can be specified and directly passed to the model.
207
+
155
208
  def self.generate(dependent_variable, fixed_effects, random_effects, data, **options)
156
209
  Rust.prerequisite("lmerTest")
157
210
  Rust.prerequisite("rsq")
@@ -169,7 +222,7 @@ module Rust::Models::Regression
169
222
  end
170
223
 
171
224
  def r_2
172
- Rust.exclusive do
225
+ Rust.exclusive do
173
226
  Rust._eval("tmp.rsq <- rsq(#{self.r_mirror}, adj=F)")
174
227
  return Rust['tmp.rsq']
175
228
  end