Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype ddl + tdata #140

Closed
wants to merge 19 commits into from
Closed

Prototype ddl + tdata #140

wants to merge 19 commits into from

Conversation

gogonzo
Copy link
Contributor

@gogonzo gogonzo commented Mar 15, 2023

This branch presents prototype of the new tdata object being a replacement to the TealData and friends. Look at the removed and added number of lines to see why ;)

This branch two independent issues are addressed.

  1. Easier way of specifying connectors
  2. Unifying data management across whole teal process

1. The DDL

New DDL object structure is a simple object containing:

  • code: for example "adsl <- adsl <- synthetic_cdisc_data({ arg })$adsl"
  • offline_args: named list where each element replaces { arg } in the RETURNED code.
  • ui: shiny ui object containing inputs which correspond to the { arg } element. In this case it suppose to be a textInput(id = "latest"). These inputs replace relevant { arg } in the EVALUATED code.
  • server: shiny server function which evaluates the code and return data. Server should:
    • evaluate the code with args taken from the input
    • pass evaluated env and code to the postprocess_fun
  • postprocess_fun: is the function which takes environment and code and creates a tdata object which is then passed further to teal.

It might seem that responsibilities of the server are diffficult, they are not. If you look at the username_password_server the code has only one simple eventReactive call, thanks to the ddl_run which wraps everything together and return what has been specified in the postprocess_fun. ddl_run automatically matches code, input and offline_args and executes postprocess_fun.

eventReactive(input$submit, {
      ddl_run(
        offline_args = offline_args, 
        code = code,
        postprocess_fun = postprocess_fun, 
        input = input
      )
})

custom example

Try simple example below. code and relevant ui are specified to evaluate the code. postprocess_fun returns simple list with data and code (normally we would return tdata). Example app utilizing ddl object just calls ui and server and displays server output (as ddl resolves code independently).

x <- ddl(
  code = '
  ADSL <- scda::synthetic_cdisc_data({ version })$adsl
  ADTTE <- scda::synthetic_cdisc_data({ version })$adtte
  ADRS <- scda::synthetic_cdisc_data({ version })$adrs
  ',
  
  offline_args = list(version = "i want to return this version in the code"),
  
  ui = function(id) {
    ns <- NS(id)
    tagList(
      textInput(ns("version"), label = "SCDA version", value = "latest"),
      actionButton(ns("submit"), label = "Submit")
    )
  },

  postprocess_fun = function(env_list, code) {
    list(data = env_list, code = code)
  }
)

app <- shinyApp(
  ui = fluidPage(
    fluidRow(
      column(3, h1("User Inputs"), x$ui(id = "custom_ui")),
      column(9, 
        h1("R code"), 
        verbatimTextOutput("code"),
        h1("R data"), 
        verbatimTextOutput("data")
      )
    )
  ),
  server = function(input, output, session) {
    loaded_data <- x$server(id = "custom_ui", x$offline_args, x$code, x$postprocess_fun)
    output$code <- renderPrint(cat(loaded_data()$code))
    output$data <- renderPrint(loaded_data()$data)
  }
)

shiny::runApp(app)

2. Standardize data flow in teal

In this prototype tdata is an S4 class inheriting from qenv. tdata has extra join_keys and datanames slots. Object is mutable - one can use eval_code function and it's reproducible as qenv. This object can be included in the teal_module modules and it could replace a qenv object.

tdata manipulation
library(teal.code)
library(teal.data)
library(scda)
adsl <- synthetic_cdisc_data("latest")$adsl
adtte <- synthetic_cdisc_data("latest")$adtte
tdata_obj <- cdisc_data(
  data = list(
    adsl = adsl,
    adtte = adtte
  ),
  code = '
  adsl <- synthetic_cdisc_data("latest")$adsl
  adtte <- synthetic_cdisc_data("latest")$adtte
  '
)
tdata_obj2 <- tdata_obj |> eval_code(quote(adsl <- filter(adsl, ARMCD == "ARM A")))
tdata_obj2[["adsl"]]
example custom ddl connection
x <- ddl(
  # code to be run when app user presses submit
  code = "
    conn <- open_dummy_conn(username = {username}, password = {password})
    my_data <- get_dummy_data(conn)
    close_dummy_conn()
  ",

  # arguments used for show R code
  offline_args = username_password_args(),

  # ui they wish to use for the loading data
  ui = username_password_ui,

  server = username_password_server,

  postprocess_fun = function(env_list, code) {
    cdisc_data(data = env_list, code = code)
  }
)

app <- shinyApp(
  ui = fluidPage(
    fluidRow(
      column(3, h1("User Inputs"), x$ui(id = "custom_ui")),
      column(9, h1("R code"), verbatimTextOutput("output"))
    )
  ),
  server = function(input, output, session) {
    loaded_data <- x$server(id = "custom_ui", x$offline_args, x$code, x$postprocess_fun)
    output$output <- renderPrint({
      req(loaded_data())
      loaded_data()
    })
  }
)

shiny::runApp(app)
example scda connector
x <- ddl(
  # code to be run when app user presses submit
  code = '
  ADSL <- scda::synthetic_cdisc_data({ version })$adsl
  ADTTE <- scda::synthetic_cdisc_data({ version })$adtte
  ADRS <- scda::synthetic_cdisc_data({ version })$adrs
  ',

  # ui they wish to use for the loading data
  ui = function(id) {
    ns <- NS(id)
    tagList(
      textInput(ns("version"), label = "SCDA version", value = "latest"),
      actionButton(ns("submit"), label = "Submit")
    )
  },

  postprocess_fun = function(env_list, code) {
    cdisc_data(data = env_list, code = code)
  }
)
app <- shinyApp(
  ui = fluidPage(
    fluidRow(
      column(3, h1("User Inputs"), x$ui(id = "custom_ui")),
      column(9, h1("R code"), verbatimTextOutput("output"))
    )
  ),
  server = function(input, output, session) {
    loaded_data <- x$server(id = "custom_ui", x$offline_args, x$code, x$postprocess_fun)
    output$output <- renderPrint({
      req(loaded_data())
      loaded_data()
    })
  }
)

shiny::runApp(app)

mhallal1 and others added 18 commits February 24, 2023 17:35
adsl <- synthetic_cdisc_data("latest")$adsl
adtte <- synthetic_cdisc_data("latest")$adtte
jk <- default_cdisc_join_keys(c("adsl", "adtte"))

tdata_obj <- new_tdata2(
  data = list(adsl = adsl, adtte = adtte),
  code = '
  adsl <- synthetic_cdisc_data("latest")$adsl
  adtte <- synthetic_cdisc_data("latest")$adtte
  ',
  jk 
)


tdata_obj2 <- eval_code(tdata_obj, quote(new <- data.frame(a = 1)))
tdata_obj2[["new"]]
tdata_obj2[["adsl"]]
get_code(tdata_obj2)
@gogonzo gogonzo added this to the Simplify TealData milestone Mar 15, 2023
Comment on lines +4 to +13
#' @export
setClass(
Class = "tdata",
contains = "qenv",
slots = c(join_keys = "JoinKeys", datanames = "character"),
prototype = list(
join_keys = join_keys(),
datanames = character(0)
)
)
Copy link
Contributor Author

@gogonzo gogonzo Jul 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ruckip imagine non-ddl cdisc_data could be just an extension of qenv. This class transports data, code and join_keys down to the modules.

@gogonzo
Copy link
Contributor Author

gogonzo commented Aug 15, 2023

Closing in favour of #161

@gogonzo gogonzo closed this Aug 15, 2023
@gogonzo gogonzo deleted the prototype2 branch August 15, 2023 07:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants