Module: R

Defined in:
lib/R_interface/rlist.rb,
lib/R_interface/r.rb,
lib/R_interface/rpkg.rb,
lib/R_interface/r_arrow.rb,
lib/R_interface/rdevice.rb,
lib/R_interface/rmatrix.rb,
lib/R_interface/robject.rb,
lib/R_interface/rsymbol.rb,
lib/R_interface/rvector.rb,
lib/R_interface/rclosure.rb,
lib/R_interface/rsupport.rb,
lib/R_interface/r_methods.rb,
lib/R_interface/rlanguage.rb,
lib/R_interface/r_module_s.rb,
lib/R_interface/rdata_frame.rb,
lib/R_interface/rexpression.rb,
lib/R_interface/renvironment.rb,
lib/R_interface/ruby_callback.rb,
lib/R_interface/rsupport_scope.rb,
lib/R_interface/rindexed_object.rb,
lib/R_interface/ruby_extensions.rb,
lib/R_interface/ruby_extensions.rb,
lib/R_interface/ruby_extensions.rb,
lib/R_interface/runary_operators.rb,
lib/R_interface/rbinary_operators.rb,
lib/R_interface/new_bridge_adapter.rb,
lib/R_interface/rlogical_operators.rb,
lib/R_interface/rmd_indexed_object.rb

Overview

Copyright © 2018 Rodrigo Botafogo. All Rights Reserved. Permission to use, copy, modify, and distribute this software and its documentation, without fee and without a signed licensing agreement, is hereby granted, provided that the above copyright notice, this paragraph and the following two paragraphs appear in all copies, modifications, and distributions.

IN NO EVENT SHALL RODRIGO BOTAFOGO BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF RODRIGO BOTAFOGO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

RODRIGO BOTAFOGO SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". RODRIGO BOTAFOGO HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

Author:

  • Rodrigo Botafogo

Defined Under Namespace

Modules: Arrow, Async, BinaryOperators, CallBinOp, ExecBinOp, ExecUniOp, ExpBinOp, ExpUniOp, IndexedObject, LogicalOperators, MDIndexedObject, S, Scope, Support, UnaryOperators Classes: BatchEvaluationError, Closure, DataFrame, Device, Environment, Language, List, Matrix, NewBridgeAdapter, NotAvailable, NullCheckResult, Object, Package, RExpression, RSymbol, RubyCallback, SymbolRef, UnboxDepthError, Vector

Constant Summary collapse

RCONSTANTS =
["LETTERS", "letters", "month.abb", "month.name", "pi"]
NA =
NotAvailable.new
NULL_IS_NULL =
NullCheckResult.new(true).freeze
NULL_IS_FALSE =
NullCheckResult.new(false).freeze

Class Method Summary collapse

Class Method Details

.[](name) ⇒ Object



45
46
47
# File 'lib/R_interface/r.rb', line 45

def self.[](name)
  R::SymbolRef.new(name)
end

.batch {|col| ... } ⇒ Object

