清理我的Shiny模块理解

huangapple go评论58阅读模式
英文:

cleaning up my understanding of Shiny modules

问题

我有一个项目,我想开始将我的Shiny应用程序中的代码模块化。我想理清几件事情。

1) 我可以毫无问题地将`UI`模块传递到用户界面,例如`ui_MODULE`和`ui_MODULE_LOCATIONS`。当我尝试将服务器模块传递到应用程序时 `server_location_dropdown_filter`,问题就出现了。**我该如何解决这个问题?**

2) 我的模块被称为 `ui_MODULE("myMODUEL")` 和 `ui_MODULE_LOCATIONS("myMODULE2")` - 我知道 `myMODULE` 和 `myMODULE2` 被传递给 `NS`,但我仍然在直觉上有点难以理解这部分如何工作 - 我以为我们可以通过再次调用它们来重用模块,但这次要传递一个新的`NS`到模块 - 例如,在应用程序中我传递 `ui_MODULE_LOCATIONS("USE_AGAIN")` - 但我并没有再次获得位置下拉菜单... **我该如何正确地重用这些模块?**

3) 我想更好地理解如何正确地将我的项目模块化。我在网上阅读了很多关于模块的内容,但我发现通过将其应用到自己的代码中学习会更容易一些。你能帮忙将以下的应用程序模块化吗?

(注:上述内容是代码的翻译,而不是实际执行的代码。)

英文:

I have a project and I would like to start modularising my code in my Shiny App. I would like to get my head around a few things regarding modules.

  1. I can pass the UI modules to the user interface without any issue ui_MODULE and ui_MODULE_LOCATIONS. My problem arises when I try to pass the server module to the App server_location_dropdown_filter. How can I fix this issue?

  2. My modules are called ui_MODULE("myMODUEL") and ui_MODULE_LOCATIONS("myMODULE2") - I know the myMODULE and myMODULE2 are passed to the NS but I am still having a little difficulty understanding how this part works intuitively - I thought we could reuse the modules by calling them again but this time passing a new NS to the module - i.e. in the App I pass ui_MODULE_LOCATIONS("USE_AGAIN") - but I do not get the location dropdowns again... How can I correctly re-use the modules?

  3. I would like to understand a bit better how I can correctly modularise my projects. I read a lot about modules online but I find it easier to learn by applying it to my own code. Can you help modularise the following App?

Shiny App:

library(bslib)
library(shiny)
library(dplyr)
library(tidyr)
library(reactable)

################ GENERATE DATA ##############################
# Define the US states and cities
us_states <- c("Alabama", "Alaska", "Arizona", "Arkansas", "California")
alabama_cities <- c("Birmingham", "Huntsville", "Mobile", "Montgomery", "Tuscaloosa")
alaska_cities <- c("Anchorage", "Fairbanks", "Juneau", "Sitka", "Wasilla")
arizona_cities <- c("Phoenix", "Tucson", "Mesa", "Chandler", "Scottsdale")
arkansas_cities <- c("Little Rock", "Fort Smith", "Fayetteville", "Springdale", "Jonesboro")
california_cities <- c("Los Angeles", "San Francisco", "San Diego", "San Jose", "Fresno")

# Set the seed for reproducibility
set.seed(123)

# Generate random price and quantity data
data <- tibble(
  state = sample(us_states, 5000, replace = TRUE),
  city = case_when(
    state == "Alabama" ~ sample(alabama_cities, 5000, replace = TRUE),
    state == "Alaska" ~ sample(alaska_cities, 5000, replace = TRUE),
    state == "Arizona" ~ sample(arizona_cities, 5000, replace = TRUE),
    state == "Arkansas" ~ sample(arkansas_cities, 5000, replace = TRUE),
    state == "California" ~ sample(california_cities, 5000, replace = TRUE)
  ),
  price = runif(5000, 10, 100),
  quantity = sample(1:10, 5000, replace = TRUE)
)
#############################################################

################# UI ###################

ui_MODULE <- function(id) {
  ns <- NS(id)

  tagList(
    tags$div(
      class = "panel-header",
      numericInput("price", label = "Price?", value = 10, min = 1, step = 500),
      numericInput("quantity", label = "Quantity?", value = 10000, min = 1, step = 50),
      sliderInput("discount", label = "Discount", min = 0, max = 0.25, post  = " %", value = 0.08, step = 0.001),
      actionButton("compute", "Compute!")

    )
  )
}

