Passing Data Between F# and R

Passing Parameters

Parameter Passing Conventions

R supports various kinds of parameters, which we try to map onto equivalent F# parameter types:

  • All R formal parameters have names, and you can always pass their values either by name or positionally. If you pass by name, you can skip arguments in your actual argument list. We simply map these onto F# arguments, which you can also pass by name or positionally.
  • In R, essentially all arguments are optional (even if no default value is specified in the function argument list). It's up to the receiving function to determine whether to error if the value is missing. So we make all arguments optional.
  • R functions support ... (varargs/paramarray). We map this onto a .NET ParamArray, which allows an arbitrary number of arguments to be passed. However, there are a couple of kinks with this:

    • R allows named arguments to appear after the ... argument, whereas .NET requires the ParamArray argument to be at the end. Some R functions use this convention because their primary arguments are passed in the ... argument and the named arguments will sometimes be used to modify the behavior of the function. From the RProvider you will to supply values for the positional arguments before you can pass to the ... argument. If you don't want to supply a value to one of these arguments, you can explicitly pass System.Reflection.Missing.

    • Parameters passed to the R ... argument can also be passed using a name. Those names are accessible to the calling function. Example are list and dataframe construction (R.list, and R.data_frame). To pass arguments this way, you can use the overload of each function that takes an IDictionary, either directly, or using the namedParams function. For example:

      R.data_frame(namedParams [ "A", [|1;2;3|]; "B", [|4;5;6|] ])

Parameter Types

Since all arguments to functions are of type obj, it is not necessarily obvious what you can pass. Ultimately, you will need to know what the underlying function is expecting, but here is a table to help you. When reading this, remember that for most types, R supports only vector types. There are no scalar string, int, bool etc. types.

R TypeF#/.NET Type
characterstring or string[]
complexSystem.Numerics.Complex or Complex[]
integerint or int[]
logicalbool or bool[]
numericdouble or double[]
listCall R.list, passing the values as separate arguments
dataframeCall R.data_frame, passing column vectors in a dictionary

NB: For any input, you can also pass a SymbolicExpression instance you received as the result of calling another R function. Doing so it a very efficient way of passing data from one function to the next, since there is no marshalling between .NET and R types in that case.

Creating and passing an R function

R has some high-level functions (e.g. sapply) that require a function parameter. Although F# has first-class support of functional programming and provides better functionality and syntax for apply-like operations, which often makes it sub-optimal to call apply-like high-level functions in R, the need for parallel computing in R, which is not yet directly supported by F# parallelism to R functions, requires users to pass a function as parameter. Here is an example way to create and pass an R function:

let fun1 = R.eval(R.parse(text="function(i) {mean(rnorm(i))}"))
let nums = R.sapply(R.c(1,2,3),fun1)

The same usage also applies to parallel apply functions in parallel package.

Accessing results

Functions exposed by the RProvider return an instance of RDotNet.SymbolicExpression. This keeps all return data inside R data structures, so does not impose any data marshalling overhead. If you want to pass the value in as an argument to another R function, you can simply do so.

In order to access the result in .NET code, you have three routes:

Convert the data into a specified .NET type via GetValue()

RProvider adds a generic GetValue<'T> extension method to SymbolicExpression. This supports conversions from certain R values to specific .NET types. Here are the currently supported conversions:

R TypeRequested F#/.NET Type
character (when vector is length 1)string
characterstring[]
complex (when vector is length 1)Complex
complexComplex[]
integer (when vector is length 1)int
integerint[]
logical (when vector is length 1)bool
logicalbool[]
numeric (when vector is length 1)double
numericdouble[]

Custom conversions can be supported through plugins.

Convert the data into the default .NET type the .Value property

We also expose an extension property called Value that performs a default conversion of a SymbolicExpresion to a .NET type. These are the current conversions:

R TypeF#/.NET Type
characterstring[]
complexComplex[]
integerint[]
logicalbool[]
numericdouble[]

Again, custom conversions can be supported through plugins.

Explicitly access the data in the SymbolicExpression

If there are no supported conversions, you can access the data through the RDotNet object model. RDotNet exposes properties, members and extension members (available only if you open the RDotNet namespace) that allow you to access the underlying data directly. So, for example:

let res = R.sum([|1;2;3;4|])
if res.Type = RDotNet.Internals.SymbolicExpressionType.IntegerVector then res.AsInteger().[0]
else failwithf "Expecting a Numeric but got a %A" res.Type

To make this easier, we have defined some active patterns, under the RProvider.Helpers namespace, which is auto-opened when you open the RProvider namespace. These combine the type tests and conversion. An equivalent example:

match R.sum([|1;2;3;4|]) with 
| IntegerVector(iv) -> iv.[0]
| _                 -> failwithf "Expecting a Numeric but got a %A" res.Type

What if I commonly need an argument or result conversion that RProvider does not support?

If you believe the argument conversion is universally appropriate and should be available to everybody, please fork the repo and submit a pull request.

RProvider also supports custom conversions to/from your own data types using plugins.

namespace RDotNet
namespace RProvider
val fun1 : SymbolicExpression
type R = static member ! :?paramArray: obj [] -> SymbolicExpression + 2 overloads static member != :?paramArray: obj [] -> SymbolicExpression + 2 overloads static member !_hexmode :?a: obj -> SymbolicExpression + 2 overloads static member !_octmode :?a: obj -> SymbolicExpression + 2 overloads static member $ :?paramArray: obj [] -> SymbolicExpression + 2 overloads static member $<- :?paramArray: obj [] -> SymbolicExpression + 2 overloads static member $<-_data_frame :?x: obj *?name: obj *?value: obj -> SymbolicExpression + 2 overloads static member $_DLLInfo :?x: obj *?name: obj -> SymbolicExpression + 2 overloads static member $_package__version :?x: obj *?name: obj -> SymbolicExpression + 2 overloads static member %% :?paramArray: obj [] -> SymbolicExpression + 2 overloads ...
Base R functions.
R.eval(paramsByName: List<string * obj>) : SymbolicExpression
R.eval(paramsByName: System.Collections.Generic.IDictionary<string,obj>) : SymbolicExpression
R.eval(?expr: obj,?envir: obj,?enclos: obj) : SymbolicExpression
Evaluate an (Unevaluated) Expression
R.parse(paramsByName: List<string * obj>) : SymbolicExpression
R.parse(paramsByName: System.Collections.Generic.IDictionary<string,obj>) : SymbolicExpression
R.parse(?file: obj,?n: obj,?text: obj,?prompt: obj,?keep_source: obj,?srcfile: obj,?encoding: obj) : SymbolicExpression
Parse R Expressions
val nums : SymbolicExpression
R.sapply(paramsByName: List<string * obj>) : SymbolicExpression
R.sapply(paramsByName: System.Collections.Generic.IDictionary<string,obj>) : SymbolicExpression
R.sapply(?X: obj,?FUN: obj,?___: obj,?simplify: obj,?USE_NAMES: obj,?paramArray: obj []) : SymbolicExpression
No documentation available
R.c(?paramArray: obj []) : SymbolicExpression
Combine Values into a Vector or List
val res : SymbolicExpression
R.sum(paramsByName: List<string * obj>) : SymbolicExpression
R.sum(paramsByName: System.Collections.Generic.IDictionary<string,obj>) : SymbolicExpression
R.sum(?paramArray: obj []) : SymbolicExpression
Sum of Vector Elements
property SymbolicExpression.Type: Internals.SymbolicExpressionType with get
namespace RDotNet.Internals
type SymbolicExpressionType = | Null = 0 | Symbol = 1 | Pairlist = 2 | Closure = 3 | Environment = 4 | Promise = 5 | LanguageObject = 6 | SpecialFunction = 7 | BuiltinFunction = 8 | InternalCharacterString = 9 ...
field Internals.SymbolicExpressionType.IntegerVector: Internals.SymbolicExpressionType = 13
(extension) SymbolicExpression.AsInteger() : IntegerVector
val failwithf : format:Printf.StringFormat<'T,'Result> -> 'T
<summary>Print to a string buffer and raise an exception with the given result. Helper printers must return strings.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
Multiple items
active recognizer IntegerVector: SymbolicExpression -> IntegerVector option

--------------------
type IntegerVector = inherit Vector<int> new : engine: REngine * length: int -> unit + 3 overloads member CopyTo : destination: int [] * length: int *?sourceIndex: int *?destinationIndex: int -> unit member GetAltRepArray : unit -> int [] member GetArrayFast : unit -> int [] member GetValue : index: int -> int member GetValueAltRep : index: int -> int member SetValue : index: int * value: int -> unit member SetValueAltRep : index: int * value: int -> unit member SetVectorDirect : values: int [] -> unit ...

--------------------
IntegerVector(engine: REngine, length: int) : IntegerVector
IntegerVector(engine: REngine, vector: System.Collections.Generic.IEnumerable<int>) : IntegerVector
IntegerVector(engine: REngine, vector: int []) : IntegerVector
val iv : IntegerVector