Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using R reference classes to pass values from one window to another in a GUI

I am making a GUI in R using gWidgets. Until now I have been passing values from one window to another via the global environment. Using the global environment is simple to implement but not ideal. One problem is that R CMD check complains about lacking visible bindings for global variables.

As a solution to this problem, reference classes have been mentioned by several R programmers. But to understand how reference classes would work in this context, it would really help to have a simple example.

Let me give a silly GUI to work with. When the user hits the button of the first window, it puts the model m in the global environment. The second button gets m from the global environment and gives an output. When you hit the first button again, it will make a new model m and change the output of the second button. If you close the first window, the button in the second window will still work, because m is in the global environment.

library(gWidgets)
options(guiToolkit = "tcltk")

h1 <- function(h, ...){
  d1 <- data.frame(x=runif(10), y=runif(10))
  .GlobalEnv$m <- lm(x ~ y, data=d1)
}

g1 <- gbutton("1. Make model", 
  container=gwindow(), handler=h1)

h2 <- function(h, ...){
  d2 <- data.frame(y=(1:10)/10)
  p <- predict(.GlobalEnv$m, newdata=d2)
  print(p)
}

g2 <- gbutton("2. Make prediction", 
  container=gwindow(), handler=h2)

How can I use reference classes in this example?

like image 894
JacobVanEtten Avatar asked Oct 04 '22 16:10

JacobVanEtten


2 Answers

Call setRefClass, and include each widget and data value as a field. Widgets should have type ANY. Initialize those widgets in the initialize method, and outsource functionality to other methods. Create a function to wrap the creation of the class.

silly_gui_generator <- setRefClass(
  "SillyGui",
  fields = list(
    #widgets
    win1           = "ANY",
    win2           = "ANY",
    button1        = "ANY",
    button2        = "ANY",
    #data
    modelData      = "data.frame",
    predictionData = "data.frame",
    model          = "lm"
  ),
  methods = list(
    initialize = function(modelData = NULL)
    {
      if(is.null(modelData))
      {
        modelData <<- data.frame(x = runif(10), y = runif(10))
      }

      win1 <<- gwindow(visible = FALSE)
      win2 <<- gwindow(visible = FALSE)
      button1 <<- gbutton(
        "1. Make model", 
        container = win1, 
        handler   = function(h, ...)
        {          
          makeModel()
        }
      )
      button2 <<- gbutton(
        "2. Make prediction", 
        container = win2, 
        handler   = function(h, ...)
        {          
          print(predictModel())
        }
      )
      visible(win1) <- TRUE
      visible(win2) <- TRUE
    },
    makeModel = function()
    {
      model <<- lm(x ~ y, data = modelData)
    },
    predictModel = function()
    {
      predictionData <<- data.frame(y = (1:10) / 10)
      predict(model, newdata = predictionData)
    }
  )
)

generate_silly_gui <- function(modelData = NULL)
{
  invisible(silly_gui_generator$new(modelData = modelData))
}
like image 162
Richie Cotton Avatar answered Oct 12 '22 11:10

Richie Cotton


Richie's answer is one way to do it. It gives you a single object (the instance returned by generate_silly_gui that you can use to manipulate the model and the widgets used for the GUI. A good approach. The following is simpler, it just does the model and is simply a slight deviation from the code in the question. The use of reference classes here is more structured way of using an environment:

OurModel <- setRefClass("OurModel",
                       fields="m")

## a global
model_instance = OurModel$new(m=NULL)

Then just replace .GlobalEnv$m with model_instance$m in your code and run.

Using a reference class allows you do things like add getter's and setter's that also do other things and pushes you towards the model-view-controller style. The objectSignals package goes in that direction.

If your GUI gets more complicated you might want to decouple the two approaches.

like image 24
jverzani Avatar answered Oct 12 '22 10:10

jverzani