real.pl -- An interface to the R statistical software.

Introduction

This library enables the communication with an R process started as a shared library. Version 1, was the result of the efforts of two research groups that have worked in parallel. The syntactic emphasis on a minimalistic interface. Versions between 1.4 and 2.0, also work done by others particularly in interfacing to web applications. See credits for more details.

In the doc/ directory of the distribution there is user's guide, a published paper and html documentation from PlDoc. There is large number of examples in examples/for_real.pl.

By default when the library is loaded an R object is started which will serve the R commands. If current_prolog_flag(real_start,false) succeeds, the R object is not loaded and the user needs to issue r_start/0 to do that.

A single predicate (<-/2,<-/1) channels the bulk of the interactions between Prolog and R. In addition to using R as a shared library, real uses the c-interfaces of SWI/Yap and R to pass objects in both directions. The usual mode of operation is to load Prolog values on to R variables and then call R functions on these values. The return value of the called function can be either placed on R variable or passed back to Prolog. It has been tested extensively on current SWI and YAP on Linux machines but it should also compile and work on MS operating systems and Macs.

Since v1.1 Real supports threads for web services and v1.3 it supports running an R server in any thread, not just the main thread. The library now has the concept of a designated R server thread. By default, there is no designated server thread, and the evaluation/execution of R expressions/commands is done in the calling thread. This should be done in a single threaded way. A designated server thread can come into existence in one of three ways:

  1. By starting a dedicated server thread using r_start_server/0.
  2. By running r_thread_loop/1 in any thread. This will run until a message to quit the thread is received by executing r(r_thread_loop_stop) or <- r_thread_loop_stop in any thread.
  3. By running any goal G as r_call_as_server(G). While G is running, the thread that it is running in becomes the designated server thread, and G should call r_serve/0 periodically to answer any R requests that accumulate. While there is a designated server thread, a call to r/1, r/2, (<-)/1 or (<-)/2 in any thread results in the request being posted to the server thread and the current thread blocking until a reply is received. As of July 2016 SWI-Prolog also has an alternative pack (pack(rserve_client)) which works with Rserve and Swish.

The main modes for utilising the interface are

     <- +Rexpr
     <- +Rvar
Print  Rvar or evaluate expression Rexpr in R
     +Rvar   <- +PLdata
     +Rexpr  <- +PLdata
     -PLvar  <- +Rvar
     -PLvar  <- +Rexpr
     +Rexpr1 <- +Rexpr2

Pass Prolog data to R, pass R data to Prolog or assign an R expression to an assignable R expression.

Testing

There is a raft of examples packed in a sinlge file that test the library.

     ?- [pack(real/examples/for_real)].

     ?- for_real.

     ?- edit( pack(real/examples/for_real) ).

Syntax

There are syntactic conventions in R that make unparsable prolog code. Notably function and variable names are allowed to contain dots, square brackets are used to access parts of vectors and arrays and functions are allowed empty argument tuples. We have introduced relevant syntax which allows for easy transition between prolog and R. Prolog constructs are converted by the library as follows:

Data transfers

R vectors are mapped to prolog lists and matrices are mapped to nested lists. The convention works the other way around too.

There are two ways to pass prolog data to R. The more efficient one is by using

 Rvar <- PLdata

Where Pldata is one of the basic data types (number,boolean) a list or a c/n term. This transfers via C data between R and Prolog. In what follows atomic PLval data are simply considered as singleton lists. Flat Pldata lists are translated to R vectors and lists of one level of nesting to R matrices (which are 2 dimensional arrays in R parlance). The type of values of the vector or matrice is taken to be the type of the first data element of the Pldata according to the following :

Booleans are represented in prolog as true/false atoms. Currently arrays of aribtrary dimensions are not supported in the low-level interface. Note that in R a scalar is just a one element vector. When passing non-scalars the interface will assume the type of the object is that of the first scalar until it encounters something different. Real will currently re-start and repopulate partial integers for floats as illustrated below:

r <- [1,2,3].         % pass 1,2,3 to an R vector r
R <- r.               % pass contents of R vector r to Prolog variable R
R = [1, 2, 3].

i <- [1,2,3.1].       % r is now a vector of floats, rather than integers
I <- i.
I = [1.0, 2.0, 3.1].

However, not all possible "corrections" are currently supported. For instance,

?- c <- [a,b,c,1].
ERROR: real:set_r_variable/2: Type error: `boolean' expected, found `a'

In the data passing mode we map Prolog atoms to R strings-

?- x <- [abc,def].
true.

?- <- x.
[1] "abc" "def"
true.

?- X <- x.
X = [abc, def].

In addition, Prolog data can be passed through the expression mechanism. That is, data appearing in an arbitrary R expression will be parsed and be part of the long string that will be passed from Prolog to R for evaluation. This is only advisable for short data structures. For instance,

tut_4a :-
    state <- c(+"tas", +"sa",  +"qld", +"nsw", +"nsw"),
    <- state.

tut_4b :-
    state <- c(+tas, +sa,  +qld, +nsw, +nsw),
    <- state.

Through this interface it is more convenient to be explicit about R chars by Prolog prepending atoms or codes with + as in the above example.

The Prolog atoms '$NaN' and '' are passed to NA values in R. '$NaN' is the bidirectional value, '' is only understood in the Prolog -> R direction as it is useful for passing missing values from CSV read matrices.

nan_ex :-
    x <- [c(1,2,''),c(3,4,'$NaN')],
    X <- x,
    write( x(X) ), nl.

?- nan_ex.
x( [[1, 2, '$NaN'], [3, 4, '$NaN']] )

Other predicates

Use r_citation/2 to access publication information about the interface. Although the original name was R..eal, when citating please use Real as the name for this library.

The library listens to

?- debug(real).
?- nodebug(real).

Predicate <<-/2 is a shorthand that ensures that the R variable on the left is fresh/new at the time of call, and <<-/1 blanks R variable out (r_remove/1).

Examples


?- e <- numeric(.).
yes
?- e^[3] <- 17.
yes
?- e[3] <- 17.
yes
?- Z <- e.
Z = ['$NaN','$NaN',17.0]
?- e^[10] <- 12.
yes
?- Z <- e.
Z = ['$NaN','$NaN',17.0,'$NaN','$NaN','$NaN','$NaN','$NaN','$NaN',12.0]

rtest :-
     y <- rnorm(50),               % get 50 random samples from normal distribution
     <- y,                         % print the values via R
     x <- rnorm(y),                % get an equal number of normal samples
     <- x11(width=5,height=3.5),   % create a plotting window
     <- plot(x,y)                  % plot the two samples
     r_wait,                       % wait for user to hit Enter
     % <- dev..off(.).             % old syntax, still supported
     <- dev..off().                % close the plotting window. foo() now acceptable in supported Prologs

tut6 :-
     d <- outer(0:9, 0:9),
     fr <- table(outer(d, d, "-")),
     <- plot(as..numeric(names(fr)), fr, type="h", xlab="Determinant", ylab="Frequency").

tut4b :-
     state <- [tas,sa,qld,nsw,nsw,nt,wa],
     statef <- factor(state),
     incmeans <- tapply( c(60, 49, 40, 61, 64, 60, 59), statef, mean ),
     <- incmeans.

logical :-
     t <- [1,2,3,4,5,1],
     s <- t==1,
     <- s,
     S <- s,
     write( s(S) ), nl.

Info

author
- Nicos Angelopoulos
- Vitor Santos Costa
version
- 2:2:0, 2022/6/21, new_bins
See also
- http://stoics.org.uk/~nicos/sware/real
- ?- pack(real/examples/for_real), for_real
- pack(real/doc/real.html)
- pack(real/doc/guide.pdf)
- pack(real/doc/padl2013-real.pdf)
- http://www.r-project.org/
license
- MIT
 r_start is det
