rust 0.11 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77dee9cb8c4a1e2894a8054fab8dbf140b8f6a4aed8ef38a60b41564fb5e8e69
4
- data.tar.gz: c9bf14d2239f464cf73ffaee86bcbfe9155a323269eff1b34b7bcd1272c46d36
3
+ metadata.gz: 56e795fb0a8893df45abd976e2ed91344156f3c3dd4a68e17afd1a0fb317ece3
4
+ data.tar.gz: 406416738f1ab84fca06edd5cb59efdc623b12cefe03cd51b1a2cd840e218647
5
5
  SHA512:
6
- metadata.gz: a648f440574e7734f979c0ec5d65bc9594202cfe88414517dcc50482e391360809ced88bbc0b825f548dfd23dd2b9dfe47b71cabbd216249d992d8a18d14717c
7
- data.tar.gz: 1b999d94b96c05a0615ff05b3e1588c4a8e822baebba57055ae041a6fe2c7d0355d747e3c88973c9325046c59c4eff013b27336d95297c8c0e1cc06f515b2f27
6
+ metadata.gz: 56854c3ff1bbd64ca8ff9d1201bc16fd37f4d3d465527217ab5c49d5cee0d6f4f34998bdf8a3ebdedf7ea8379909ca0db75d05da1bd46460c3e7066ee882ba7b
7
+ data.tar.gz: 6b21ba70c7d144384d1647dfa76f8894457bf4d65b74ce32d4e775cc3ecdc660f4596f61edd81e91b08c9d2b3def3491cebe10b7923b9f2988d072f7c5d25674
data/lib/rust/core/csv.rb CHANGED
@@ -90,9 +90,9 @@ module Rust
90
90
  dataframe.column_names.each do |column_name|
91
91
  values = dataframe.column(column_name)
92
92
 
93
- if values.all? { |s| !!Integer(s) rescue false }
93
+ if values.all? { |s| s == nil || !!Integer(s) rescue false }
94
94
  integer_columns << column_name
95
- elsif values.all? { |s| !!Float(s) rescue false }
95
+ elsif values.all? { |s| s == nil || !!Float(s) rescue false }
96
96
  float_columns << column_name
97
97
  end
98
98
  end
@@ -103,11 +103,11 @@ module Rust
103
103
  end
104
104
 
105
105
  integer_columns.each do |numeric_column|
106
- dataframe.transform_column!(numeric_column) { |v| v.to_i }
106
+ dataframe.transform_column!(numeric_column) { |v| v != nil ? v.to_i : v }
107
107
  end
108
108
 
109
109
  float_columns.each do |numeric_column|
110
- dataframe.transform_column!(numeric_column) { |v| v.to_f }
110
+ dataframe.transform_column!(numeric_column) { |v| v != nil ? v.to_f : v }
111
111
  end
112
112
 
113
113
  return dataframe