################# UI LOCATIONS ###############
ui_MODULE_LOCATIONS = function(id){
  ns <- NS(id)
  tagList(
    tags$div(
      class = "panel-header-locations",
      selectInput("stateSelect", label = "Select State Data", choices = c()),
      selectInput("citySelect", label = "Select City Variable", choices = c()),
    )
  )

}

################## SERVER MODUEL ############
# This doesn't work correctly...
server_location_dropdown_filter <- function(id){
  moduleServer(id, function(input, output, session){
    # Changes with changes in the slider input

    updatedTable = reactive(
      data %>%
        filter(state == input$stateSelect) %>%
        filter(city == input$citySelect)
    )

    output$table = DT::renderDT({
      updatedTable()
    })
  })
}

############### FUNCTIONS ###################

myFUNCTION = function(interest){
  data %>%
    mutate(
      p_q = price * quantity,
      someOtherCalc = p_q*interest
      )
}

# myFUNCTION(0.08)

#############################################


ui <- bootstrapPage(
  theme = bs_theme(version = 5, bootswatch = 'minty'),
  #titlePanel("Old Faithful Geyser Data"),

  navbarPage("App Title",
             tabPanel("Plot",
                      fluidPage(
                        fluidRow(
                          column(6,
                                 ##### UI #######
                                 ui_MODULE("myMODUEL")

                          ),
                          column(6,
                                 #### UI Locations ####
                                 ui_MODULE_LOCATIONS("myMODULE2")
                                 )
                        ),
                        fluidRow(
                          column(6,
                                 DT::DTOutput('table')
                                 ),
                          column(6,
                                 DT::DTOutput('newTableOUT')
                                 )
                        )
                      )
             ),
             tabPanel("use_UI_Again",
                      fluidPage(
                        fluidRow(
                          column(12,
                                 ui_MODULE_LOCATIONS("USE_AGAIN")
                                 )
                        )
                      )
                      )
  )
)

server <- function(input, output, session) {
  # (1) First observe the states unique values in the data
  observe({
    choices = data %>% select(state) %>% unique() %>% pull(state)

    updateSelectInput(
      session,
      "stateSelect",
      choices = choices
    )
  })

  ## (2) ## The cities are then updated to reflect the states selection
  observeEvent(input$stateSelect,{

    choices = data %>% filter(state == input$stateSelect) %>% select(city) %>% unique() %>% pull(city)

    updateSelectInput(
      session,
      "citySelect",
      choices = choices
    )
  })

  #server_location_dropdown_filter("myFILTERS") # here I can't get this working...so I have to run the updatedTable and output$table here
  updatedTable = reactive(
    data %>%
      filter(state == input$stateSelect) %>%
      filter(city == input$citySelect)
  )

  output$table = DT::renderDT({
    updatedTable()
  })

  # Now I want to update the table by using a function...

  newTable = reactive(
    updatedTable() %>%
      myFUNCTION(input$discount)
  )

  output$newTableOUT = DT::renderDT({
    newTable()
  })

}

shinyApp(ui = ui, server = server)

答案1

得分: 2

你有一些问题。首先,你需要在模块UI中为每个inputID使用命名空间(ns)。其次,你需要定义一个服务器模块来从ui_MODULE中返回输入变量。最后,如果你希望再次调用UI模块,你需要再次调用相应的服务器模块。

请尝试以下代码:

library(bslib)
library(shiny)
library(dplyr)
library(tidyr)
library(reactable)
library(DT)

################ 生成数据 ##############################
# 定义美国各州和城市
us_states <- c("Alabama", "Alaska", "Arizona", "Arkansas", "California")
alabama_cities <- c("Birmingham", "Huntsville", "Mobile", "Montgomery", "Tuscaloosa")
alaska_cities <- c("Anchorage", "Fairbanks", "Juneau", "Sitka", "Wasilla")
arizona_cities <- c("Phoenix", "Tucson", "Mesa", "Chandler", "Scottsdale")
arkansas_cities <- c("Little Rock", "Fort Smith", "Fayetteville", "Springdale", "Jonesboro")
california_cities <- c("Los Angeles", "San Francisco", "San Diego", "San Jose", "Fresno")

# 设置随机数种子以便可重复性
set.seed(123)