Queue several eval_r_with_result assignments and send them in one bridge round-trip. Fail-fast: on the first R error, remaining queued ops are not run (+R::BatchEvaluationError+, #failed_index is 0-based). Same outcomes as sequential eval_r_with_result when all succeed.

Yields:

  • (col)

Raises:

  • (ArgumentError)


94
95
96
97
98
99
100
# File 'lib/R_interface/r.rb', line 94

def self.batch
  col = R::Support::BatchCollector.new
  yield col
  raise ArgumentError, 'R.batch requires at least one eval_with_result assignment' if col.ops.empty?

  R::Support.batch_eval_with_result(col.ops)
end

.bridgeObject

R↔Ruby bridge: NewBridge only.



60
61
62
63
# File 'lib/R_interface/r.rb', line 60

def self.bridge
  require_relative 'new_bridge_adapter'
  R::NewBridgeAdapter.instance
end

.call2(fn, *args, **kwargs) ⇒ Object

Build a call object with rlang::call2().



230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/R_interface/r.rb', line 230

def self.call2(fn, *args, **kwargs)
  ensure_rlang!
  var_name = R::Support.generate_var_name
  parts = [R::Support.parse_arg(fn)]
  parts.concat(args.map { |a| R::Support.parse_arg(a) }) unless args.empty?
  unless kwargs.empty?
    kw = kwargs.map { |k, v| "#{k.to_s.gsub(/__/, ".")} = #{R::Support.parse_arg(v)}" }
    parts.concat(kw)
  end
  R.bridge.eval_r("#{var_name} <- rlang::call2(#{parts.join(', ')})")
  R::Object.build(var_name)
end

.const_missing(name) ⇒ Object





57
58
59
# File 'lib/R_interface/rpkg.rb', line 57

def self.const_missing(name)
  R::Package[name.to_s.downcase]
end

.empty_symbolObject



12
13
14
# File 'lib/R_interface/rsupport.rb', line 12

def self.empty_symbol
  ""
end

.ensure_rlang!Object


rlang helpers (reintroduced legacy API surface)



213
214
215
216
217
218
# File 'lib/R_interface/r.rb', line 213

def self.ensure_rlang!
  ok = R::Support.eval("requireNamespace('rlang', quietly=TRUE)")
  return if ok == true

  raise "R package 'rlang' is required for this API. Install with: Rscript -e \"install.packages('rlang', repos='https://cloud.r-project.org/')\""
end

.eval_r_async(code, timeout: nil, &block) ⇒ Object

Async string eval against R; block receives NewBridge::EvalResult (+#value+ matches eval_r on success).



103
104
105
# File 'lib/R_interface/r.rb', line 103

def self.eval_r_async(code, timeout: nil, &block)
  R.bridge.eval_r_async(code, timeout: timeout, &block)
end

.exec(fn, *args, **kwargs) ⇒ Object

Execute a function call with dynamic args via rlang::exec().



244
245
246
247
# File 'lib/R_interface/r.rb', line 244

def self.exec(fn, *args, **kwargs)
  ensure_rlang!
  R.rlang___exec(fn, *args, **kwargs)
end

.expr(arg) ⇒ Object

Build an R expression/symbol using rlang::expr().



221
222
223
224
225
226
227
# File 'lib/R_interface/r.rb', line 221

def self.expr(arg)
  ensure_rlang!
  var_name = R::Support.generate_var_name
  parsed = R::Support.parse_arg(arg)
  R.bridge.eval_r("#{var_name} <- rlang::expr(#{parsed})")
  R::Object.build(var_name)
end

.install_and_loads(*libs, install_timeout_sec: nil, callback_timeout_ms: nil, bridge_timeout_sec: nil) ⇒ Object





203
204
205
206
207
# File 'lib/R_interface/r.rb', line 203

def self.install_and_loads(*libs, install_timeout_sec: nil, callback_timeout_ms: nil, bridge_timeout_sec: nil)
  R.install_rlibs(*libs, install_timeout_sec: install_timeout_sec, callback_timeout_ms: callback_timeout_ms, bridge_timeout_sec: bridge_timeout_sec)
  # Use library() so load failures throw immediately with a clear R error
  libs.each { |lib| R.library(lib) }
end

.install_rlibs(*libs, install_timeout_sec: nil, callback_timeout_ms: nil, bridge_timeout_sec: nil) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/R_interface/r.rb', line 135

def self.install_rlibs(*libs, install_timeout_sec: nil, callback_timeout_ms: nil, bridge_timeout_sec: nil)
  with_callback_timeout_ms(callback_timeout_ms) do

    # Use a fixed local library directory and ensure R can see it
    lib_dir = File.expand_path("~/R/x86_64-pc-linux-gnu-library/galaaz")
    FileUtils.mkdir_p(lib_dir) unless Dir.exist?(lib_dir)
    R.bridge.eval_r(".libPaths(c('#{lib_dir.gsub("'", "\\\\'")}', .libPaths()))", timeout: bridge_timeout_sec)

    packages = R.c(*libs)

  # installed.packages() returns a matrix; package names are in the row names.
  # Using [:all, "Package"] on this matrix ends up calling [[ with a missing
  # row subscript in R, which raises "missing subscript". Instead, rely on
  # the row names vector for the list of installed packages.
  installed_mat   = R.installed__packages(nil)
  installed_names = R.rownames(installed_mat)

  new_packages = packages[!(packages._ :in, installed_names)]
  new_packages_str = new_packages.to_s

    if(new_packages.length > 0 && new_packages_str != "character(0)")
    $stderr.puts "[RUBY] The following packages are missing and will be installed: #{new_packages_str}"
    $stderr.puts "[RUBY] Installing to: #{lib_dir}"
    
    # Get packages as Ruby array using :native mode
    pkg_list = []
    new_packages.each(:native) { |pkg| pkg_list << pkg }
    $stderr.puts "[RUBY] Package list: #{pkg_list.inspect}"
    
    # Install each package
    pkg_list.each do |pkg|
      $stderr.puts "[RUBY] Installing #{pkg}..."
      if install_timeout_sec
        timeout_i = Integer(install_timeout_sec)
        R.bridge.eval_r("options(timeout=#{timeout_i})", timeout: bridge_timeout_sec)
      end
      r_cmd = "install.packages('#{pkg.gsub("'", "\\\\'")}', repos='https://cloud.r-project.org', lib='#{lib_dir.gsub("'", "\\\\'")}', dependencies=NA)"
      $stderr.puts "[RUBY] R command: #{r_cmd}"
      result = R.bridge.eval_r(r_cmd, timeout: bridge_timeout_sec)
      $stderr.puts "[RUBY] Install output: #{result.inspect}"
      if result.to_s.include?("ANTICONF") || result.to_s.include?("Configuration failed")
        $stderr.puts "[RUBY] WARNING: Package #{pkg} failed to install due to missing system libraries."
        $stderr.puts "[RUBY] For kableExtra, you may need: libfontconfig1-dev libxml2-dev libfreetype6-dev"
      end
    ensure
      begin
        R.bridge.eval_r("options(timeout=60)", timeout: bridge_timeout_sec)
      rescue StandardError
        nil
      end
    end
    
    # Re-check installed packages after install attempt
    installed_mat = R.installed__packages(nil)
    installed_names = R.rownames(installed_mat)
    still_missing = packages[!(packages._ :in, installed_names)]
    if still_missing.length > 0
      raise "Failed to install packages: #{still_missing.to_s}. Check stderr output above for [RUBY] debug messages."
    end
    end

  end
end

.internal_eval(symbol, *args) ⇒ Object





87
88
89
# File 'lib/R_interface/r.rb', line 87

def self.internal_eval(symbol, *args)
  R::Support.process_missing(symbol, true, *args)
end

.method_missing(symbol, *args, &block) ⇒ Object


Executes a missing method. If a block is given, then the method needs to be executed in the scope of the block. @bug: Not ready yeat



72
73
74
75
76
77
78
79
80
81
# File 'lib/R_interface/r.rb', line 72

def self.method_missing(symbol, *args, &block)
  
  if (block_given?)
    val = R::Support.process_missing(symbol, false, *args)
    return R::Support.new_scope(symbol, val, *args, &block)
  end

  R::Support.process_missing(symbol, false, *args)

end

.with_callback_timeout_ms(callback_timeout_ms) ⇒ Object


Checks to see if the given libs are installed in R and if not, install them install

Parameters:

  • libs (Array)

    Array of strings with the names of the libraries to check and



125
126
127
128
129
130
131
132
133
# File 'lib/R_interface/r.rb', line 125

def self.with_callback_timeout_ms(callback_timeout_ms)
  return yield if callback_timeout_ms.nil?

  old = ENV['GALAAZ_CALLBACK_TIMEOUT_MS']
  ENV['GALAAZ_CALLBACK_TIMEOUT_MS'] = Integer(callback_timeout_ms).to_s
  yield
ensure
  ENV['GALAAZ_CALLBACK_TIMEOUT_MS'] = old unless callback_timeout_ms.nil?
end