@@ -0,0 +1,89 @@
1
+ require_relative 'rust'
2
+
3
+ module Rust
4
+ class Manual
5
+ @@manuals = {}
6
+
7
+ def self.about
8
+ puts "Manuals available:"
9
+ @@manuals.each do |category, manual|
10
+ puts "\t- #{manual.name} (:#{category}) → #{manual.description}"
11
+ end
12
+
13
+ return nil
14
+ end
15
+
16
+ def self.for(category)
17
+ category = category.to_sym
18
+ raise "No manual found for '#{category}'." unless @@manuals[category]
19
+
20
+ return @@manuals[category]
21
+ end
22
+
23
+ def self.register(category, name, description)
24
+ category = category.to_sym
25
+
26
+ @@manuals[category] = Manual.new(name, description)
27
+
28
+ return nil
29
+ end
30
+
31
+ attr_reader :name
32
+ attr_reader :description
33
+
34
+ def initialize(name, description)
35
+ @name = name
36
+ @description = description
37
+ @voices = {}
38
+ end
39
+
40
+ def lookup(query)
41
+ @voices.each do |key, value|
42
+ if query.match(key[1])
43
+ puts "*** #{key[0]} ***"
44
+ puts value
45
+ return
46
+ end
47
+ end
48
+
49
+ puts "Voice not found"
50
+
51
+ return nil
52
+ end
53
+
54
+ def n_voices
55
+ @voices.size
56
+ end
57
+
58
+ def about
59
+ puts "****** Manual for #@name ******"
60
+ puts @description
61
+ puts "Voices in manual #@name:"
62
+ @voices.keys.each do |key, matcher|
63
+ puts "\t- #{key}"
64
+ end
65
+
66
+ return nil
67
+ end
68
+
69
+ def register(voice, matcher, description)
70
+ @voices[[voice, matcher]] = description
71
+ end
72
+
73
+ def inspect
74
+ return "Manual for #@name with #{self.n_voices} voices"
75
+ end
76
+ end
77
+ end
78
+
79
+ module Rust::RBindings
80
+ def rust_help(category = nil, query = nil)
81
+ if !category
82
+ return Rust::Manual.about
83
+ elsif !query
84
+ return Rust::Manual.for(category).about
85
+ else
86
+ return Rust::Manual.for(category).lookup(query)
87
+ end
88
+ end
89
+ end
@@ -102,7 +102,10 @@ module Rust
102
102
  end
103
103
 
104
104
  def self._rexec(r_command, return_warnings = false)
