rust 0.9 → 0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -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