5

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?

JacobVanEtten
  • 203
  • 1
  • 7

2 Answers2

2

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))
}
Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
  • Great example. The code gives a warning: In .checkFieldsInMethod(def, fieldNames, allMethods) : Local assignment to field name will not change the field: modelData <- data.frame(x = runif(10), y = runif(10)); visible(win1) <- TRUE; visible(win2) <- TRUE Did you mean to use "<<-"? ( in method "initialize" for class "SillyGui") – JacobVanEtten Jul 01 '13 at 15:04
  • 1
    @JacobVanEtten Thanks. I've fixed the `modelData <-` line. The `visible <-` lines should be local assignment. Wrap the call to `setRefClass` in `suppressWarnings` if it annoys you. – Richie Cotton Jul 01 '13 at 15:34
  • Thanks! Even though I will follow John´s suggestion first, it is great to have this example online. – JacobVanEtten Jul 01 '13 at 18:48
  • I checked it more thoroughly now. The model is not updating properly. It will generate a model when you hit the Make model button, but Make prediction will stick with this first model. Hitting the Make model button for the second time will not give a different outcome, though it should. – JacobVanEtten Jul 02 '13 at 00:07
  • 1
    @JacobVanEtten: If you want to use different data each time, then move `modelData <<- data.frame(x = runif(10), y = runif(10))` into the `makeModel` function. – Richie Cotton Jul 04 '13 at 09:26
2

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.

jverzani
  • 5,600
  • 2
  • 21
  • 17
  • Great example and I will try this first and take a look at `objectSignals`. `R CMD CHECK` will not complain about the visible bindings when using this approach? – JacobVanEtten Jul 01 '13 at 15:07
  • I just did a little test. I create an instance of a reference class in an .onLoad function of my package and then use it inside the functions of the package. This still produces a global bindings problem... There are of course dirty tricks to avoid this: [link](http://stackoverflow.com/questions/8096313/no-visible-binding-for-global-variable-note-in-r-cmd-check). But I thought references classes would make this unnecessary... – JacobVanEtten Jul 02 '13 at 00:28
  • You can create the instance in the code. See for example https://github.com/jverzani/gWidgets2RGtk2/blob/master/R/icons.R#L106 – jverzani Jul 02 '13 at 01:45
  • Yes, that will take away the NOTE for that function. But it is when calling it in another function that I will throw a NOTE... – JacobVanEtten Jul 02 '13 at 22:23