# 生成随机价格和数量数据
data <- tibble(
  state = sample(us_states, 5000, replace = TRUE),
  city = case_when(
    state == "Alabama" ~ sample(alabama_cities, 5000, replace = TRUE),
    state == "Alaska" ~ sample(alaska_cities, 5000, replace = TRUE),
    state == "Arizona" ~ sample(arizona_cities, 5000, replace = TRUE),
    state == "Arkansas" ~ sample(arkansas_cities, 5000, replace = TRUE),
    state == "California" ~ sample(california_cities, 5000, replace = TRUE)
  ),
  price = runif(5000, 10, 100),
  quantity = sample(1:10, 5000, replace = TRUE)
)
#############################################################

################# UI ###################

ui_MODULE <- function(id) {
  ns <- NS(id)
  
  tagList(
    tags$div(
      class = "panel-header",
      numericInput(ns("price"), label = "价格?", value = 10, min = 1, step = 500),
      numericInput(ns("quantity"), label = "数量?", value = 10000, min = 1, step = 50),
      sliderInput(ns("discount"), label = "折扣", min = 0, max = 0.25, post  = " %", value = 0.08, step = 0.001),
      actionButton(ns("compute"), "计算!")
    )
  )
}

server_module <- function(id){
  moduleServer(id, function(input, output, session){
    rv <- reactiveValues()
    observe({
      rv$price <- input$price
      rv$discount <- input$discount
    })
    return(rv)
  })
}

################# UI LOCATIONS ###############
ui_MODULE_LOCATIONS = function(id){
  ns <- NS(id)
  tagList(
    tags$div(
      class = "panel-header-locations",
      selectInput(ns("stateSelect"), label = "选择州数据", choices = c()),
      selectInput(ns("citySelect"), label = "选择城市变量", choices = c()),
    )
  )
}

################## SERVER MODULE ############
# 这个部分需要修复...
server_location_dropdown_filter <- function(id){
  moduleServer(id, function(input, output, session){
    # 随着滑块输入的更改而更改
    
    # (1) 首先观察数据中州的唯一值
    observe({
      choices = data %>% select(state) %>% unique() %>% pull(state)
      
      updateSelectInput(
        session,
        "stateSelect",
        choices = choices
      )
    })
    
    ## (2) ## 然后根据所选州更新城市
    observeEvent(input$stateSelect,{
      
      choices = data %>% filter(state == input$stateSelect) %>% select(city) %>% unique() %>% pull(city)
      
      updateSelectInput(
        session,
        "citySelect",
        choices = choices
      )
    })
    
    updatedTable = reactive(
      data %>%
        filter(state == input$stateSelect) %>%
        filter(city == input$citySelect)
    )
    
    return(updatedTable)

  })
}

############### 函数 ###################

myFUNCTION <- function(data, interest){  
  df <- data %>%
    dplyr::mutate(p_q = price * quantity)  %>%
    dplyr::mutate(
      someOtherCalc = p_q * interest
    )
  return(df)
}

# myFUNCTION(0.08)

###########################################

ui <- bootstrapPage(
  theme = bs_theme(version = 5, bootswatch = 'minty'),
  navbarPage("应用标题",
             tabPanel("绘图",
                      fluidPage(
                        fluidRow(
                          column(6,
                                 ##### UI #######
                                 ui_MODULE("myMODULE")
                                 
                          ),
                          column(6,
                                 #### UI Locations ####
                                 ui_MODULE_LOCATIONS("myMODULE2")
                          )
                        ),
                        fluidRow(
                          column(6,
                                 DT::DTOutput('table')
                          ),
                          column(6,
                                 DT::DTOutput('newTableOUT')
                          )
                        )
                      )
             ),
             tabPanel("再次使用_UI",
                      fluidPage(
                        fluidRow(
                          column(6,
                                 ui_MODULE("USE_AGAIN")
                          ),
                          column(6,
                                 ui_MODULE_LOCATIONS("USE_AGAIN2")
                          )
                        ),
                        fluidRow(
                          column(6,
                                 DT::DTOutput('table2')
                          ),
                          column(6,
                                 DT::DTOutput('newTableOUT2')
                          )
                        )
                      )
             )
  )
)

