fslaborg / RProvider

Access R packages from F#
http://fslab.org/RProvider/
Other
235 stars 69 forks source link

R.nls fitted values when no exception is thrown #120

Closed tinoswe closed 9 years ago

tinoswe commented 9 years ago

Hi all, I managed to handle R.nls exceptions and pass them on to an Excel spreadsheet using ExcelDNA with the following block of F# code:

let my_function()

    [...] //all needed operations done before calling R.nls: read in arrays, create a dataframe, initialize model parameters, define the non linear equation used as formula in nls... 

    let result = 

        try
            R.nls(namedParams [ "formula", box custom_formula; 
                                "start", box init_params;
                                "data", box dataset;
                                "control", box test_ctrl;]) |> ignore
        with
            | _ as exc -> raise (InvalidOperationException exc.Message) 

    0.0 //returned value

In Excel a MessageBox will contain the exception string and this is exactly what I want. And it works fine so far. However I would like my function to return the array "fitted_values" (instead of 0.0) when available (i.e. when no exception is thrown) using:

let R_fitted_values = R.fitted(result)
let fitted_values = R_fitted_values.AsNumeric().GetValue<float[]>()

Since I am not an F# expert could you please help me with this?

hmansell commented 9 years ago

Since your code sample is not a self-contained runnable example it is hard for me to see what the issue is. Which part of it is failing? Does it fail if you do this in a standalone script/executable? Is this an issue with calling R or returning it to Excel via ExcelDNA?

tinoswe commented 9 years ago

Hi hmanselll. I'll post a working example later today. Thanks.

tinoswe commented 9 years ago

Hi again, below you find a simplified working example (just copy and paste as an .fsx. I am using VS2012).

Just to be clear: everything is working fine with excelDNA and the current version of the code does what I want. But I need to modify the current implementation of the function so that it returns "fitted_values" when no exception is thrown. If any exception is thrown it should throw the exception the same way as it does now.

So... the last 15 lines within the function body should be changed and I need some help with this. Thanks in advance.

#r @"packages\R.NET.Community.1.5.15\lib\net40\RDotNet.dll"
#r @"packages\R.NET.Community.FSharp.0.1.8\lib\net40\RDotNet.FSharp.dll"
#r @"packages\R.NET.Community.1.5.15\lib\net40\RDotNet.NativeLibrary.dll"
#r @"packages\RProvider.1.0.13\lib\net40\RProvider.dll"
#r @"packages\RProvider.1.0.13\lib\net40\RProvider.Runtime.dll"
open System
open System.Collections.Generic
open System.Data
open System.IO
open RDotNet
open RProvider
open RProvider.``base``
open RProvider.stats 
open RProvider.grDevices 
open RProvider.graphics
open RProvider.utils
type public heatflux_int_type = { Name:string; Chosen:string; Values:float []; }

