I'm trying to learn how to use shiny modules to simplify a messy shiny app I have. The app currently reads in several data sets using a list of names like this:
dataSetsToLoad <- c("set1name", "set2name", "etc")
for (i in 1:length(dataSetsToLoad) {
dt <- readRDS(paste0(dataSetsToLoad[i], ".RDS")
assign(dataSetsToLoad[i], dt)
}
These end up in the global environment and are accessible to all my non-modularized code.
Following a code pattern from here, I'd like to modify the above to something like the following
stash = reactiveValues()
for (i in 1:length(dataSetsToLoad) {
stashVar <- paste0("stash$", dataSetsToLoad[i])
dt <- readRDS(paste0(dataSetsToLoad[i], ".RDS")
assign(stashVar, dt)
}
The summary question is how do I put the dt into the stash reactive with the dynamically created name in stashVar. A second question is whether there is any way to test this without actually running it in a shiny app.
You can do something like this. Store the dataframes in a list and then assign them in a loop to the reactiveValues().
dflist <- list(mtcars, airquality, mtcars)
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 3,
value = 3)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
values <- reactiveValues()
for(i in 1:length(dflist)) {
values[[paste0("df_", i)]] <- dflist[[i]]
}
observeEvent(input$bins, {
print(values$df_1)
print(values$df_2)
print(values$df_3)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Related
I developed a small shiny app:
app
The app plots the rain for stations that are chosen in the selectizeInput.
It goes to an external server for the data each time a station is add or removed.
At the moment, it fetches the data from an external server for all the stations regardless if they remain in the list or not. This adds time and computation that are not needed.
My question is how do I reduce the need to get data that is already present?
because I can't present the real app I created a reproducible app to illustrate my code flow:
#data
id <- as.numeric(1:26)
names(id) <- letters
#dataframe function
get.rain.data <- function(id){
print(id)
vec <- 1:100
id <- as.numeric(id)
print(id)
df <- do.call(rbind,lapply(id,function(i)
tibble(x=vec,y=vec*i+vec^2*i,
id=as.factor(rep(i,length(vec))))))
return(df)
}
#plot function
plot.rain <- function(df){
print(df)
p <- ggplot(df,aes(x=x,y=y,group=id))+
geom_line(aes(color=id),size=0.6)
ggplotly(p,height=700)
}
#### UI
ui <- fluidPage(
titlePanel(h1("Rain Intensities and Cumulative Rain")),
sidebarLayout(
sidebarPanel(
helpText("Check rain with info from
IMS.gov.il"),
selectizeInput("var", h3("Select station"),
choices = id,
multiple = T,
selected = 4)
),
mainPanel(
plotlyOutput("rain")
)
)
)
# Define server logic ----
server <- function(input, output) {
dataInput <- reactive({
get.rain.data(input$var)
})
output$rain <- renderPlotly({
req(input$var)
plot.rain(dataInput())
})
}
# Run the application
shinyApp(ui = ui, server = server)
You have the needed code. Everywhere you want to use results from input$var call DataInput() instead. By creating the reactive dataInput function, it will be called when the input$var is updated
# Define server logic ----
server <- function(input, output) {
dataInput <- reactive({
get.rain.data(input$var)
})
output$rain <- renderPlotly({
plot.rain(dataInput())
})
}
I think what you need is to cache values so that they are only queried once. You may want look at the memoise package the can automatically do this for you.
https://github.com/r-lib/memoise
This is my first attempt at creating a Shiny app so I wanted to do something very simple: use fileInput so the user can select an image on their computer and then renderImage to plot the image.
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("plot image"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
fileInput("image",
"Select your image:", placeholder = "No file selected")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("photo")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$photo <- renderImage({
img <- load.image(input$image)
plot(img)
})
}
# Run the application
shinyApp(ui = ui, server = server)
However, when I try to load an image with this I get an error: invalid filename argument.
Ultimately I would like to integrate selectInput the choices option would have a few default images as well as those the user uploaded with fileInput but I feel that I'm already getting ahead of myself on that.
UPDATE 1:
library(shiny)
library(ggplot2)
library(imager)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("plot images"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
fileInput("image",
"Select your image:", placeholder = "No file selected")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("photo")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$photo <- renderPlot({
# Ensure the values for 'image' are available
# If not, the operation is stopped by raising a "silent" exception
req(input$image)
# Get edges of image with imager::cannyEdges
img <- cannyEdges(input$image)
# img is now a logical array with 4 dimensions but we only want 2 - discard two of the dimensions
img <- img[, , 1, 1]
# Convert the matrix into a list of coordinates
coords <- which(img, arr.ind = T)
# Turn the coordinates into a dataframe
df <- data.frame(x = coords[, 1], y = coords[, 2])
# Plot the coordinates
ggplot(df, aes(x, -y)) +
geom_point()
})
}
# Run the application
shinyApp(ui = ui, server = server)
See ?fileInput. If "image" is the id, then the path to the uploaded file is input$image$datapath, not input$image.
So you can do something like:
output$photo <- renderImage({
req(input$image)
list(src = input$image$datapath, alt="alternative text")
})
and in ui,
imageOutput("photo")
I encountered the same issue and solved. the image can't be loaded is because the input$image doesn't just give the filename you want, but also give more information. That make it a named list rather than a single value. Maybe you can try as following in your first case.
server <- function(input, output) {
output$photo <- renderImage({
img <- load.image(input$image[[4]])
plot(img)
})
}
Hope it works.
In an R shiny app I'm attempting to render a varying number of tables based on user input. As an example, I've created the following app:
# ui.R
fluidPage(
numericInput("numeric.input", "Select Number of Tables:", 0, min = 0),
tableOutput("table")
)
# server.R
data(iris)
function(input, output) {
output$table <- renderTable({
head(iris)
})
}
What I'd like for this app to do is to generate a number of tables dependent on the value selected for numeric.input. Currently numeric.input doesn't influence the app and is only shown for example. If numeric.input is set to zero, I'd like for the app to display no copies of the table, if numeric.input is set to one, I'd like for the app to display one copy of the table, etc.
Is something like this possible in R Shiny?
I've solved the issue by using the R Shiny Gallery app on creating UI in a loop, but rendering the UI loop within the R Shiny server. The following code works correctly:
# ui.R
fluidPage(
numericInput("numeric.input", "Select Number of Tables:", 1, min = 1),
uiOutput("tables")
)
# server.R
data(iris)
function(input, output) {
# Rendering tables dependent on user input.
observeEvent(input$numeric.input, {
lapply(1:input$numeric.input, function(i) {
output[[paste0('table', i)]] <- renderTable({
head(iris)
})
})
})
# Rendering UI and outputtign tables dependent on user input.
output$tables <- renderUI({
lapply(1:input$numeric.input, function(i) {
uiOutput(paste0('table', i))
})
})
}
Your approach is simple and straightforward. Just putting out the usage of the insertUI and removeUI for this purpose based on the link provided in comments by #r2evans.
ui <- fluidPage(
numericInput("numericinput", "Select Number of Tables:", 0, min = 0),
tags$div(id = 'tabledisplay')
)
server <- function(input, output) {
inserted <- c()
observeEvent(input$numericinput, {
tablenum <- input$numericinput
id <- paste0('table', tablenum)
if (input$numericinput > length(inserted)) {
insertUI(selector = '#tabledisplay',
ui = tags$div(h4(
paste0("Table number ", input$numericinput)
), tags$p(renderTable({
head(iris)
})),
id = id))
inserted <<- c(id, inserted)
}
else {
inserted <- sort(inserted)
removeUI(selector = paste0('#', inserted[length(inserted)]))
inserted <<- inserted[-length(inserted)]
}
})
}
shinyApp(ui, server)
I am trying to run an R Script as a R Shiny App. From the ui, the user will upload a .csv file and input 4 numeric variables. These variables should be passed through the function and it will generate a final_table which should be displayed as output in the Shiny App. Currently, the variables are being passed through the function but not resulting in the final table. I am new with RShiny, appreciate your help in making this work.
my_function.R is the script file which contains the function my_function(). This in fact is a 500 line R script compressed into a function for ease of use.
my_function <- function(tbl_load, ts_freq, ts_start_yr, ts_start_month, seasonal_cat) {
..........
return(collect_ALL_final_fair)
}
ui.R
library(shiny)
fluidPage(
titlePanel("Elasticity Tool"),
sidebarLayout(
sidebarPanel(
fileInput('file1', 'Choose datatable csv file', accept=c('.csv')),
numericInput("ts_freq", "Time series frequency:", 52, min = 1, max = 100),
numericInput("ts_start_yr", "Starting year:", 2013, min = 1990, max = 2030),
numericInput("ts_start_month", "Starting month:", 3, min = 1, max = 12),
numericInput("seasonal_cat", "Seasonal Category", 0, min = 0, max = 1),
br(),
actionButton("goButton", label = "Run tool"),
br()
),
mainPanel(
tabsetPanel(type = 'tabs',
tabPanel("Output", tableOutput('contents2'))
)
)
)
)
server.R
library(shiny)
library(datasets)
source("my_function.R")
#packages
library("glmnet")
library(Matrix)
library(dplyr)
library(forecast)
library(zoo)
library(stats)
library(car)
options(scipen = 999)
shinyServer(function(input, output) {
observeEvent(input$goButton, {
tbl_load <- input$file1
ts_freq <- input$ts_freq
ts_start_yr <- input$ts_start_yr
ts_start_month <- input$ts_start_month
seasonal_cat <- input$seasonal_cat
output$contents2 <- renderDataTable({
my_function(tbl_load, ts_freq, ts_start_yr, ts_start_month, seasonal_cat)
})
})
})
It seems like your output-render pairs are mismatched. If you want to use a regular table, you should have:
# ui
tableOutput('contents2')
# server
output$contents2 <- renderTable({})
If you want to use a datatable, you should have:
# ui
DT::dataTableOutput('contents2')
# server
output$contents2 <- DT::renderDataTable({})
If you want to do this, make sure you've installed the DT package.
If this turns out to not be the only problem, include the function definition in the server.R file (after you load the appropriate libraries). If this fixes it, it's because you weren't sourcing the file correctly (maybe your path isn't right or something like that). If it still doesn't work, the problem is in your function itself.
Also, why aren't you passing the inputs directly to my_function? You should also avoid using an observer in this case. Instead you can use this pattern:
my_table <- eventReactive(input$goButton, {
my_function(input$file1, input$ts_freq, input$ts_start_yr,
input$ts_start_month, input$seasonal_cat)
})
output$contents2 <- renderTable({
my_table()
})
I understand that reactive values notifies any reactive functions that depend on that value as per the description here
based on this I wanted to make use of this property and create a for loop that assigns different values to my reactive values object, and in turn I am expecting another reactive function to re-execute itself as the reactive values are changing inside the for loop. Below is a simplified example of what i am trying to do:
This is the ui.R
library(shiny)
# Define UI
shinyUI(pageWithSidebar(
titlePanel("" ,"For loop with reactive values"),
# Application title
headerPanel(h5(textOutput("Dummy Example"))),
sidebarLayout(
#Sidebar
sidebarPanel(
textInput("URLtext", "Enter csv of urls", value = "", width = NULL, placeholder = "Input csv here"),
br()
),
# Main Panel
mainPanel(
h3(textOutput("caption"))
)
)
))
This is the server file:
library(shiny)
shinyServer(function(input, output) {
values = reactiveValues(a = character())
reactive({
url_df = read.table(input$URLtext)
for (i in 1:5){
values$a = as.character(url_df[i,1])
Sys.sleep(1)
}
})
output$caption <- renderText(values$a)
})
This does not give the expected result. Actually when I checked the content of values$a
it was null. Please help!
Rather than using a for loop, try using invalidateLater() with a step counter. Here's a working example that runs for me with an example csv found with a quick google search (first column is row index 1-100).
library(shiny)
# OP's ui code
ui <- pageWithSidebar(
titlePanel("" ,"For loop with reactive values"),
headerPanel(h5(textOutput("Dummy Example"))),
sidebarLayout(
sidebarPanel(
textInput("URLtext", "Enter csv of urls", value = "", width = NULL, placeholder = "Input csv here"),
br()
),
mainPanel(
h3(textOutput("caption"))
)
)
)
server <- function(input, output, session) {
# Index to count to count through rows
values = reactiveValues(idx = 0)
# Create a reactive data_frame to read in data from URL
url_df <- reactive({
url_df <- read.csv(input$URLtext)
})
# Reset counter (and url_df above) if the URL changes
observeEvent(input$URLtext, {values$idx = 0})
# Render output
output$caption <- renderText({
# If we have an input$URLtext
if (nchar(req(input$URLtext)) > 5) {
# Issue invalidation command and step values$idx
if (isolate(values$idx < nrow(url_df()))) {
invalidateLater(0, session)
isolate(values$idx <- values$idx + 1)
}
}
# Sleep 0.5-s, so OP can see what this is doing
Sys.sleep(0.5)
# Return row values$idx of column 1 of url_df
as.character(url_df()[values$idx, 1])
})
}
shinyApp(ui = ui, server = server)