Использование эталонных классов R для передачи значений из одного окна в другое в графическом интерфейсе

Я делаю графический интерфейс в R, используя gWidgets. До сих пор я передавал значения из одного окна в другое через глобальную среду. Использование глобальной среды просто в реализации, но не идеально. Одна проблема заключается в том, что R CMD check жалуется на отсутствие видимых привязок для глобальных переменных.

В качестве решения этой проблемы несколько R-программистов упомянули ссылочные классы. Но чтобы понять, как ссылочные классы будут работать в этом контексте, очень поможет простой пример.

Позвольте мне дать глупый графический интерфейс для работы. Когда пользователь нажимает кнопку первого окна, он помещает модель m в глобальную среду. Вторая кнопка получает m из глобальной среды и дает вывод. Когда вы снова нажмете первую кнопку, она создаст новую модель m и изменит вывод второй кнопки. Если вы закроете первое окно, кнопка во втором окне все равно будет работать, потому что m находится в глобальной среде.

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)

Как я могу использовать эталонные классы в этом примере?


person JacobVanEtten    schedule 30.06.2013    source источник


Ответы (2)


Вызовите setRefClass и включите каждый виджет и значение данных в качестве поля. Виджеты должны иметь тип ANY. Инициализируйте эти виджеты в методе initialize и передайте функциональность другим методам. Создайте функцию для переноса создания класса.

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))
}
person Richie Cotton    schedule 01.07.2013
comment
Отличный пример. Код выдает предупреждение: В .checkFieldsInMethod(def, fieldNames, allMethods): локальное присвоение имени поля не изменит поле: modelData ‹- data.frame(x = runif(10), y = runif(10)); видимый(win1) ‹- ИСТИНА; visible(win2) ‹- TRUE Вы хотели использовать ‹‹-? (в методе инициализации для класса SillyGui) - person JacobVanEtten; 01.07.2013
comment
@JacobVanEtten Спасибо. Я исправил строку modelData <-. Линии visible <- должны иметь местное назначение. Оберните вызов setRefClass в suppressWarnings, если он вас раздражает. - person Richie Cotton; 01.07.2013
comment
Спасибо! Несмотря на то, что сначала я последую предложению Джона, здорово иметь этот пример в сети. - person JacobVanEtten; 01.07.2013
comment
Сейчас проверил более тщательно. Модель не обновляется должным образом. Он сгенерирует модель, когда вы нажмете кнопку «Создать модель», но «Сделать прогноз» останется с этой первой моделью. Нажатие кнопки «Создать модель» во второй раз не даст другого результата, хотя и должно. - person JacobVanEtten; 02.07.2013
comment
@JacobVanEtten: Если вы хотите каждый раз использовать разные данные, переместите modelData <<- data.frame(x = runif(10), y = runif(10)) в функцию makeModel. - person Richie Cotton; 04.07.2013

Ответ Ричи - один из способов сделать это. Он дает вам один объект (экземпляр, возвращенный generate_silly_gui, который вы можете использовать для управления моделью и виджетами, используемыми для графического интерфейса. Хороший подход. Следующий вариант проще, он просто делает модель и это просто небольшое отклонение от кода в вопросе Использование ссылочных классов здесь является более структурированным способом использования среды:

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

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

Затем просто замените .GlobalEnv$m на model_instance$m в своем коде и запустите.

Использование ссылочного класса позволяет вам делать такие вещи, как добавление геттеров и сеттеров, которые также делают другие вещи и подталкивают вас к стилю модель-представление-контроллер. Пакет objectSignals идет в этом направлении.

Если ваш графический интерфейс становится более сложным, вы можете разделить два подхода.

person jverzani    schedule 01.07.2013
comment
Отличный пример, и я попробую сначала и взгляну на objectSignals. R CMD CHECK не будет жаловаться на видимые привязки при таком подходе? - person JacobVanEtten; 01.07.2013
comment
Я только что провел небольшой тест. Я создаю экземпляр эталонного класса в функции .onLoad моего пакета, а затем использую его внутри функций пакета. Это все еще создает глобальную проблему привязки... Конечно, есть грязные приемы, чтобы избежать этого: ссылка. Но я думал, что классы ссылок сделают это ненужным... - person JacobVanEtten; 02.07.2013
comment
Вы можете создать экземпляр в коде. См., например, github.com/jverzani/gWidgets2RGtk2/blob/master /R/icons.R#L106 - person jverzani; 02.07.2013
comment
Да, это уберет ПРИМЕЧАНИЕ для этой функции. Но именно при вызове его в другой функции я выкину ПРИМЕЧАНИЕ... - person JacobVanEtten; 03.07.2013