let rnlm_evolution( (q_arr:float[]),
                    (b1:string),
                    (n1:string),
                    (arr1:float[]),
                    (b2:string),
                    (n2:string),
                    (arr2:float[]),
                    (b3:string),
                    (n3:string),
                    (arr3:float[])) = 

    let records = [ { Name = "Q"; Chosen = "Yes"; Values = q_arr }
                    { Name = n1;  Chosen = b1;    Values = arr1 }
                    { Name = n2;  Chosen = b2;    Values = arr2 }
                    { Name = n3;  Chosen = b3;    Values = arr3 } ] |> List.filter (fun x -> match x.Chosen with
                                                                                        | "Yes" -> true
                                                                                        | _     -> false)
    let dataset = 

            match records.Length with

            | 4 -> namedParams [records.[0].Name.Split('[').[0].Replace(" ",""), box records.[0].Values;
                                records.[1].Name.Split('[').[0].Replace(" ",""), box records.[1].Values;
                                records.[2].Name.Split('[').[0].Replace(" ",""), box records.[2].Values;
                                records.[3].Name.Split('[').[0].Replace(" ",""), box records.[3].Values;] |> R.data_frame

            | 3 -> namedParams [records.[0].Name.Split('[').[0].Replace(" ",""), box records.[0].Values;
                                records.[1].Name.Split('[').[0].Replace(" ",""), box records.[1].Values;
                                records.[2].Name.Split('[').[0].Replace(" ",""), box records.[2].Values;] |> R.data_frame

            | 2 -> namedParams [records.[0].Name.Split('[').[0].Replace(" ",""), box records.[0].Values;
                                records.[1].Name.Split('[').[0].Replace(" ",""), box records.[1].Values;] |> R.data_frame

    let coef_names = R.names(dataset).GetValue<string []>()
    let debug_coef_names = coef_names

    let custom_formula = 

        match coef_names.Length with

        | 4 -> R.paste( namedParams ["A", box coef_names.[0];
                                     "B", box "~";
                                     "C", box "p1 * (" ;
                                     "D", box coef_names.[1];
                                     "E", box "**p2)*(";
                                     "F", box coef_names.[2];
                                     "G", box "**p3)*(";
                                     "H", box coef_names.[3];
                                     "I", box "**p4)";];).GetValue<string>()

        | 3 -> R.paste( namedParams ["A", box coef_names.[0];
                                     "B", box "~";
                                     "C", box "p1 * (" ;
                                     "D", box coef_names.[1];
                                     "E", box "**p2)*(";
                                     "F", box coef_names.[2];
                                     "G", box "**p3)";];).GetValue<string>()

        | 2 -> R.paste( namedParams ["A", box coef_names.[0];
                                     "B", box "~";
                                     "C", box "p1 * (" ;
                                     "D", box coef_names.[1];
                                     "E", box "**p2)"];).GetValue<string>().Replace(" ","")

    let debug_custom_formula = custom_formula

    let init_params = 

        match coef_names.Length with
        | 4 -> namedParams [ "p1", 1000; "p2", 2; "p3", 2; "p4", 2] |> R.list
        | 3 -> namedParams [ "p1", 1000; "p2", 2; "p3", 2] |> R.list
        | 2 -> namedParams [ "p1", 1000; "p2", 1] |> R.list

    let ctrl_pars   = namedParams [ "maxiter", box 1000; 
                                    "tol", box 1e-5;   //"printEval", box true;
                                    "warnOnly", box true ] //|> R.list 

    let test_ctrl = R.nls_control(ctrl_pars)

    let result = 

        try
            R.nls(namedParams ["formula", box custom_formula; 
                               "start", box init_params;
                               "data", box dataset;
                               "control", box test_ctrl;]) |> ignore
        with
            | _ as exc -> raise (InvalidOperationException exc.Message) //printfn "Error 1: %s" exc.Message 

    //let result_fitted_values = R.fitted(result)
    //let fitted_values = result_fitted_values.AsNumeric().GetValue<float[]>()

    0.0 //I want to return fitted_values instead of 0.0 if no exception is thrown

//now I define input vectors
let SH = [| 28.0; 28.0; 28.0; 28.0; 31.0; 31.0; 31.0; 31.0; 31.0; 31.0 |]
let HF = [| 1398.31; 1319.65; 1385.41; 1376.9; 1175.89; 1191.41; 1198.86; 1209.61; 1197.23; 1328.33 |]
let CS = [| 0.602; 0.552; 0.6; 0.6; 0.6; 0.6; 0.6; 0.6; 0.6; 0.6 |]
let CE = [| 0.36; 0.36; 0.36; 0.36; 0.327; 0.327; 0.327; 0.327; 0.327; 0.327 |]

//no exception is thrown in this case. Function returns 0.0 as it should. But I would like to modify the function so that it returns the fitted_values array... and I don't know how to do this...
let outp1 = rnlm_evolution(HF, 
                            "Yes","SH", SH, 
                            "Yes", "CS", CS, 
                            "No", "CE", CE)

//R exception is thrown. Fine. I can handle it
let outp2 = rnlm_evolution(HF, 
                            "Yes","SH", SH, 
                            "Yes", "CS", CS, 
                            "Yes", "CE", CE)