Start an R object. This is done automatically upon loading the library, except if current_prolog_flag( real_start, false) succeeds. Only 1 instance should be started per Prolog session. Calls to the predicate when the R object is loaded and connected to succeed silently but have no useful side-effects.
 r_started(-F:boolean) is det
Unifies F with true if R has been started or false if not.
 <- +Rvar
<- +Rexpr
If Rvar is an atom and a known R object, then print Rvar on R. Else treat the input as an R expression and pass it on R for interpretation. (Throws result away, if expression is not a <- expression itself).
 +Rexpr <- +PLdata
-PLvar <- +Rexpr
+Rexpr1 <- +Rexpr2
Pass Prolog data PLdata to Rvar. PLdata is a term that is one of: an atomic value, flat list or list of depth 2. This mode uses the C-interface to pass the value to an R variable.

Pass PLdata to an assignable R expression.

Pass Rvar to PLvar variable via the C-interface.

Evaluate Rexpr and store its return value to PLvar.

Pass Rexpr1 <- Rexpr2 to R.

Note that all Rexpr* are first processed as described in the section about syntax before passed to R. Real also looks into Rexpressions and passes embeded lists to hidden R variables in order to pass large data efficiently.

c/n terms are recognised as PLdata if and only if they contain basic data items in all their arguments that can be cast to a single data type. This builds on the c() function of R that is a basic data constructor. Currently c/n terms are not recognised within nested expressions. But a mechanism similar to the hidden variables for Prolog lists in expressions should be easy to implement.

 Rvar <<-
Nick name for r_remove( Rvar ).

See r_remove/1.

 +Rv <<- +Expr
True iff Rv is a undefined R variable and Rv <- Expr succeeds. If Rv is not an atom or if its an atom that corresponds to an R variable the predicate errors.

See r_new/1 for a predicate that fails instead in a similar context.

 ?- x <<- [1,2,3].
 true.

 ?- x <<- [1,2,3].
 ERROR: First argument of <<- exists as R variable: x.
 r(R)
Nickname for <-(R).
 r(?L, +R)
Nickname for <-(L,R).
 r_thread_loop is det
Starts a loop that serves R calls received from <-/1 and <-/2 calls from other threads. It can be run on any thread as long as no other thread is running an R serving thread. If there is, an exception is thrown. To stop it, query from any thread in the pool:
   <- r_thread_loop_stop.
 r_serve
Serves any R calls that are waiting on the thread queue. The queue is populated by calls to <-/1 and <-/2 that are called on other threads. The predicate succeeds if there are no calls in the queue.

This predicate must be called in the context of r_call_as_server/1; this is required to ensure that the current thread is designated as an R server thread, so that R evaluations from other threads are properly redirected to this thread.

throws
- real_error(no_server_thread) if no thread has been designated a server thread.
- real_error(thread_server_mismatch(T1,T2) if r_serve/0 is called on thread T1 but the designated server thread is T2.
 r_is_var(+Rvar)
True if Rvar is an atom and a known variable in the R environment.
 r_is_var(+Rvar, -RvarAtom)
True if Rvar is a term and a known variable in the R environment. RvarAtom is the atomic representation of the Rvar term.
 r_char(+Atomic, +RcharAtom)
Wrap an atomic value with double quotes so it can pass as an R char type. This is more or less obsolete. You can use +Atomic directly in R expressions.
 r_devoff
Close the current plot devise without any reporting. Short for <- invisible('dev.off'()').
 r_devoff_all
Close all open devices.
 r_wait
Currently only waiting for Return to be pressed.
 r_library(+Rlib)
Load Rlib while respecting prolog_flag/2 real_suppress_lib_messages.

By default and when the flag is not defined messages are suppressed by wrapping the call to R's suppressPackageStartupMessages().

If you want the messages, use

 ?- set_prolog_flag( real_suppress_lib_messages, false ).

The predicate first looks into all subdirs of R_LIB_REAL for Rlib, Rlib.r and Rlib.R which allows to use local implementations rather than library packages. This is useful if you have made changes to a publically available R package that has a single file entry point. You can then use the local version for your purposes but allow others to also use your Real code with the puablic R function without any changes to the interface calls. The usual scenario is that the local version has a couple of extra arguments that specialises usage. Interface predicates to the R package can happily thus work with either version.

For instance, assume file '/home/user/r/lib/pheatmap.r' is a local file that can be independently sourced and corrensponds to the main function file of R's package pheatmap. Then the following code will source the local copy rather than look for the package installed via R.

 ?- setenv( 'R_LIB_REAL', '/home/user/r/lib' ), debug(real), r_library(pheamap).
 % Sending to R: source("/home/nicos/islp/r/lib/pheatmap.R")

If you want to use locally installed packages include their root location to R_LIB_USER (as per R documentation).

Examples:

  ?- r_library( ggplot2 ).
  ?- r_library( "ggplot2" ).
  ?- r_library( [ggplot2,goProfiles] ).
  ?- debug( real ).
  ?- <- library("ggplot2").
  % Sending to R: suppressPackageStartupMessages(library(ggplot2))
  ?- set_prolog_flag( real_suppress_lib_messages, false ).
  ?- <- library("ggplot2").
  % Sending to R: library(ggplot2)

<- library(Rlib) also re-directs here. These are the best ways to include R libraries from within Real. Rlib is allowed to be atomic or a string, or a list of atoms each corresponding to an R library name.

 r_version(-Version, -Date, -Note)
Version and release Date (data(Y,M,D) term). Note is either a note or nickname for the release. In git development sources this is set to <Something>_dev.
 ?- r_version( V, D, N ).
 V = 2:2:0,
 D = date(2022, 6, 21),
 N = new_bins.
version
- 2:1:0, 2020/5/29, swi8_2
- 2:0:0, 2016/9/5, ijar
- 1:5:0, 2016/1/23, j_review
- 1:4:0, 2015/5/24, configurable
- 1:3:0, 2015/5/3, collaborative
- 1:2:0, 2015/1/2, regardless
- 1:1:0, 2013/3/24, thankless_task
- 1:0:0, 2013/12/6, sinter_class
- 0:1:2, 2013/11/3, the_stoic
- 0:1:0, 2012/12/26,oliebollen
 r_citation(-Atom, -Bibterm)
Although the original name was R..eal, when citating please use Real as the name for this library.

This predicate succeeds once for each publication related to this library. Atom is the atom representation % suitable for printing while Bibterm is a bibtex(Type,Key,Pairs) term of the same publication. Produces all related publications on backtracking.

 r_remove(Rvar)
Remove Rvar from R's workspace (<- remove(Rvar)).
 r_call(+Fun, +Opts)
Construct and possibly call an R function. Fun can be an atom or a compound, eg plot, or plot(width=3). The predicate also supports multiple output destinations.

Opts a single or list of the following:

Ropt = Rarg
=/2 terms in Opts are added to the function call
call(Call=true)
whether to call the constructed function
debug(Dbg=false)
turn on debug(real) and restore at end of call
fcall(Fcall)
returns the constructed Fcall
outputs(Outs=false)
a single or list of [false,x11,pdf] also terms of those (eg x11(width=7))
post_call(Post)
call this after the function call. this can be an arbitrary callable including another <-/2 or r_call/2
rmv(Rmv=false)
when Rvar is given, should it be removed from R workspace at end? (see r_remove/1)
rvar(Rvar)
when given call is expanded to Rvar <- Fcall, else <- Fcall is called
stem(Stem=real_plot)
stem to use for output files

Only the first Ropt=Rarg for each matching Ropt is used. This is also the case for =pairs in args of Func. These are pre-pended for the check, so they always have precedence.

 ?- r_call( plot([1,2,3]), [debug(true)]  ).
 ?- <- plot(c(1,2,3)) ++ debug(true).
 ?- <- plot(c(1,2,3)) ++ xlab=+an_xlab

Undocumented predicates

The following predicates are exported, but not or incorrectly documented.

 r_end
 r_start_server
 r_call_as_server(Arg1)