I'm trying to store user input into a pre-existing dataframe in shiny.
In this simplified example I have a dataframe with a column labelled 'colour' and I want radio choice buttons to be able to assign values to each row in the dataframe, starting with row 1, then row 2 then row 3 etc. But it seems the dataframe always resets to the original value. What am I misunderstanding about shiny here?
library(shiny)
library(tidyverse)
library(DT)
dataframe <- tibble(row_id = c(1,2,3),
colour = c("","",""))
ui <- fluidPage(
radioButtons("colour",
"Pick a colour:",
choices = c("blue","green","red")),
actionButton("next_button", "Pick the next colour"),
DTOutput("dataframe")
)
server <- function(input, output) {
vals <- reactiveValues(active_row = 1)
observeEvent(input$next_button,{
dataframe[vals$active_row, "colour"] = input$colour
# view(dataframe)
# print(vals$active_row)
vals$active_row = vals$active_row + 1
output$dataframe <- renderDataTable(dataframe)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I would store your data also in reactiveValues. In addition, you can move your output outside of your observeEvent. This server function should work based on your description.
server <- function(input, output) {
vals <- reactiveValues(active_row = 1, data = dataframe)
observeEvent(input$next_button,{
vals$data[vals$active_row, "colour"] = input$colour
vals$active_row = vals$active_row + 1
})
output$dataframe <- renderDataTable(vals$data)
}
Related
I'm working on a shiny dashboard app, where i'm trying to add new rows to a table when clicking a button. When clicking, data is gathered from multiple input elements, collected in a list, which is then added as a new row. However, while the row is added, all previous rows become 'NA'.
Server code:
RowList <- c()
dfRowList <<- data.frame(matrix(ncol = 13, nrow = 0))
colnames(dfRowList) <<- c(# list of row properties #)
observeEvent(input$AddRow, {
Newrow <- paste0("R", length(RowList) + 1)
RowList <<- append(RowList, NewRow)
RProps <- c()
RProps <- c(NewRow)
for (prop in c(# list of row properties #)){
Propvalue <- input[[paste0("R", prop)]]
RProps <- append(RProps, Propvalue)
}
dfRowList[length(RowList),] <- RProps
output$RowList <- renderTable(dfRowList)
})
When using rbind() no new rows are created, just the 1 row is replaced by the new values and the column names are screwed over :/
I checked all other values. Creation of new row names, properties and lists works fine. When i use View(dfRowList) to look at the dataframe itself, it also shows the same problem (so its not a rendering problem). So, only adding the row does not work.
Does someone know what's going on here?
Thanks a lot in advance!
A minimal working example based on the repository already posted:
library(shiny)
library(data.table)
df = data.frame(Column1=character(), Column2=numeric())
ui <- fluidPage(
sidebarPanel(
textInput("input1", "First Input: (character)", "test"),
numericInput("input2", "Second Input: (numeric)", min = 1, max = 10, value = 1),
actionButton("add", "Add Data"),
),
mainPanel(
tabsetPanel(
tabPanel("table", value = 1, DT::dataTableOutput("showtable"))
)
)
)
server <- function(input, output) {
data_table <- reactiveVal(df)
observeEvent(input$add, {
t = rbind(data.frame(Column1 = input$input1,
Column2 = input$input2),data_table())
data_table(t)
})
output$showtable <- DT::renderDataTable({
data.table::data.table(data_table())
})
}
# Run the application
shinyApp(ui = ui, server = server)
I am trying to build a shiny app with rhandsontable in it. This rhandsontable is based on the datframe I create inside the app.
In the app I initially display the first row of this dataframe with al 3 columns. When the value of the 1st column is modified by the list of its dropdown levels and press search then the other 2 columns are modified.
I would like to do the same with the second column as well. Also I would like initially to display only the first 2 columns and the third will be displayed when the search button is pressed and of course if this row exists.
I tried to replicate what I did for the 1st column (commented code) but it does not work. The first 2 columns should always display all of their levels in the dropdown but the third only the available ones after the every search.
DF = data.frame(agency_postcode = factor(rep(c(12345,45678,24124,32525,32325),2)),
car_group=factor(rep(c("Microcar","City car","Supermini","Compact","SUV"),2)),
transmission=factor(rep(c("automatic","manual"),5)))
write.csv(DF,"C:/Users/User/Documents/Test//cars.csv", row.names = FALSE)
ui.r
library(shiny)
library(rhandsontable)
ui <- fluidPage(
titlePanel("RHandsontable"),
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File",
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv"),
actionButton("sr", "Search")
),
mainPanel(
rHandsontableOutput("test")
)
)
)
server.r
server <- function(input, output) {
# Assign value of 12345 as default to postcode for the default table rendering
values <- reactiveValues(postcode = "12345"
#car_group = "Microcar"
,tabledata = data.frame())
# An observer which will check the value assigned to postcode variable and create the sample dataframe
observeEvent(values$postcode,{
inFile <- input$file1
if (is.null(inFile))
return(NULL)
DF<- read.csv(inFile$datapath,stringsAsFactors = T)
for(i in 1:ncol(DF)){
DF[,i]<-as.factor(DF[,i])
}
DF
DF2 = data.frame(agency_postcode = factor(rep(c(12345,45678,24124,32525,32325),2)),
car_group=factor(rep(c("Microcar","City car","Supermini","Compact","SUV"),2)),
transmission=factor(rep(c("automatic","manual"),5)))
# Created dataframe is assigned to a reactive dataframe 'tabledata'
values$tabledata <- DF2[ which(DF2$agency_postcode ==values$postcode
#&DF2$car_group==values$car_group
), ]
for(i in 2:ncol(values$tabledata)){
values$tabledata[,i] <- factor(values$tabledata[,i])
}
})
# Capture changes made in the first column of table and assign the value to the postcode reactive variable. This would then trigger the previous observer
observeEvent(input$test$changes$changes,{
col <- input$test$changes$changes[[1]][[2]]
if(col==0){
values$postcode <- input$test$changes$changes[[1]][[4]]
#values$car_group<-input$test$changes$changes[[1]][[4]]
}
})
# Use the reactive df 'tabledata' to render.
output$test <- renderRHandsontable({input$sr
isolate(rhandsontable(values$tabledata[1,], rowHeaders = NULL, width = 550, height = 300)%>%
hot_col(colnames(values$tabledata)))
})
}
In the code that you have added for retrieving the value selected in second column, we would need to update something.
if(col==0){
values$postcode <- input$test$changes$changes[[1]][[4]]
#values$car_group<-input$test$changes$changes[[1]][[4]]
}
Index of handsontable starts with 0. So, its 0 for first column and 1 for second column, meaning you cannot update the values to car_group reactive variable within the if condition for the first column
A solution to your current question based on the answer that I provided here. Update rhandsontable by changing one cell value
library(shiny)
library(rhandsontable)
ui <- fluidPage(
titlePanel("RHandsontable"),
sidebarLayout(
sidebarPanel(),
mainPanel(
rHandsontableOutput("test")
)
)
)
server <- function(input, output) {
# Assigning blank values to reactive variable as all the values need to be listed first
values <- reactiveValues(postcode = "",cargroup = "",tabledata = data.frame())
observeEvent(values$postcode,{
DF2 = data.frame(agency_postcode = factor(rep(c(12345,45678,24124,32525,32325),2)),
car_group=factor(rep(c("Microcar","City car","Supermini","Compact","SUV"),2)),
transmission=factor(rep(c("automatic","manual"),5)))
# When the user selects any value from the dropdown, filter the table and update the value of reactive df
if(values$postcode!=""){
values$tabledata <- DF2[ which(DF2$agency_postcode ==values$postcode), ]
}else{
# When the postcode value is blank, meaning the user hasn't selected any, the table
# will render without the third column
values$tabledata <- DF2[,-3]
}
})
observeEvent(values$cargroup,{
DF2 = data.frame(agency_postcode = factor(rep(c(12345,45678,24124,32525,32325),2)),
car_group=factor(rep(c("Microcar","City car","Supermini","Compact","SUV"),2)),
transmission=factor(rep(c("automatic","manual"),5)))
values$tabledata <- DF2
# When the user selects any value from the dropdown, filter the table and update the value of reactive df
if(values$cargroup!=""){
values$tabledata <- DF2[ which(DF2$car_group ==values$cargroup), ]
}else{
# When the cargroup value is blank, meaning the user hasn't selected any, the table
# will render without the third column
values$tabledata <- DF2[,-3]
}
})
# Observer for changes made to the hot
observeEvent(input$test$changes$changes,{
col <- input$test$changes$changes[[1]][[2]]
# Changes made in first column
if(col==0){
values$postcode <- input$test$changes$changes[[1]][[4]]
}
# Changes made in second column
if(col==1){
values$cargroup <- input$test$changes$changes[[1]][[4]]
}
})
# Render the hot object
output$test <- renderRHandsontable({
rhandsontable(values$tabledata[1,], rowHeaders = NULL, width = 550, height = 300)%>%
hot_col(colnames(values$tabledata))
})
}
shinyApp(ui = ui, server = server)
Check if this suits your needs. You can then update the observer part based on search button instead of being reactive to the changes made by user.
Say I have a data frame called summarized which includes the columns TY_COMP and LY_COMP (among others). I could write a function in R that performs a calculation on TY_COMP and LY_COMP and creates a new column called cac in the data frame like this:
summarized$cac <- summarized$TY_COMP/summarized$LY_COMP-1
cac is now a new column in the summarized data frame.
Now say that summarized() is a reactive data frame with the same columns.
How could I achieve the effect done in the non-reactive data frame, i.e. create a new column within the current frame? Or how would I get the same effect?
I tried:
summarized$cac <- reactive({summarized()$TY_COMP/summarized()$LY_COMP-1})
I reckon you want to modify a reactive when for example an actionButton is clicked. For this purpose I would use reactiveValues. You can modify reactiveValue inside of observers such as observe or observeEvent.
Check out this simple example:
summarized <- data.frame(id = 1:20, group = letters[1:4], TY_COMP = runif(20), LY_COMP = runif(20))
library(shiny)
ui <- fluidPage(
verbatimTextOutput("text"),
actionButton("btn", "Add the 'cac' column to summarized")
)
server <- function(input, output){
rv <- reactiveValues(summarized = summarized)
output$text <- renderPrint(rv$summarized)
observeEvent(input$btn, {
rv$summarized$cac <- summarized$TY_COMP / summarized$LY_COMP - 1
})
summarized_mod <- reactive({
summarized()$TY_COMP / summarized()$LY_COMP-1
})
}
shinyApp(ui, server)
Another option would be to create another reactive that has an additional column. This is possible to use, but depending on your use case, I recommend the first solution.
Example:
summarized <- data.frame(id = 1:20, group = letters[1:4], TY_COMP = runif(20), LY_COMP = runif(20))
library(shiny)
ui <- fluidPage(
verbatimTextOutput("text1"),
verbatimTextOutput("text2")
)
server <- function(input, output){
output$text1 <- renderPrint(summarized_orig())
output$text2 <- renderPrint(summarized_mod())
summarized_orig <- reactive( {
summarized
})
summarized_mod <- reactive({
df <- summarized_orig()
df$cac <- summarized_orig()$TY_COMP / summarized_orig()$LY_COMP - 1
df
})
}
shinyApp(ui, server)
I want to delete the last row of a table using an action button. I have tried to follow this post Shiny: dynamically add/ remove textInput rows based on index
but I don't know how to apply the idea to my particular case.
A minimal reproducible example
library(shiny)
ui <- fluidPage(
sidebarPanel(numericInput("c1","Example", NA),
actionButton("update", "Update Table"),
br(), br(),
actionButton("reset", "Clear")
),
mainPanel( tableOutput("example")
)
)
server <- function(input, output, session) {
# stores the current data frame, called by values() and set by
values(new_data_table)
values <- reactiveVal(data.frame(A=1, B=2, C=3))
# update values table on button click
observeEvent(input$update,{
old_values <- values()
A_new <- input$c1
B_new <- A_new + 2
C_new <- A_new + B_new
new_values <- data.frame(A=A_new, B=B_new, C=C_new)
# attach the new line to the old data frame here:
new_df <- rbind(old_values, new_values)
#store the result in values variable
values(new_df)
#reset the numeric input to NA
updateNumericInput(session, "c1", "Example", NA)
})
#delete last row
deleteEntry <- observeEvent(input$reset,{
#....
})
#Print the content of values$df
output$example <- renderTable({ return(values()) })
}
shinyApp(ui = ui, server = server)
Actually I don't know how to call the last row of my interactive data frame. I have tried something like values() <- values[nrow(values())-1] but it doesn't work. Any suggestion?
EDITED
Following the suggestion below I have modified the deleteEntry function and now it works.
##delete last row
deleteEntry <- observeEvent(input$reset,{
values( values()[-nrow(values()),])
})
To remove the last row of a data.frame as a reactiveVal , use this syntax:
values(values()[-nrow(values()),])
I am trying to dynamically populate the values of the selectInput from the data file uploaded by the user. The selectInput must contain only numeric columns.
Here is my code snippet for server.R
...
idx <- sapply(data.file, is.numeric)
numeric_columns <- data.file[, idx]
factor_columns <- data.file[, !idx]
updateSelectInput(session, "bar_x", "Select1", choices = names(numeric_columns))
updateSelectInput(session, "bar_y", "Select2", choices = names(factor_columns))
...
Corresponding ui.r
...
selectInput("bar_x", "Select1", choices = NULL),
selectInput("bar_y", "Select2", choices = NULL)
...
The code works fine as long as there are more than one values in any dropdown. However, it fails as soon as it encounters only one value to be displayed in the selectInput.
How can I handle this specific condition, given that the data is uploaded and it cannot be controlled if there is just one column as numeric?
It appears that in 2019, this issue still exists. The issue that I have seen is that when there is only one option in the dropdown, the name of the column is displayed instead of the one option.
This appears to only be a graphical problem, as querying the value for the selectInput element returns the correct underlying data.
I was unable to figure out why this problem exists, but an easy way around this bug is to simply change the name of the column so that it looks like the first element in the list.
library(shiny)
ui <- fluidPage(
selectInput("siExample",
label = "Example Choices",
choices = list("Loading...")),
)
server <- function(input, output, session) {
# load some choices into a single column data frame
sampleSet <- data.frame(Example = c("test value"))
# rename the set if there is only one value
if (length(sampleSet$Example) == 1) {
# This should only be done on a copy of your original data,
# you don't want to accidentally mutate your original data set
names(sampleSet) <- c(sampleSet$Example[1])
}
# populate the dropdown with the sampleSet
updateSelectInput(session,
"siExample",
choices = sampleSet)
}
shinyApp(ui = ui, server = server)
Info: Code was adapted by OP to make error reproducible.
To solve your issue use val2 <- val[,idx, drop = FALSE]
You dropped the column names by subsetting the data.frame().
To avoid this use drop = FALSE; see Keep column name when select one column from a data frame/matrix in R.
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
# drj's changes START block 1
#selectInput('states', 'Select states', choices = c(1,2,4))
selectInput('states', 'Select states', choices = NULL)
# drj's changes END block 1
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output, session) {
observe({
#drj's changes START block 2
#val <- c(1,2,3)
#names(val) <- c("a","b","c")
#updateSelectInput(session, 'states', 'Select states', choices = names(val[1]))
val <- as.data.frame(cbind(c("_1","_2","_3"), c(4, 4, 6)))
names(val) <- c("a","b")
val$b <- as.numeric(val$b)
idx <- sapply(val, is.numeric)
val2 <- val[,idx, drop = FALSE]
updateSelectInput(session, 'states', 'Select states', choices = names(val2))
#drj's changes END block 2
})
}
# Run the application
shinyApp(ui = ui, server = server)