Rclient: LuaJIT Client for Rserve

A library that allows to execute arbitrary R programs within LuaJIT. Being the most widely used language for statistical computing, R features a huge number of scientific libraries. Of particular relevance is the possibility to leverage on the plotting capabilities of ggplot2 and similar libraries:


local R = require "rclient"
 
local r = R.connect()
 
-- Lua --> R:
r.myvec  = { 1,2,3 } -- Array.
r.mymat  = R.asmatrix({ {1,2,3},{4,5,6} })
r.mylist = R.aslist({ 7,8,9 }, { "a","b","c" })
r.mydf   = R.asdataframe({ 7,8,9 }, { "a","b","c" }, { "row1" })
 
-- Execute R commands and evaluate expression as in R interpreter:
r "myvec <- myvec^2"
r "myvec" --> [1] 1 4 9

-- R --> Lua:
local vec  = r.myvec
local mat  = r.mymat
local list = r.mylist
local df   = r.mydf
print(unpack(vec))    --> 1 4 9
print(unpack(mat[1])) --> 1 2 3
print(unpack(mat[2])) --> 4 5 6
print(unpack(list[0][1]))                 --> a b c
print(list[1][1], list[2][1], list[3][1]) --> 7 8 9
print(list.a[1],  list.b[1],  list.c[1] ) --> 7 8 9
print(unpack(df[0][1]))             --> a b c
print(unpack(df[0][2]))             --> row1
print(df[1][1], df[2][1], df[3][1]) --> 7 8 9
print(df.a[1],  df.b[1],  df.c[1] ) --> 7 8 9

RServe

RCLIENT implements a client for Rserve which is used to host a (local or remote) R session toward which a connection is established. The following R code, to be executed in a R session, installs Rserve and starts a local R session:


install.packages("Rserve")
library("Rserve")
Rserve(args="--no-save")

R Types Crash Course

In R almost every data structure is based on a R vector (also called generic vector). Conceptually a R vector is a sequence of elements of arbitrary type (including other R vectors). We say that a R object is atomic if it cannot be further decomposed into finer data structures. The following table lists the correspondences between R atomic type names and Lua type names:

R Atomic Type Lua Type
null nil
numeric number
integer number
complex cdata<complex>
logical boolean
character string

We say that a R object is a R array if it is a R vector and and all its elements are atomics of the same type. A R scalar is a R array of length 1. R objects can have associated attributes which store metadata information. A R matrix is a R array with an attribute identifying the dimensions. A R list is a R vector of R vectors with an attribute identifying the column names. A R data.frame is a R vector of R vectors sharing the same length with attributes identifying the column names and the row names. This library allows the exchange between Lua and R of the following R aggregate types:

R Aggregate Type
array
matrix
list
data.frame

NOTE: no nesting is allowed for any of the R aggregate types (for instance a list of lists is not supported).

NOTE: in the following we refer to "Lua table" for a generic Lua table, to "Lua table array" for a Lua table whose only keys are 1, ..., N and to "Lua array" for a Lua table array whose elements are of the same type and correspond to a R atomic. This last data structure is the one that corresponds to a R array. Whenever a Lua array is expected a Lua scalar corresponding to a R atomic can be passed instead and this is equivalent to passing a Lua array of length 1.

Data Exchange

Objects are transfered from Lua to R via set-variable statements. The Lua object on the right hand side must be either nil, or a Lua array, or the object returned by one of the following functions: asmatrix(), aslist(), asdataframe().

Objects are transfered from R to Lua via get-variable statements. The R object being indexed on the left hand side must be either a R atomic or a not nested R aggregate. A Lua table is always returned (unless the R object is null, in which case nil is returned) whose structure depends on the R type. The cases of R arrays and R matrices behave as expected: a Lua array or a Lua table array of Lua arrays is returned. Remember that R scalars really are R arrays of length 1. Otherwise:


local y = r.x -- r is a connection obtained via rclient.connect().

-- If x is a list with N columns:
y = { [0] = { [1] = column_names },
      [1] = column_1_elements,
      ...
      [N] = column_N_elements,
    }
y[column_name_1] = y[1]
...
y[column_name_N] = y[N]

-- If x is a data.frame with N columns:
y = { [0] = { [1] = column_names, [2] = row_names },
      [1] = column_1_elements,
      ...
      [N] = column_N_elements,
    }
y[column_name_1] = y[1]
...
y[column_name_N] = y[N]
-- Please notice that here strings are automatically interpreted as factors, in 
-- which case what is passed back to Lua are the numeric encodings (1, ..., N)
-- associated with such factors. If this behavior is undesired please use the 
-- "stringsAsFactors=FALSE" option when constructing data.frames on R side.

API

In the following r is a connection to a R instance started by Rserve.

rclient = require "rclient"

Returns the loaded module (no global variable is set).

r = rclient.connect(address = "localhost", port = 6311)

Opens a connection to an instance of R started by Rserve and returns a connection object. The defaults result in a connection to a local instance of R started by Rserve in its default configuration. If the environment variable LUA_EXEC is set (as it is the case if using ULua), the Rserve instance's working directory is set equal to it.

r(rcode, out = print)

Executes the string rcode and and passes the last result (if present) in string form to out. As out defaults to print this function mimics the R interpreter by default.

r[variable] = x -- set-variable

Sets the R variable named as variable (a string) to x.

y = rclient.asmatrix(x)

Returns an object which is interpreted on R side as a R matrix. The input x must be a Lua table array of Lua arrays sharing the same length and element type.

y = rclient.aslist(x, colnames = { "X1", ..., "XN" })

Returns an object which is interpreted on R side as a R list. The input x must be a Lua table array of Lua arrays. The input colnames, if present, must be a Lua table array of strings and have the same length of x.

y = rclient.asdataframe(x, colnames = { "X1", ..., "XN" }, rownames = { "1", ..., "M" })

Returns an object which is interpreted on R side as a data.frame. The input x must be a Lua table array of Lua arrays sharing the same length. The inputs colnames and rownames, if present, must be Lua table arrays of strings and have respectively the same length of x and of x[1].

x = r[variable] -- get-variable

Returns an object which corresponds to the R variable named as variable (a string).