RCall: Mapping functions from the C API for R

Douglas Bates

February 24, 2015


In [1]:
using DataArrays,DataFrames,RCall

The file defining the C API for R is R.h in the directory whose name is ENV["R_INCLUDE_DIR"]. In my case this is


In [2]:
joinpath(ENV["R_INCLUDE_DIR"],"R.h")


Out[2]:
"/usr/share/R/include/R.h"

R.h causes inclusion of several other header files from that directory, the most important of which is Rinternals.h.

A typical function declaration in Rinternals.h is

Rboolean Rf_isVector(SEXP);

We can call this function directly from Julia using ccall. We define a Julia function isRVector to do this.


In [3]:
isRVector(s::SEXPREC) = ccall((:Rf_isVector,libR),Bool,(Ptr{Void},),s.p)


Out[3]:
isRVector (generic function with 1 method)

In [4]:
"library(robustbase)" |> rcopy


Out[4]:
8-element Array{ASCIIString,1}:
 "robustbase"
 "stats"     
 "graphics"  
 "grDevices" 
 "utils"     
 "datasets"  
 "methods"   
 "base"      

In [5]:
m1 = reval("m1 <- lmrob(Y ~ ., coleman, setting = 'KS2011')")


Out[5]:
VecSxp(-536870733,Ptr{Void} @0x000000000a4c0160,Ptr{Void} @0x000000000b8433c0,Ptr{Ptr{None}} @0x000000000b8433e8,22,0)

In [6]:
isRVector(m1)  # true because m1 is a list


Out[6]:
true

In [7]:
isRVector(m1[1]) # true because the coefficients are a numeric vector


Out[7]:
true

In [8]:
typeof(m1[18])  # the "call" component is a LANGSXP (function call)


Out[8]:
LangSxp (constructor with 2 methods)

In [9]:
isRVector(m1[18]) # a LANGSXP does not behave like a vector


Out[9]:
false

We can simplify many of the ccall expressions by creating a convert method to produce a Ptr{Void} from the Julia SEXP type

Base.convert(::Type{Ptr{Void}},s::SEXP) = s.p

In fact, because all these predicate functions have the same signature, we can (and do) define corresponding Julia functions through metaprogramming.

## predicates applied to an SEXP
for sym in (:isArray,:isComplex,:isEnvironment,:isExpression,
            :isFactor,:isFrame,:isFree,:isFunction,:isInteger,
            :isLanguage,:isList,:isLogical,:isSymbol,:isMatrix,
            :isNewList,:isNull,:isNumeric,:isNumber,:isObject,
            :isOrdered,:isPairList,:isPrimitive,:isReal,
            :isS4,:isString,:isTs,:isUnordered,:isUnsorted,
            :isUserBinop,:isValidString,:isValidStringF,
            :isVector,:isVectorAtomic,:isVectorizable,
            :isVectorList)
    @eval $sym(s::SEXP) =  ccall(($(string("Rf_",sym)),libR),Bool(Ptr{Void},),s)
end

Many of these functions; isComplex, isExpression, isList, isLogical, isNewList,isSymbol, isReal and isS4, are redundant because the Julia SEXP type is a templated type with the code built-in. At present only a few of these functions are exported.

Note that the meaning of Array is slightly different for R and for Julia. In Julia a vector is an Array. In R a vector is an Array only if it has a dim attribute.


In [10]:
RCall.isArray(m1[1])  # coefficients are a vector but not an R array


Out[10]:
false

In [11]:
RCall.isArray(m1[22]) # the model matrix is an array


Out[11]:
true

Creating and manipulating Julia SEXPREC objects

There are several methods for the sexp generic function that create a Julia SEXP from other types of Julia objects.

For example, an R symbol is retrieved (installing it in the symbol table if necessary) via a C function whose signature is

SEXP Rf_install(const char *);

We define an sexp method for the Symbol type in Julia as

function sexp(s::Symbol)
    sexp(ccall((:Rf_install,libR),Ptr{Void},(Ptr{Uint8},),
               string(s)))
end

(If you know about R's garbage collection and are wondering why the result is not protected, objects in the symbol table are never garbage collected.)


In [12]:
sexp(:foo)


Out[12]:
SymSxp(1,Ptr{Void} @0x000000000a36adf8,Ptr{Void} @0x000000000a4c0048,Ptr{Void} @0x000000000a4c0080,Ptr{Void} @0x0000000005406268,Ptr{Void} @0x000000000a36adc0,Ptr{Void} @0x000000000a36adf8)

In [13]:
rprint(ans)


foo