I don't really know to hook up mutate with reactive when dealing with names of columns that are numerical.
I've got data that looks something like this:
df <- tibble(a=c("a", "b", "c"), `1990`=c(1,2,3), `2010`=c(3,2,1))
Everything looks fine when I do:
p <- df %>%
mutate(newvar = `1990`)
But I want to use it in my Shiny App such that newvar is assigned to the input from select list.
I created this reactive for that;
selectedyear <- reactive({
input$select
})
But now it doesn't seem to work:
p <- df %>%
mutate(newvar = selectedyear())
I tried different modifications, like:
p <- df %>%
mutate(newvar = `selectedyear()`)
but nothing seems to work for me.
The full code of the app:
library(shiny)
library(tibble)
library(dplyr)
df <- tibble(a=c("a", "b", "c"), `1990`=c(1,2,3), `2010`=c(3,2,1))
ui <- fluidPage(
selectInput("select", "Select:", c(1990, 2010)),
tableOutput("val")
)
server <- function(input, output) {
selectedyear <- reactive({
input$select
})
output$val <- renderTable({
p <- df %>%
mutate(temperature = selectedyear())
p
})
}
shinyApp(ui, server)
The same thing, but with characters as input is easy. Do you know some hack around this?
You can do
output$val <- renderTable({
p <- df %>%
mutate(temperature = !!selectedyear())
p
})
The problem seems to be the non-syntactic name of the columns 1990, 2010. The approach below should work. It uses eval/parse inside the right hand side of the mutate call. Note that you usually do not need to create reactives of (and with only) inputs, because each input is already reactive.
library(shiny)
library(tibble)
library(dplyr)
df <- tibble(a=c("a", "b", "c"), `1990`=c(1,2,3), `2010`=c(3,2,1))
ui <- fluidPage(
selectInput("select", "Select:", c(1990, 2010)),
tableOutput("val")
)
server <- function(input, output) {
output$val <- renderTable({
df %>%
mutate(temperature = eval(parse(text = paste0("`", input$select,"`"))))
})
}
shinyApp(ui, server)
If you are only interested in the temperature column and you do not need to show the rest of the data, then dplyr::select lets you access the input$select variable in a much more straightforward fashion:
output$val <- renderTable({
df %>%
select(temperature = input$select)
})
Related
I am writing a Shiny program which manipulates a dataset the user uploads.
The dataset has fixed column names and I create several UI elements (selectInputs) to filter that dataset.
Reprex looks like this:
ui <- fluidPage(
fluidRow(selectInput("filter_a","label",choices = c("a","b","c"),multiple = T),
selectInput("filter_b","label",choices = c("x","z","y"),multiple = T),
dataTableOutput("o1"),
br(),
dataTableOutput("o2")
)
)
server <- function(input, output) {
df <- reactive({
df <- data.frame(a = c("a","b","c"),
b = c("x","z","y"))
})
filter_function_1 <- reactive({
req(data)
df <- df()
if(!is.null(input$filter_a)){
df <- df %>%
filter(df$a %in% input$filter_a)
}
if(!is.null(input$filter_b)){
df <- df %>%
filter(df$b %in% input$filter_b)
}
return(df)
})
output$o1 <- renderDataTable({filter_function_1()})
While this works it looks like very bad practice. In my actual program I have a set of 14 filters and wrapping it 14 times and applying the same just doesnt look right to me.
Wanting to simplify I came up with this. I have a feeling that this is also not best practice (addressing the input$filter_a by concatenating strings doesnt seem right).
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("input$", filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
filter_function_2 <- reactive({
df <- df()
df <- df %>%
filter_func(arg="a") %>%
filter_func(arg="b")
return(df)
})
output$o2 <- renderDataTable({filter_function_2()})
}
Now, this looks cleaner to me, but I still want to modulize the code even more and have the filter function and code resign in a file. There are more data prep steps involved and I want to be able to debug them easily, hence the separate files / functions.
Code might look now like this:
filter_data.R
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("input$", filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
This is the point where it doesn't work anymore, since it can't find the input while in the function scope - that would be at least my best guess. I though of rewriting function in several ways, these are my ideas:
Have the filer_data.R function take in named arguments for all columns I want to filter. This seems straight-forward but also very redundant to me
Access shiny input variable on the server side, collect all "columns" that start with "filter_" and pass them onto the filter function. The filter function then applies the necessary filters.
I'm pretty sure I mess up somewhere, but I haven't been able to figure it out. What's not working here?
First, lets solve the problem how to call several filter in a row based on multiple inputs. We can use purrr:reduce2 for this:
In the example below reduce2 takes a custom function called myfilter with three arguments: the initial data.frame the column name and the value we want to filter. When calling reduce2 it is important to supply the data.frame to the .init argument.
library(shiny)
library(tidyverse)
myfilter <- function(df, col, vals) {
if(!is.null(vals)) {
filter(df, !!sym(col) %in% vals)
} else {
df
}
}
shinyApp(ui = fluidPage(
fluidRow(selectInput("filter_a","label", choices = c("a","b","c"), multiple = TRUE),
selectInput("filter_b","label", choices = c("x","z","y"), multiple = TRUE),
dataTableOutput("o1"),
br(),
dataTableOutput("o2")
)
),
server = function(input, output) {
df <- reactive({
df <- data.frame(a = c("a","b","c"),
b = c("x","z","y"))
})
filter_function_1 <- reactive({
req(data)
filter_ls <- map(set_names(grep("^filter", names(input), value = TRUE)), ~ input[[.x]])
col_nms <- gsub("^filter_", "", names(filter_ls))
reduce2(col_nms,
filter_ls,
myfilter,
.init = df())
})
output$o1 <- renderDataTable({filter_function_1()})
})
Then we could create a separate function filter_function_1 with two arguments: react_dat and input.
library(shiny)
library(tidyverse)
myfilter <- function(df, col, vals) {
if(!is.null(vals)) {
filter(df, !!sym(col) %in% vals)
} else {
df
}
}
filter_function_1 <- function(reac_dat, input) {
reactive({
filter_ls <- map(set_names(grep("^filter", names(input), value = TRUE)),
~ input[[.x]])
col_nms <- gsub("^filter_", "", names(filter_ls))
reduce2(col_nms,
filter_ls,
myfilter,
.init = reac_dat)
})
}
shinyApp(ui = fluidPage(
fluidRow(selectInput("filter_a","label", choices = c("a","b","c"), multiple = TRUE),
selectInput("filter_b","label", choices = c("x","z","y"), multiple = TRUE),
dataTableOutput("o1"),
br(),
dataTableOutput("o2")
)
),
server = function(input, output) {
df <- reactive({
df <- data.frame(a = c("a","b","c"),
b = c("x","z","y"))
})
filter_dat <- filter_function_1(df(), input = input)
output$o1 <- renderDataTable({filter_dat()})
})
And another way cleaning the code by putting it in external function / files would be to use shiny modules. There are many ways to set this up depending on how this module interacts with other parts of your app. One way of doing this is putting everything into the module:
library(shiny)
library(tidyverse)
myfilter <- function(df, col, vals) {
if(!is.null(vals)) {
filter(df, !!sym(col) %in% vals)
} else {
df
}
}
filterFunUI <- function(id) {
tagList(
fluidRow(selectInput(NS(id, "filter_a"),"label", choices = c("a","b","c"), multiple = TRUE),
selectInput(NS(id, "filter_b"),"label", choices = c("x","z","y"), multiple = TRUE),
dataTableOutput(NS(id, "o1")),
br(),
dataTableOutput(NS(id, "o2")))
)
}
filterFunServer <- function(id) {
moduleServer(id, function(input, output, session) {
df <- reactive({
df <- data.frame(a = c("a","b","c"),
b = c("x","z","y"))
})
filter_dat <- reactive({
filter_ls <- map(set_names(grep("^filter", names(input), value = TRUE)),
~ input[[.x]])
col_nms <- gsub("^filter_", "", names(filter_ls))
reduce2(col_nms,
filter_ls,
myfilter,
.init = df())
})
output$o1 <- renderDataTable({filter_dat()})
})
}
ui <- fluidPage(filterFunUI("first"))
server <- function(input, output, session) {
filterFunServer("first")
}
shinyApp(ui = ui, server = server)
I would like to update both a data.frame and a DT::datatable interactively when editing the datatable cells. This works fine but when I use the selectInput function to filter the data.frame and edit cells in another row of the datatable, it just copies the values I edited previously both in the data.frame and datatable. Any suggestions?
Below, is a reproducible example. I guess that this is an issue of reactivity. Being new to Shiny I am still far from mastering that.
library(tidyverse); library(DT); library(shiny)
df <- data.frame(internal_idNew=c(1, 2, 3, 4), col_1=c("this", "is", "a", "column"))
ui <- fluidPage(
#filter df
selectInput("s_internal_idNew", "Record id (new)", choices=c(1:nrow(df))),
#dt output
dataTableOutput("dt")
)
server <- function(input, output) {
#reactive df
df <- reactiveVal({df})
#reactive df filtered
df_showed <- reactiveVal({})
observeEvent(input$s_internal_idNew, {
#filter a row matching the internal id
df_showed(df() %>% filter(internal_idNew==input$s_internal_idNew))
#render dt
output$dt <- DT::renderDataTable(df_showed(), editable=list(target = "cell", disable = list(columns =c(0))), options=list(dom = 't', bSort=FALSE, pageLength=1), rownames = FALSE, selection = "none")
#create proxy dt
dt_proxy <- dataTableProxy("dt")
#edit dt
observeEvent(input$dt_cell_edit, {
this <- df()
showed <- df_showed()
#extract edited value to edit df
col_name <- showed %>% names() %>% .[input$dt_cell_edit$col+1]
row_name <- input$s_internal_idNew %>% as.numeric()
value_name <- coerceValue(input$dt_cell_edit$value, showed[row_name, col_name])
#store edited values in reactive df
this[row_name, col_name] <- value_name
df(this)
#replace data in datatable
replaceData(dt_proxy, df_showed(), resetPaging = TRUE, rownames = FALSE)
})
})
}
shinyApp(ui = ui, server = server)
A few modifications to achieve expected behavior :
dtProxy should be created only once at server launch
observeEvent(input$dt_cell_edit,...) should be independent of observeEvent(input$s_internal_idNew,...)
df_showed() should also be updated, as df()
library(tidyverse); library(DT); library(shiny)
df <- data.frame(internal_idNew=c(1, 2, 3, 4), col_1=c("this", "is", "a", "column"))
ui <- fluidPage(
#filter df
selectInput("s_internal_idNew", "Record id (new)", choices=c(1:nrow(df))),
#dt output
dataTableOutput("dt")
)
server <- function(input, output) {
#reactive df
df <- reactiveVal({df})
#reactive df filtered
df_showed <- reactiveVal({})
#create proxy dt once
dt_proxy <- dataTableProxy("dt")
observeEvent(input$s_internal_idNew, {
#filter a row matching the internal id
df_showed(df() %>% filter(internal_idNew==input$s_internal_idNew))
#render dt
output$dt <- DT::renderDataTable(df_showed(), editable=list(target = "cell", disable = list(columns =c(0))), options=list(dom = 't', bSort=FALSE, pageLength=1), rownames = FALSE, selection = "none")
})
#edit dt - separate from previous reactive
observeEvent(input$dt_cell_edit, {
this <- df()
showed <- df_showed()
#extract edited value to edit df
col_name <- showed %>% names() %>% .[input$dt_cell_edit$col+1]
row_name <- input$s_internal_idNew %>% as.numeric()
value_name <- coerceValue(input$dt_cell_edit$value, showed[row_name, col_name])
#store edited values in reactive df
this[row_name, col_name] <- value_name
df(this)
df_showed(this[row_name, ]) # Also updated
#replace data in datatable
replaceData(dt_proxy, df_showed(), resetPaging = TRUE, rownames = FALSE)
})
}
shinyApp(ui = ui, server = server)
I am trying to use auto generated selectInput IDs inside the reactive element or observe event. When I explicitly write the input IDs like input$dfSelect1,input$dfSelect2,input$dfSelect3, it works as I wanted.
Since I don't know in advance how many IDs will be there (data will be user input), I need to create same input ID strings as automated, but it doesn't recognize it as a trigger in observe event or a input data in reactive element.
Here is the minimal reproducible example of my problem. if you comment out the line 1 req(input$dfSelect1,input$dfSelect2,input$dfSelect3) and line 2 dfx <- data.frame(carb = c(input$dfSelect1,input$dfSelect2,input$dfSelect3),stringsAsFactors = F) and remove the comment from the following lines, this will be the case I am trying to do.
any idea how to pass these values?
library(dplyr)
library(DT)
exdata <- head(mtcars, 3)
exdata$ROWs <- row.names(exdata)
ui <- fluidPage(
headerPanel("Example"),
mainPanel(
uiOutput("selectionUI"),
uiOutput("tableOutput")
)
)
server <- function(input, output, server) {
### reqString result <- input$dfSelect1,input$dfSelect2,input$dfSelect3
reqString <- noquote(paste0(unlist(lapply(1:length(sort(unique(row.names(exdata)))),function(i) {paste0("input$dfSelect",i,"")})),collapse = ","))
values <- reactiveValues(
upload_state = NULL
)
observe({
### 1-USE the line below with reqString instead -doesn't work ##
req(input$dfSelect1,input$dfSelect2,input$dfSelect3)
# req(reqString)
values$upload_state <- 'uploaded'
})
output$selectionUI <- renderUI({
df <- sort(unique(row.names(exdata)))
wellPanel(
lapply(1:length(df), function(i) {selectizeInput(paste0("dfSelect",i,""),df[i],choices=c("", unique(exdata$carb)))})
)
})
completeTable <- reactive({
browser()
if (is.null(values$upload_state)) {
return(exdata)
}else if (values$upload_state == 'uploaded') {
### 2-USE the line below with reqString instead -doesn't work##
dfx <- data.frame(carb = c(input$dfSelect1,input$dfSelect2,input$dfSelect3),stringsAsFactors = F)
# dfx <- data.frame(carb = c(reqString),stringsAsFactors = F)
dfx <- data.frame(carb =as.numeric(unlist(dfx)))
dataJoin <- exdata %>% left_join(dfx,by=("carb"))
}
})
output$tableOutput <- renderUI({
DT::dataTableOutput("dataTableServer")
})
output$dataTableServer <- DT::renderDataTable({
DT::datatable(completeTable())
})
}
shinyApp(ui = ui, server = server)
You can index input using [[ instead of $:
sapply(1:length(sort(unique(row.names(exdata)))),
FUN=function(x) req(input[[paste0("dfSelect", x)]]))
and
l <- sapply(1:length(sort(unique(row.names(exdata)))),
FUN=function(x) input[[paste0("dfSelect", x)]])
dfx <- data.frame(carb = l,stringsAsFactors = F)
I am trying to build a shiny app that gives user the flexibility to choose the variables for group by and summarize. Checkbox will have an option for selecting group by variables. Right now I haven't given measure variables as selections, since I struggling with group by. I want the numbers to be aggregated basis the selection.
library(shiny)
library(ggplot2) # for the diamonds dataset
library(shinydashboard)
library(dplyr)
y1<-diamonds
ui <- fluidPage(
checkboxGroupInput("variable", "Variables to show:",
c("cut","color","clarity"),selected = "cut"),
tableOutput("data"),
textOutput("result")
)
server <- function(input, output, session) {
base <- reactive({
groupby <- enquo(input$variable)
print(groupby)
res <-y1%>% group_by(!!!groupby,x) %>%
tally() %>%
ungroup() %>%
summarise(sum = sum(x)) %>%
pull()
res
})
output$result <- renderText({
input$variable
})
output$data<-renderTable({
base()
}
)
}
shinyApp(ui, server)
Thanks,
Hema
I'm not sure that I understood your question exactly, but maybe something like this:
library(shiny)
library(ggplot2) # for the diamonds dataset
library(shinydashboard)
library(dplyr)
y1<-diamonds
ui <- fluidPage(
checkboxGroupInput("variable", "Variables to show:",
c("cut","color","clarity"),selected = "cut"),
tableOutput("data"),
textOutput("result")
)
server <- function(input, output, session) {
base <- reactive({
res <- y1 %>% group_by(eval(parse(text = input$variable)),x) %>%
tally() %>%
#ungroup() %>%
summarise(sum = sum(x)) %>%
pull()
res
})
output$result <- renderText({
input$variable
})
output$data<-renderTable({
base()
})
}
shinyApp(ui, server)
if it's possible to select multiple from the check boxes such that you'd want something like group_by(x,y) this may help you get what you want:
group_by(across(all_of(input$group)))
I am triyng to use a selectInput to subset a data.table to the selected column, preserving its name. So far I have done:
library(data.table)
mtcars <- data.table(mtcars)
ui <- bootstrapPage(
uiOutput('variables'),
tableOutput('table')
)
server <- function(input, output) {
output$variables<- renderUI ({
selectInput('var',
label = 'select Vars:',
choices = as.list(colnames(mtcars)),
multiple = F)
})
df <- reactive({
df <- mtcars[, list(var_name=get(input$var)), ]
})
output$table <- renderTable({head(df())})
}
shinyApp(ui = ui, server = server)
and the output is
But what I really wants is that the column name is the same as in the original df.
I have tried options with no success, like:
df <- mtcars[, list(input$var), ]
df <- mtcars[, list(paste0(input$var)=get(input$var)), ]
but neither gave me the desired output...
Any ideas ?
thanks in advance
Do you mean something like this? :
df <- reactive({
df <- mtcars[, list(var_name=get(input$var)), ]
colnames(df) <- input$var
df
})
Obviously you can then edit the colname to something else as well
You could re-assign the column name after you subset:
df <- reactive({
df <- mtcars[, list(var_name=get(input$var)), ]
colnames(df) <- input$var
return(df)
})