tpetricek commented 9 years ago

Thanks for sending a full example!

The R.nls function returns an object of class "nls" and I don't think the R provider understands this, so it does not give you an easy way of extracting the information from the result out-of-the-box. What does the result actually represent (I'm not an R expert....) and what do you want to get from it?

I had a look at the samples in the docs and they, for example, use coef(fm1DNase1). You could do that in R provider too - when you get the result of R.nls, you could write:

let result = 
    try
        R.nls(namedParams ["formula", box custom_formula; 
                           "start", box init_params;
                           "data", box dataset;
                           "control", box test_ctrl;]) 
    with exc -> raise (InvalidOperationException exc.Message)

// Call the coef function and get the coefficients as a float array...
R.coef(result).GetValue<float[]>()
tinoswe commented 9 years ago

Hi, thanks for the reply. Your example illustrates very well what I am trying to do. What I am trying to get is

R.fitted(result).AsNumeric().GetValue<float[]>()

that is an array of float, i.e. an object that is identical to your:

R.coef(result).GetValue<float[]>()

but when trying to get the coefficients what I get is:

Error in coef.default() : argument "object" is missing, with no default
RDotNet.EvaluationException: Error in coef.default() : argument "object" is missing, with no default

   at RDotNet.REngine.Parse(String statement, StringBuilder incompleteStatement)
   at RDotNet.REngine.<Defer>d__0.MoveNext()
   at System.Linq.Enumerable.LastOrDefault[TSource](IEnumerable`1 source)
   at RDotNet.REngine.Evaluate(String statement)
   at RProvider.RInteropInternal.eval(String expr) in c:\Tomas\Public\FSharp.RProvider\src\RProvider\RInterop.fs:line 288
   at RProvider.RInterop.callFunc(String packageName, String funcName, IEnumerable`1 argsByName, Object[] varArgs) in c:\Tomas\Public\FSharp.RProvider\src\RProvider\RInterop.fs:line 458
   at RProvider.RInterop.call(String packageName, String funcName, String serializedRVal, Object[] namedArgs, Object[] varArgs) in c:\Tomas\Public\FSharp.RProvider\src\RProvider\RInterop.fs:line 489
   at FSI_0066.rnlm_evolution(Double[] q_arr, String b1, String n1, Double[] arr1, String b2, String n2, Double[] arr2, String b3, String n3, Double[] arr3) in C:\...\Script2.fsx:line 30
   at <StartupCode$FSI_0068>.$FSI_0068.main@() in C:\...\Script2.fsx:line 135
Stopped due to error

and I don't understand it...

tpetricek commented 9 years ago

I'm not entirely sure what is going wrong here - the following works fine on my side:

let result = 
    try
        R.nls(namedParams ["formula", box custom_formula; 
                           "start", box init_params;
                           "data", box dataset;
                           "control", box test_ctrl;]) 
    with exc -> raise (InvalidOperationException exc.Message) //printfn "Error 1: %s" exc.Message 

R.coef(result).GetValue<float[]>(),
R.fitted(result).GetValue<float[]>(),
R.fitted(result).AsNumeric().GetValue<float[]>()

The call to AsNumeric seemed to be the only difference between your & my code, but it has no effect...

tinoswe commented 9 years ago

ok, thanks for checking. Then I have to find out why it doesn't work for me...

tinoswe commented 9 years ago

I think I found out. You removed the "ignore" thing in the result calculation. If I do the same it works for me as well.

let result = 

        try
            R.nls(namedParams ["formula", box custom_formula; 
                               "start", box init_params;
                               "data", box dataset;
                               "control", box test_ctrl;]) //|> ignore
        with
            | _ as exc -> raise (InvalidOperationException exc.Message) //printfn "Error 1: %s" exc.Message 
tinoswe commented 9 years ago

Thanks guys. I close the thread.

tpetricek commented 9 years ago

Oh, yes, of course, ignore was ignoring the results! Glad you got it to work!