server <- function(input, output, session) {
  
  updatedTable <- server_location_dropdown_filter("myMODULE2")
  
  output$table = DT::renderDT({
    updatedTable()
  })
  
  values <- server_module("myMODULE")
  
  newTable = reactive({
    req(updatedTable(),values$discount)
    myFUNCTION(updatedTable(),values$discount)
  })

  output$newTableOUT = DT::renderDT({
    newTable()
  })
  
  ### 再次使用模块
  
  updatedTable2 <- server_location_dropdown_filter("USE_AGAIN2")
  output$table2 = DT::renderDT(updatedTable2())
  
  values2 <- server_module("USE_AGAIN")
  
  newTable2 = reactive({
    req(updatedTable2(),values2$discount)
    myFUNCTION(updatedTable2(),values2$discount)
  })
  
  output$newTableOUT2 = DT::renderDT(new

<details>
<summary>英文:</summary>

You have a few issues. First, you need to use namespace (`ns`) for each inputID in the module UIs. Second, you need to define a server module to return the input variables from `ui_MODULE`. Lastly, if you wish to call a ui module again, you need to call the corresponding server module again.

Try this



    library(bslib)
    library(shiny)
    library(dplyr)
    library(tidyr)
    library(reactable)
    library(DT)
    
    ################ GENERATE DATA ##############################
    # Define the US states and cities
    us_states &lt;- c(&quot;Alabama&quot;, &quot;Alaska&quot;, &quot;Arizona&quot;, &quot;Arkansas&quot;, &quot;California&quot;)
    alabama_cities &lt;- c(&quot;Birmingham&quot;, &quot;Huntsville&quot;, &quot;Mobile&quot;, &quot;Montgomery&quot;, &quot;Tuscaloosa&quot;)
    alaska_cities &lt;- c(&quot;Anchorage&quot;, &quot;Fairbanks&quot;, &quot;Juneau&quot;, &quot;Sitka&quot;, &quot;Wasilla&quot;)
    arizona_cities &lt;- c(&quot;Phoenix&quot;, &quot;Tucson&quot;, &quot;Mesa&quot;, &quot;Chandler&quot;, &quot;Scottsdale&quot;)
    arkansas_cities &lt;- c(&quot;Little Rock&quot;, &quot;Fort Smith&quot;, &quot;Fayetteville&quot;, &quot;Springdale&quot;, &quot;Jonesboro&quot;)
    california_cities &lt;- c(&quot;Los Angeles&quot;, &quot;San Francisco&quot;, &quot;San Diego&quot;, &quot;San Jose&quot;, &quot;Fresno&quot;)
    
    # Set the seed for reproducibility
    set.seed(123)
    
    # Generate random price and quantity data
    data &lt;- tibble(
      state = sample(us_states, 5000, replace = TRUE),
      city = case_when(
        state == &quot;Alabama&quot; ~ sample(alabama_cities, 5000, replace = TRUE),
        state == &quot;Alaska&quot; ~ sample(alaska_cities, 5000, replace = TRUE),
        state == &quot;Arizona&quot; ~ sample(arizona_cities, 5000, replace = TRUE),
        state == &quot;Arkansas&quot; ~ sample(arkansas_cities, 5000, replace = TRUE),
        state == &quot;California&quot; ~ sample(california_cities, 5000, replace = TRUE)
      ),
      price = runif(5000, 10, 100),
      quantity = sample(1:10, 5000, replace = TRUE)
    )
    #############################################################
    
    ################# UI ###################
    
    ui_MODULE &lt;- function(id) {
      ns &lt;- NS(id)
      
      tagList(
        tags$div(
          class = &quot;panel-header&quot;,
          numericInput(ns(&quot;price&quot;), label = &quot;Price?&quot;, value = 10, min = 1, step = 500),
          numericInput(ns(&quot;quantity&quot;), label = &quot;Quantity?&quot;, value = 10000, min = 1, step = 50),
          sliderInput(ns(&quot;discount&quot;), label = &quot;Discount&quot;, min = 0, max = 0.25, post  = &quot; %&quot;, value = 0.08, step = 0.001),
          actionButton(ns(&quot;compute&quot;), &quot;Compute!&quot;)
          
        )
      )
    }
    
    server_module &lt;- function(id){
      moduleServer(id, function(input, output, session){
        rv &lt;- reactiveValues()
        observe({
          rv$price &lt;- input$price
          rv$discount &lt;- input$discount
          
        })
        return(rv)
      })
    }
    
    ################# UI LOCATIONS ###############
    ui_MODULE_LOCATIONS = function(id){
      ns &lt;- NS(id)
      tagList(
        tags$div(
          class = &quot;panel-header-locations&quot;,
          selectInput(ns(&quot;stateSelect&quot;), label = &quot;Select State Data&quot;, choices = c()),
          selectInput(ns(&quot;citySelect&quot;), label = &quot;Select City Variable&quot;, choices = c()),
        )
      )
      
    }
    
    ################## SERVER MODUEL ############
    # This doesn&#39;t work correctly...
    server_location_dropdown_filter &lt;- function(id){
      moduleServer(id, function(input, output, session){
        # Changes with changes in the slider input
        
        # (1) First observe the states unique values in the data
        observe({
          choices = data %&gt;% select(state) %&gt;% unique() %&gt;% pull(state)
          
          updateSelectInput(
            session,
            &quot;stateSelect&quot;,
            choices = choices
          )
        })
        
        ## (2) ## The cities are then updated to reflect the states selection
        observeEvent(input$stateSelect,{
          
          choices = data %&gt;% filter(state == input$stateSelect) %&gt;% select(city) %&gt;% unique() %&gt;% pull(city)
          
          updateSelectInput(
            session,
            &quot;citySelect&quot;,
            choices = choices
          )
        })
        
        updatedTable = reactive(
          data %&gt;%
            filter(state == input$stateSelect) %&gt;%
            filter(city == input$citySelect)
        )
        
        return(updatedTable)
    
      })
    }
    
    ############### FUNCTIONS ###################
    
    myFUNCTION &lt;- function(data,interest){  
      df &lt;- data %&gt;%
        dplyr::mutate(p_q = price * quantity)  %&gt;% 
        dplyr::mutate(
          someOtherCalc = p_q * interest
        )
      return(df)
    }
    
    # myFUNCTION(0.08)
    
    #############################################
    
    
    ui &lt;- bootstrapPage(
      theme = bs_theme(version = 5, bootswatch = &#39;minty&#39;),
      #titlePanel(&quot;Old Faithful Geyser Data&quot;),
      
      navbarPage(&quot;App Title&quot;,
                 tabPanel(&quot;Plot&quot;,
                          fluidPage(
                            fluidRow(
                              column(6,
                                     ##### UI #######
                                     ui_MODULE(&quot;myMODULE&quot;)
                                     
                              ),
                              column(6,
                                     #### UI Locations ####
                                     ui_MODULE_LOCATIONS(&quot;myMODULE2&quot;)
                              )
                            ),
                            fluidRow(
                              column(6,
                                     DT::DTOutput(&#39;table&#39;)
                              ),
                              column(6,
                                     DT::DTOutput(&#39;newTableOUT&#39;)
                              )
                            )
                          )
                 ),
                 tabPanel(&quot;use_UI_Again&quot;,
                          fluidPage(
                            fluidRow(
                              column(6,
                                     ui_MODULE(&quot;USE_AGAIN&quot;)
                              ),
                              column(6,
                                     ui_MODULE_LOCATIONS(&quot;USE_AGAIN2&quot;)
                              )
                            ),
                            fluidRow(
                              column(6,
                                     DT::DTOutput(&#39;table2&#39;)
                              ),
                              column(6,
                                     DT::DTOutput(&#39;newTableOUT2&#39;)
                              )
                            )
                          )
                 )
      )
    )
    
    server &lt;- function(input, output, session) {
      
      updatedTable &lt;- server_location_dropdown_filter(&quot;myMODULE2&quot;)
      
      output$table = DT::renderDT({
        updatedTable()
      })
      
      values &lt;- server_module(&quot;myMODULE&quot;)
      
      newTable = reactive({
        req(updatedTable(),values$discount)
        myFUNCTION(updatedTable(),values$discount)
      })
    
      output$newTableOUT = DT::renderDT({
        newTable()
      })
      
      ### USE MODULES again
      
      updatedTable2 &lt;- server_location_dropdown_filter(&quot;USE_AGAIN2&quot;)
      output$table2 = DT::renderDT(updatedTable2())
      
      values2 &lt;- server_module(&quot;USE_AGAIN&quot;)
      
      newTable2 = reactive({
        req(updatedTable2(),values2$discount)
        myFUNCTION(updatedTable2(),values2$discount)
      })
      
      output$newTableOUT2 = DT::renderDT(newTable2())
    }
    
    shinyApp(ui = ui, server = server)



</details>



huangapple
  • 本文由 发表于 2023年2月19日 01:55:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/75495297.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定