Is it possible to access members directly by "." operator or in a cleaner way?
In many cases, I use R objects which is a R List and contains some other objects. If we want to access these elements, we need expr.AsList().["member"].AsNumeric() for example:
open RProvider.tseries
let x = R.rnorm(100)
let test = R.adf_test(x)
let p = test.AsList().["p.value"].AsNumeric().[0]
If we load libraries like fUnitRoots which returns S4 objects, we need to use other ways to extract the member, for example:
open RProvider.fUnitRoots
let x = R.rnorm(100)
let test = R.adfTest(x) // which returns a S4 object rather than a List object.
let p = test.GetAttribute("test").AsList().["p.value"].AsNumeric().[0]
If I define:
type SymbolicExpression with
/// Get the member symbolic expression of given name.
member this.Member(name: string) =
match this.Type with
| SymbolicExpressionType.List -> this.AsList().[name]
| SymbolicExpressionType.S4 -> this.GetAttribute(name)
| _ -> invalidOp "Unsupported operation on R object"
/// Get the value from a named vector by name.
member this.ValueOf<'a>(name: string) = this.AsVector().[name] :?> 'a
/// Get the value from an indexed vector by index.
member this.ValueAt<'a>(index: int) = this.AsVector().[index] :?> 'a
/// Get the first value of a vector.
member this.First<'a>() = this.ValueAt<'a>(0)
let inline (?) (expr:SymbolicExpression) (mem:string) =
expr.Member(mem)
This will allow me to write like this:
let x = R.rnorm(100)
let test = R.adf_test(x)
let stat:double = test?statistic.First()
let p:double = test?``p.value``.First()
for another,
let test = R.adfTest(x)
let stat:double = test?test?statistic.First()
let p:double = test?test?``p.value``.First()
which looks much cleaner if such a ? operator is defined.
My question is: Is there a better way to use more RProvider-way to access R object members (which does not bother too much about internal workings and operations of R objects but easy to operate and manipulate) rather than in a R.NET way?
I've thought the same things and wanted to define a ? operator. Problem is that if we define it as a standalone operator, I believe (not 100% sure) that it overrides all other ? operators. So it would be better to define it on the SymbolicExpression type itself (it cannot be done using extensions).
This can be done in C# as follows:
public static SymbolicExpression op_Dynamic<K>(SymbolicExpression sexp, string name) {
return ...;
}
public static void op_DynamicAssignment<K>(SymbolicExpression sexp, string name, dynamic value) {
}
I imagine the author would be amenable to such a pull-request. It would be super-useful!
@hmansell , do you have an example for what op_Dynamic<> could look like in C#, and what one of the unit tests of this function would be? I found very little documentation online on op_Dynamic overloads. TDD for these features would be a preferable approach. For cross-reference, this issue relates to R.NET issue
I can't point to any in any external code, but in some internal code we have the following (this is from a legacy C# DataFrame implementation):
public static Series<K, double> op_Dynamic<K>(DataFrame<K> frame, string name) {
return frame.GetSeries<double>(name);
}
Given a frame df this allows one to do let s = df?A to get out a series called A. For SymbolicExpressions it could allow you to get list entries by name or S4 slots by name.
You can also implement assignment similarly:
public static void op_DynamicAssignment<K>(DataFrame<K> frame, string name, dynamic value) {
// Dispatch using dynamic to resolve to an overload of AddSeries for specific
// concrete types (e.g. Series, IEnumerable<Task<T>> etc.)
frame.AddSeries(name, value, true);
}
This would allow you to set values in a list by name, e.g. list?foo <- 3.0
This would be nice feature to add. Marking as up-for-grabs.
There is more code & ideas here: https://github.com/BlueMountainCapital/FSharpRProvider/issues/50
It would be nice to get this into the core library. Although to do that, the additional code cannot use the R provider, which makes it a bit uglier (but still, I think it is reasonable to do that - the REnv type that provides access to environments does similar thing and calls the R interop directly: https://github.com/BlueMountainCapital/FSharpRProvider/blob/master/src/RProvider/RInterop.fs#L517)