RProvider icon indicating copy to clipboard operation
RProvider copied to clipboard

Is it possible to access members directly by "." operator or in a cleaner way?

Open renkun-ken opened this issue 12 years ago • 5 comments

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?

renkun-ken avatar Dec 06 '13 02:12 renkun-ken

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 avatar Dec 09 '13 22:12 hmansell

@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

jmp75 avatar Apr 17 '14 23:04 jmp75

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

hmansell avatar Apr 23 '14 12:04 hmansell

This would be nice feature to add. Marking as up-for-grabs.

tpetricek avatar May 21 '14 17:05 tpetricek

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)

tpetricek avatar May 21 '14 21:05 tpetricek