105
- puts "Calling _rexec with command: #{r_command}" if @@debugging
105
+ if @@debugging
106
+ puts "Calling _rexec with command: #{r_command}"
107
+ puts "\t" + Kernel.caller.select { |v| !v.include?("irb") }.last(3).map { |v| v.sub(/^.*gems\//, "")}.join("\n\t")
108
+ end
106
109
  R_MUTEX.synchronize do
107
110
  assert("This command must be executed in an exclusive block") { @@in_client_mutex }
108
111
 
@@ -154,10 +157,17 @@ module Rust
154
157
 
155
158
  ##
156
159
  # Installs the given +name+ library and its dependencies.
160
+ # +github+ indicates whether the package is in GitHub.
157
161
 
158
- def self.install_library(name)
162
+ def self.install_library(name, github = false)
163
+ self.prerequisite("remotes") if github
164
+
159
165
  self.exclusive do
160
- self._eval("install.packages(\"#{name}\", dependencies = TRUE)")
166
+ if github
167
+ self._eval("remotes::install_github(\"#{name}\", dependencies=TRUE)")
168
+ else
169
+ self._eval("install.packages(\"#{name}\", dependencies = TRUE)")
170
+ end
161
171
  end
162
172
 
163
173
  return nil
@@ -165,9 +175,15 @@ module Rust
165
175
 
166
176
  ##
167
177
  # Installs the +library+ library if it is not available and loads it.
178
+ # +github+ indicates whether the package appears in GitHub.
168
179
 
169
- def self.prerequisite(library)
170
- self.install_library(library) unless self.check_library(library)
180
+ def self.prerequisite(library, github = false)
181
+ full_library = library
182
+ library = library.split("/").last if github
183
+
184
+ unless self.check_library(library)
185
+ self.install_library(full_library, github)
186
+ end
171
187
  self.load_library(library)
172
188
  end
173
189
 
@@ -218,4 +234,11 @@ def bind_r!
218
234
  include Rust::RBindings
219
235
  end
220
236
 
237
+ ##
238
+ # Shortcut for requiring rust external libraries
239
+
240
+ def require_rust(name)
241
+ require "rust/external/#{name}"
242
+ end
243
+
221
244
  bind_r! if ENV['RUBY_RUST_BINDING'] == '1'
@@ -1,4 +1,5 @@
1
1
  require_relative 'datatype'
2
+ require 'tempfile'
2
3
 
3
4
  module Rust
4
5
 
@@ -355,8 +356,25 @@ module Rust
355
356
  end
356
357
 
357
358
  def load_in_r_as(variable_name)
358
- command = []
359
+ tempfile = Tempfile.new('rust.dfport')
360
+ tempfile.close
361
+
362
+ Rust::CSV.write(tempfile.path, self)
363
+ Rust._eval("#{variable_name} <- read.csv(\"#{tempfile.path}\", header=T)")
364
+
365
+ if Rust.debug?
366
+ FileUtils.cp(tempfile.path, tempfile.path + ".debug.csv")
367
+ puts "Debug CSV port file available at: #{tempfile.path + ".debug.csv"}"
368
+ end
369
+
370
+ tempfile.unlink
359
371
 
372
+ return true
373
+ end
374
+
375
+ def directly_load_in_r_as(variable_name)
376
+ command = []
377
+
360
378
  command << "#{variable_name} <- data.frame()"
361
379
  row_index = 1
362
380
  self.each do |row|
@@ -374,6 +392,10 @@ module Rust
374
392
  end
375
393
 
376
394
  Rust._eval_big(command)
395
+
396
+ tempfile.unlink
397
+
398
+ return true
377
399
  end
378
400
 
379
401
  def inspect
@@ -408,16 +430,39 @@ module Rust
408
430
  return result
409
431
  end
410
432
 
433
+ ##
434
+ # Merges this data-frame with +other+ in terms of the +by+ column(s) (Array or String). Keeps all the rows in this data frame.
435
+ # +first_alias+ and +second_alias+ allow to specify the prefix that should be used for the columns not in +by+
436
+ # for this and the +other+ data-frame, respectively.
437
+
438
+ def left_merge(other, by, first_alias, second_alias, **options)
439
+ options[:keep_right] = true
440
+ options[:keep_left] = false
441
+ return other.merge(self, by, first_alias, second_alias, **options)
442
+ end
443
+
444
+ ##
445
+ # Merges this data-frame with +other+ in terms of the +by+ column(s) (Array or String). Keeps all the rows in the other data frame.
446
+ # +first_alias+ and +second_alias+ allow to specify the prefix that should be used for the columns not in +by+
447
+ # for this and the +other+ data-frame, respectively.
448
+
449
+ def right_merge(other, by, first_alias, second_alias, **options)
450
+ options[:keep_right] = true
451
+ options[:keep_left] = false
452
+ return self.merge(other, by, first_alias, second_alias, **options)
453
+ end
454
+
411
455
  ##
412
456
  # Merges this data-frame with +other+ in terms of the +by+ column(s) (Array or String).
413
457
  # +first_alias+ and +second_alias+ allow to specify the prefix that should be used for the columns not in +by+
414
458
  # for this and the +other+ data-frame, respectively.
415
459
 
416
- def merge(other, by, first_alias = "x", second_alias = "y")
460
+ def merge(other, by, first_alias = "x", second_alias = "y", **options)
417
461
  raise TypeError, "Expected Rust::DataFrame" unless other.is_a?(DataFrame)
418
462
  raise TypeError, "Expected list of strings" if !by.is_a?(Array) || !by.all? { |e| e.is_a?(String) }
419
463
  raise "This dataset should have all the columns in #{by}" unless (by & self.column_names).size == by.size
420
464
  raise "The passed dataset should have all the columns in #{by}" unless (by & other.column_names).size == by.size
465
+ raise "Either keep_right or keep_left should be provided as options, not both" if options[:keep_right] && options[:keep_left]
421
466
 
422
467
  if first_alias == second_alias
423
468
  if first_alias == ""
@@ -473,6 +518,28 @@ module Rust
473
518
  end
474
519
 
475
520
  result << to_add
521
+
522
+ elsif options[:keep_right]
523
+ to_add = {}
524
+
525
+ by.each do |colname|
526
+ to_add[colname] = other_row[colname]
527
+ end
528
+
529
+ merged_column_self.each do |colname|
530
+ to_add["#{first_alias}#{colname}"] = nil
531
+ end
532
+
533
+ merged_column_other.each do |colname|
534
+ to_add["#{second_alias}#{colname}"] = other_row[colname]
535
+ end
536
+
537
+ result << to_add
538
+
539
+ elsif options[:keep_left]
540
+ options[:keep_left] = false
541
+ options[:keep_right] = true
542
+ return other.merge(self, by, first_alias, second_alias, **options)
476
543
  end
477
544
  end
478
545
 
@@ -36,7 +36,7 @@ module Rust
36
36
  if candidates.size > 0
37
37
  type = candidates.max_by { |c| c.pull_priority }
38
38
 
39
- puts "Using #{type} to pull #{variable}" if Rust.debug?
39
+ puts "Using #{type} to pull #{variable} (candidates: #{candidates.map { |c| c.to_s + "=>" + c.pull_priority.to_s}.join(", ")})" if Rust.debug?
40
40
  return type.pull_variable(variable, r_type, r_class)
41
41
  else
42
42
  if Rust._pull("length(#{variable})") == 0
@@ -80,7 +80,7 @@ module Rust
80
80
  def r_mirror
81
81
  varname = self.mirrored_R_variable_name
82
82
 
83
- if !Rust._pull("exists(\"#{varname}\")") || Rust._pull("#{varname}.hash") != self.r_hash
83
+ if !Rust._pull("exists(\"#{varname}\")") || Rust["#{varname}.hash"] != self.r_hash
84
84
  puts "Loading #{varname}" if Rust.debug?
85
85
  Rust[varname] = self
86
86
  Rust["#{varname}.hash"] = self.r_hash
@@ -125,6 +125,10 @@ module Rust
125
125
  @level
126
126
  end
127
127
 
128
+ def to_str
129
+ @level.to_s
130
+ end
131
+
128
132
  def to_R
129
133
  self.to_i
130
134
  end
@@ -137,7 +137,7 @@ module Rust
137
137
  # Sets the +arguments+ (Arguments type) of the function.
138
138
 
139
139
  def arguments=(arguments)
140
- raise TypeError, "Expected Arguments" unless options.is_a?(Arguments)
140
+ raise TypeError, "Expected Arguments" unless arguments.is_a?(Arguments)
141
141
 
142
142
  @arguments = arguments
143
143
  end
@@ -171,6 +171,22 @@ module Rust
171
171
  end
172
172
  end
173
173
 
174
+ ##
175
+ # Represents a verbatim R expression
176
+
177
+ class Verbatim
178
+ ##
179
+ #Creates a verbatim R expression
180
+
181
+ def initialize(expression)
182
+ @expression = expression
183
+ end
184
+
185
+ def to_R
186
+ @expression
187
+ end
188
+ end
189
+
174
190
  ##
175
191
  # Represents the arguments of a function in R. Works as an Array of objects.
176
192
 
@@ -196,4 +212,20 @@ module Rust
196
212
  return options
197
213
  end
198
214
  end
215
+
216
+ def self.verbatim(expression)
217
+ Verbatim.new(expression)
218
+ end
219
+
220
+ def self.variable(variable)
221
+ Variable.new(variable)
222
+ end
223
+
224
+ def self.function(name)
225
+ Function.new(name)
226
+ end
227
+
228
+ def self.formula(left_part, right_part)
229
+ Formula.new(left_part, right_part)
230
+ end
199
231
  end
@@ -29,6 +29,8 @@ module Rust
29
29
  @data.each do |key, value|
30
30
  Rust["#{variable_name}[[#{key + 1}]]"] = value
31
31
  end
32
+
33
+ Rust._eval("names(#{variable_name}) <- #{self.names.to_R}")
32
34
  end
33
35
 
34
36
  ##
@@ -81,6 +81,14 @@ module Rust
81
81
  @data.size
82
82
  end
83
83
 
84
+ def rownames
85
+ @row_names
86
+ end
87
+
88
+ def colnames
89
+ @column_names
90
+ end
91
+
84
92
  ##
85
93
  # Returns the number of columns.
86
94
 
data/lib/rust/core.rb CHANGED
@@ -1,7 +1,57 @@
1
1
  require_relative 'core/rust'
2
2
  require_relative 'core/csv'
3
+ require_relative 'core/manual'
3
4
 
4
5
  self_path = File.expand_path(__FILE__)
5
6
  Dir.glob(File.join(File.dirname(self_path), "core/types/*.rb")).each do |lib|
6
7
  require_relative lib
7
8
  end
9
+
10
+ Rust::Manual.register(:base, "Quick intro", "Core philosophy behind Rust.")
11
+ Rust::Manual.for(:base).register('Introduction', /intro/,
12
+ <<-EOS
13
+ Rust is a statistical library. Rust wraps R and its libraries to achieve this goal.
14
+ Rust aims at:
15
+ - Making easier for Ruby developers make all the kinds of operations that are straightforward in R;
16
+ - Providing an object-oriented interface, more familiar than the one in R.
17
+
18
+ Rust can be used in two ways:
19
+ - By using the object-oriented interface (advised if you are writing a script);
20
+ - By using the R bindings, that allow to use Ruby pretty much like R (handful if you are using it from IRB).
21
+
22
+ Rust provides wrappers for many elements, including types (e.g., data frames), statistical hypothesis tests, plots, and so on.
23
+ Under the hood, Rust creates an R environment (through rinruby), through which Rust can perform the most advanced operations,
24
+ for which a re-implementation would be impractical.
25
+ EOS
26
+ )
27
+
28
+ Rust::Manual.for(:base).register('Types', /type/,
29
+ <<-EOS
30
+ Rust provides wrappers for the most commonly-found types in R. Specifically, the following types are available:
31
+ - Data frames → Rust::DataFrame
32
+ - Factors → Rust::Factor
33
+ - Matrices → Rust::Matrix
34
+ - Lists → Rust::List
35
+ - S4 classes → Rust::S4Class
36
+ - Formulas → Rust::Formula
37
+
38
+ Note that some of them (e.g., data frames and matrices) are not just wrappers, but complete re-implementations of the R
39
+ types (for performance reasons).
40
+ EOS
41
+ )
42
+
43
+ Rust::Manual.for(:base).register('CSVs', /csv/,
44
+ <<-EOS
45
+ Rust allows to read and write CSV files, mostly like in R.
46
+ To read a CSV file, you can use:
47
+ Rust::CSV.read(filename)
48
+
49
+ It returns a data frame. You can also specify the option "headers" to tell if the first row in the CSV contains the headers
50
+ (column names for the data frame). Other options get directly passed to the R function "read.csv".
51
+
52
+ To write a CSV file, you can use:
53
+ Rust::CSV.write(filename, data_frame)
54
+
55
+ It writes the given data frame on the file at filename.
56
+ EOS
57
+ )
@@ -0,0 +1,171 @@
1
+ require_relative '../../../rust'
2
+
3
+ Rust.prerequisite("ggplot2")
4
+
5
+ module Rust::Plots::GGPlot
6
+ def self.default_theme
7
+ @@theme
8
+ end
9
+
10
+ def self.default_theme=(theme)
11
+ @@theme = theme.freeze
12
+ end
13
+
14
+ class Layer
15
+ def initialize(function_name, **options)
16
+ @function_name = function_name
17
+ @arguments = Rust::Arguments.new
18
+ @options = Rust::Options.from_hash(options)
19
+ end
20
+
21
+ def option(key, value)
22
+ @options[key] = value
23
+ end
24
+
25
+ def to_R
26
+ if !block_given?
27
+ options, arguments = @options, @arguments
28
+ else
29
+ options, arguments = yield(@options, @arguments)
30
+ end
31
+
32
+ options = Rust::Options.from_hash(options) unless options.is_a?(Rust::Options)
33
+
34
+ function = Rust::Function.new(@function_name)
35
+ function.arguments = arguments if arguments
36
+ function.options = options if options
37
+ return function.to_R
38
+ end
39
+ end
40
+
41
+ class Aes
42
+ def initialize(**options)
43
+ options = options.map { |k, v| [k, v.is_a?(Symbol) ? Rust::Variable.new(v) : v] }.to_h
44
+ @options = Rust::Options.from_hash(options)
45
+ end
46
+
47
+ def to_R
48
+ function = Rust::Function.new("aes")
49
+ function.options = @options if @options
50
+ return function.to_R
51
+ end
52
+ end
53
+
54
+ class Plot
55
+ attr_accessor :theme
56
+ attr_accessor :aes
57
+
58
+ def initialize(data, aes = nil)
59
+ @layers = []
60
+
61
+ @data = data
62
+ @aes = aes
63
+ @theme = Rust::Plots::GGPlot.default_theme
64
+ end
65
+
66
+ def layer(layer)
67
+ @layers << layer
68
+ end
69
+
70
+ def show()
71
+ Rust.exclusive do
72
+ dataset_name = nil
73
+ if @data
74
+ dataset_name = "ggplotter.data"
75
+ @data.load_in_r_as(dataset_name)
76
+ end
77
+ r = self.to_R(dataset_name)
78
+ Rust._eval(r)
79
+ end
80
+ end
81
+
82
+ def save(filename, **options)
83
+ Rust.exclusive do
84
+ dataset_name = nil
85
+ if @data
86
+ dataset_name = "ggplotter.data"
87
+ @data.load_in_r_as(dataset_name)
88
+ end
89
+ r = self.to_R(dataset_name)
90
+ Rust._eval("ggplot.latest <- #{r}")
91
+ save = Rust::Function.new("ggsave")
92
+ save.arguments = Rust::Arguments.new([Rust::Variable.new("ggplot.latest")])
93
+ save.options = Rust::Options.from_hash({file: filename}.merge(options))
94
+ save.call
95
+ end
96
+ end
97
+
98
+ def to_R(data_set_name="ggplotter.data")
99
+ function = Rust::Function.new("ggplot")
100
+ function.arguments = Rust::Arguments.new
101
+ function.arguments << (data_set_name ? Rust::Variable.new(data_set_name) : nil)
102
+ function.arguments << @aes if @aes
103
+
104
+ result = function.to_R
105
+ result += " + " + @theme.to_R
106
+ @layers.each do |layer|
107
+ result += " + " + layer.to_R
108
+ end
109
+
110
+ return result
111
+ end
112
+
113
+ def <<(others)
114
+ if others.is_a?(Array) && others.all? { |o| o.is_a?(Layer) }
115
+ @layers += others
116
+ elsif others.is_a?(Layer)
117
+ @layers << others
118
+ else
119
+ raise ArgumentError, "Expected Layer or Array of Layers"
120
+ end
121
+
122
+ return self
123
+ end
124
+
125
+ def +(others)
126
+ copy = self.deep_clone
127
+ copy << others
128
+ return copy
129
+ end
130
+
131
+ def inspect(show = true)
132
+ self.show if show
133
+ return super()
134
+ end
135
+ end
136
+
137
+ class Labels < Layer
138
+ def initialize(**options)
139
+ super("labs", **options)
140
+ end
141
+ end
142
+
143
+ class FlipCoordinates < Layer
144
+ def initialize(**options)
145
+ super("coord_flip", **options)
146
+ end
147
+ end
148
+ end
149
+
150
+ module Rust::RBindings
151
+ def ggplot(*arguments)
152
+ Rust::Plots::GGPlot::Plot.new(*arguments)
153
+ end
154
+
155
+ def aes(**options)
156
+ Rust::Plots::GGPlot::Aes.new(**options)
157
+ end
158
+
159
+ def labs(**options)
160
+ Rust::Plots::GGPlot::Labels.new(**options)
161
+ end
162
+ alias :labels :labs
163
+
164
+ def coord_flip(**options)
165
+ Rust::Plots::GGPlot::FlipCoordinates.new(**options)
166
+ end
167
+ end
168
+
169
+ def bind_ggplot!
170
+ include Rust::Plots::GGPlot
171
+ end