I have a fairly large simulation, that I currently run in Shiny using double for loop and it takes very long. I read about possibility of using foreach, but it does not work out, whatever I try, I and up in errors. Maybe some can spot the error and help me correct it?
app.R that runs (albeit very slowly (on real data) here with example data for reprex
require(shiny)
require(tidyverse)
require(foreach)
require(doMC)
registerDoMC()
options(cores = detectCores())
df <- data.frame(a=rnorm(n=26), b=1:26, c=100:125)
calc <- function(let=0.5, var1=0.1, var2=0.5){
df%>%
mutate(p1=ifelse(a<let,var1,0))%>%
mutate(p2=ifelse(a<let, var2,2))%>%
summarise(mean_b=mean(b*p1),
mean_c=mean(c*p2))
}
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Example"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput(inputId="selected_let",
label="LET",
value=0.5,
min=0,
max=1,
step=0.1),
submitButton("CALCULATE")
),
# Show a plot of the generated distribution
mainPanel(
h1(paste0("Table1")),
tableOutput("table_1"),
h1(paste0("Table2")),
tableOutput("table_2")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
data <- reactive({
data <- data.frame()
for (i in seq(0,1,by=0.1)) {
for (j in seq(0,1,by=0.1)) {
tmp <- calc(let = input$selected_let, var1 = i, var2 = j)
tmp_df <- data.frame(var1=i,
var2=j,
mean_b=tmp$mean_b,
mean_c=tmp$mean_c)
data <- rbind(data, tmp_df)
}
}
return(data)
})
output$table_1 <- renderTable({
data()%>%
select(var1,var2,mean_b)%>%
spread(var2, mean_b)
})
output$table_2 <- renderTable({
data()%>%
select(var1,var2,mean_c)%>%
spread(var2, mean_c)
})
}
# Run the application
shinyApp(ui = ui, server = server)
My goal was to change the data <-... part with foreach package and as my PC runs on UNIX I use the doMC.
to be replaced with:
data <- reactive({
foreach(i=rep(seq(0,1,by=0.1),each=11),
j=rep(seq(0,1,by=0.1),times=11),
.combine="rbind") %dopar% {
val <- calc(let=input$selected_let,
var1=i,
var2=j)
data.frame(var1=i,
var2=j,
mean_b=tmp$mean_b,
mean_c=tmp$mean_c)
}
})
But this ends up in permanent errors:
I tried to out require(dplyr) in the the server part, but that did not help either.
Any suggestions for solutions?
As stand alone, the foreach part runs well with let=0.5 as input, given its not in reactive
foreach(i=rep(seq(0,1,by=0.1),each=11),
j=rep(seq(0,1,by=0.1),times=11),
.combine="rbind") %dopar% {
val <- calc(let=0.5,
var1=i,
var2=j)
data.frame(var1=i,
var2=j,
mean_b=tmp$mean_b,
mean_c=tmp$mean_c)
}
Here is a way to avoid the double for-loop using library(data.table):
library(shiny)
library(data.table)
set.seed(0)
DF <- data.frame(a = rnorm(n = 26), b = 1:26, c = 100:125)
setDT(DF)
DT <- setDT(expand.grid(var1 = seq(0, 1, by = 0.1), var2 = seq(0, 1, by = 0.1)))
setorder(DT, var1, var2)
calc <- function(DF, let = 0.5, var1 = 0.1, var2 = 0.5) {
DF[, c("mean_b", "mean_c") := .(b * fifelse(a < let, var1, 0), c * fifelse(a < let, var2, 2))]
as.list(colMeans(DF[, .(mean_b, mean_c)]))
}
ui <- fluidPage(titlePanel("Example"),
sidebarLayout(
sidebarPanel(
sliderInput(
inputId = "selected_let",
label = "LET",
value = 0.5,
min = 0,
max = 1,
step = 0.1
),
submitButton("CALCULATE")
),
mainPanel(
h1(paste0("Table1")),
tableOutput("table_1"),
h1(paste0("Table2")),
tableOutput("table_2")
)
))
server <- function(input, output) {
data <- reactive({
DT[, c("mean_b", "mean_c") := calc(DF, let = input$selected_let, var1 = var1, var2 = var2), by = seq_len(NROW(DT))]
})
output$table_1 <- renderTable({
dcast(data(), var1 ~ var2, value.var = "mean_b")
})
output$table_2 <- renderTable({
dcast(data(), var1 ~ var2, value.var = "mean_c")
})
}
shinyApp(ui = ui, server = server)
Here you can find a benchmark taking into account dplyr and data.table (among others).
Related
In Shiny, rather than manually typing out each slider for filtering a dataframe, which is in reality much larger than this, I use the following code to dynamically produce sliders, each with the appropriate range for its column, through the use of a single functional (lapply):
library(shiny)
library(tidyverse)
dat <- data.frame(a = 0:10, b = 20:30, c = 80:90)
ui <- fluidPage(
titlePanel("Filter DF"),
mainPanel(
tableOutput("df"),
uiOutput("sliders")
)
)
server <- function(input, output) {
tmp_df <- reactive({
dat %>% filter(a > 5) # blah blah
})
output$df <- renderTable({
tmp_df()
})
output$sliders <- renderUI({
t <- tmp_df()
pvars <- names(t)
lapply(pvars, function(nm) {
min <- min(t[[nm]], na.rm = TRUE)
max <- max(t[[nm]], na.rm = TRUE)
sliderInput(inputId = paste0("range_", nm),
label = nm,
min = min,
max = max,
value = c(min, max))
})
})
}
shinyApp(ui = ui, server = server)
But I would also like to dynamically create the code which enables each slider to filter (using dplyr) the dataframe with each slider's current values.
How could I do this?
Thanks #Limey for the head's up.
Solution:
library(shiny)
library(tidyverse)
dat <- data.frame(a = 0:10, b = 20:30, c = 80:90)
nms <- names(dat)
sliderUI <- function(id) {
ns <- NS(id)
min <- min(dat[[id]], na.rm = TRUE)
max <- max(dat[[id]], na.rm = TRUE)
sliderInput(inputId = ns('slider'),
label = id,
min = min,
max = max,
value = c(min, max))
}
sliderServer <- function(df, id) {
moduleServer(
id,
function(input, output, session) {
id <- as.name(id)
df %>% filter(dplyr::between(!!id, input$slider[1], input$slider[2]))
}
)
}
ui <- fluidPage(
map(nms, sliderUI),
tableOutput("df")
)
server <- function(input, output, session) {
tmp_df <- reactive({ dat })
output$df <- renderTable({
purrr::reduce(nms, sliderServer, .init = tmp_df())
})
}
shinyApp(ui, server)
)
I would like to visualize the input of a shinyMatrix using a plotlymesh3d-plot. I was using a for-loop to convert the matrix input into a data frame having 3 columns (x, y and z) - see below. This works when being run outside the shiny app. Unfortunately, I'm stuck with using this for-loop inside a reactive() environment to pass this into plot_ly(). It says "object of type closure is not subsettable". I read, that this error often comes if you don't treat your reactive object as a function (which I did).
I know that I'm a beginner and I don't have much clue about the syntax of a shiny app. Most likely I did a supid mistake :-) But I don't know how to solve this.
Thanks!
library(shiny)
library(shinyMatrix)
ui <- fluidPage( titlePanel("shinyMatrix: Simple App"),
sidebarPanel(width = 6,tags$h4("Data"),
matrixInput("mat",
value = matrix(1:100, 10, 10),
rows = list(names=T, extend = TRUE),
cols = list(names = TRUE))),
mainPanel(width = 6,
plotlyOutput(
"plotly",
width = "100%",
height = "400px",
inline = FALSE,
reportTheme = TRUE
)))
server <- function(input, output, session) {
df <- data.frame(x="", y="", z="")
df <- reactive({
n=1
for(i in 1:nrow(input$mat)){
for(j in 1:ncol(input$mat)){
df[n,1] <- j*laenge
df[n,2] <- i*laenge
df[n,3] <- input$mat[i,j]
n=n+1
}
}
})
output$plotly <- renderPlotly({plot_ly(df(), x=~x, y=~y, z=~z, type="mesh3d")})
}
shinyApp(ui, server)
Main issue is that you used the same name df for both the reactive and the dataframe. Additionally your reactive has to return something to make your code work, i.e. return the dataframe after your for-loops.
server <- function(input, output, session) {
dd <- data.frame(x = NA, y = NA, z = NA)
df <- reactive({
n <- 1
for (i in 1:nrow(input$mat)) {
for (j in 1:ncol(input$mat)) {
dd[n, 1] <- j * laenge
dd[n, 2] <- i * laenge
dd[n, 3] <- input$mat[i, j]
n <- n + 1
}
}
dd
})
output$plotly <- renderPlotly({
plot_ly(df(), x = ~x, y = ~y, z = ~z, type = "mesh3d")
})
}
I have a rather simple problem but can not figure out why it is not working
library(shiny)
library(leaflet)
pts <- data.frame(
id = letters[seq(from = 1, to = 10)],
x = rnorm(10, mean = -93.625),
y = rnorm(10, mean = 42.0285),
stringsAsFactors = F
)
# Define UI
ui <- fluidPage(uiOutput('Select'))
server <- function(input, output, session) {
pts
output$Select <- renderUI({
Range <- sort(unique(pts$id))
selectInput("dataselect",
"select",
choices = Range,
selected = 'a')
})
mydata <- reactive({
if (input$dataselect != 'a') {
data <- pts[pts$id == input$dataselect,]
}
else
{
data <- pts
}
})
observe(print(mydata()))
}
shinyApp(ui = ui, server = server)
I basically try to subset my data set if anything else than 'a' is selected with the selected value. If 'a' is selected I want the whole df returned.
Just run into
Warning: Error in if: argument is of length zero [No stack trace
available]
You need to to not run mydata() if input$dataselect is not available, that can be done by inserting: req(input$dataselect)
As shown below:
library(shiny)
library(leaflet)
pts <- data.frame(
id = letters[seq(from = 1, to = 10)],
x = rnorm(10, mean = -93.625),
y = rnorm(10, mean = 42.0285),
stringsAsFactors = F
)
# Define UI
ui <- fluidPage(uiOutput('Select'))
server <- function(input, output, session) {
output$Select <- renderUI({
Range <- sort(unique(pts$id))
selectInput("dataselect",
"select",
choices = Range,
selected = 'a')
})
mydata <- reactive({
req(input$dataselect)
if (input$dataselect != 'a') {
data <- pts[pts$id == input$dataselect,]
}
else
{
data <- pts
}
})
observe(print(mydata()))
}
shinyApp(ui = ui, server = server)
I'm trying to create a shiny app that generates plots based on the user selection of a subset of a loaded dataframe. For example, I have the following dataset:
library(shiny)
library(data.table)
df <- rbind(
data.table( cat = rep('X', 40), grp = rep(LETTERS[1:4], each=10), x = rep(1:10, times=4), y = rnorm(40) ),
data.table( cat = rep('Y', 30), grp = rep(LETTERS[1:3], each=10), x = rep(1:10, times=3), y = rnorm(30) ),
data.table( cat = rep('Z', 20), grp = rep(LETTERS[4:6], each=10), x = rep(1:10, times=2), y = rnorm(20) )
)
Based on the value for cat that the user selects in the UI, I want shiny to produce charts for each value of grp. So, if the user selects 'X', then there will be 4 plots produced; if they select 'Y' there will be three, and if they select 'Z' there will be 3.
I also want to specify how each chart is generated based on the value of grp. So if grp is A,D or E I want it produce a line plot, otherwise it should produce a scatterplot (only if that grp has that value of course).
Below is the code for my (broken) shiny app:
server <- function(input, output) {
rv <- reactiveValues(
i = NULL,
df = NULL
)
observe({ rv$i <- input$i })
observe({ rv$df <- df[cat == rv$i] })
output$test <- renderUI({
plotList <- lapply( LETTERS[1:6], function(x) plotOutput(x) )
do.call( tagList, unlist(plotList, recursive=FALSE))
})
for(i in LETTERS[1:6]){
local({
my_i <- i
output[[my_i]] <- renderPlot({
if( my_i %in% c('A','D','E')) {
with(rv$df[grp == my_i], plot(x,y, type='l'))
} else {
with(rv$df[grp == my_i], plot(x,y))
}
})
})
}
}
ui <- fluidPage(
titlePanel('Title'),
sidebarLayout(
sidebarPanel(
helpText('Select the Category you would like to view.'),
selectInput('i', 'Category', c('X','Y','Z'), selectize=TRUE)
),
mainPanel(
uiOutput('test')
)
)
)
shinyApp(ui, server)
A reproducible example can be found at the bottom.
A few hints:
1) Using reactive contexts:
In your for Loop at the bottom of the Server Code you are using the reactive variable rv, so you will have to run the Code in a reactive Content. So wrap it in observe().
2) Create a list of Outputs:
If I am not mistaken you used some of the Code in this answer: dynamically add plots to web page using shiny.
It is a good starting Point. For the part of the taglist it might be easier to simplify to:
output$test <- renderUI({
lapply(unique(rv$df$grp), plotOutput)
})
You can also add tagList(), but it is not necessary here,...
3) Correcting the sample data:
You might want to update the df variable:
data.table(cat = rep('Z', 20), grp = rep(LETTERS[4:6], each=10),
x = rep(1:10, times=2), y = rnorm(20) )
Here your have three letters, so you might change it to LETTERS[5:6] or update the other numbers.
Full reproducible example:
library(shiny)
library(data.table)
df <- rbind(
data.table( cat = rep('X', 40), grp = rep(LETTERS[1:4], each=10), x = rep(1:10, times=4), y = rnorm(40) ),
data.table( cat = rep('Y', 30), grp = rep(LETTERS[1:3], each=10), x = rep(1:10, times=3), y = rnorm(30) ),
data.table( cat = rep('Z', 30), grp = rep(LETTERS[4:6], each=10), x = rep(1:10, times=3), y = rnorm(30) )
)
server <- function(input, output) {
rv <- reactiveValues(
i = NULL,
df = NULL
)
observe({ rv$i <- input$i })
observe({ rv$df <- df[cat == rv$i] })
observe({
for(letter in unique(rv$df$grp)){
local({
let <- letter
output[[let]] <- renderPlot({
if( let %in% c('A','D','E')) {
with(rv$df[grp == let], plot(x, y, type='l'))
} else {
with(rv$df[grp == let], plot(x,y))
}
})
})
}
})
output$test <- renderUI({
lapply(unique(rv$df$grp), plotOutput)
})
}
ui <- fluidPage(
titlePanel('Title'),
sidebarLayout(
sidebarPanel(
helpText('Select the Category you would like to view.'),
selectInput('i', 'Category', c('X','Y','Z'), selectize=TRUE)
),
mainPanel(
uiOutput('test')
)
)
)
shinyApp(ui, server)
I'm following this example to plot multiple graphs depending of different parameters (as data frame columns). So the case is that the number of plots to do will vary each day.
I have modified the code to use Highcharter to get javascript charts instead of basic plots but it doesn't work.
Also I would like to know what I have to add to this code to plots charts in 2,3 or 4 columns?
Thanks
ui.R
fluidPage(
# Application title
titlePanel("Hello World!"),
# Show a plot
fluidRow(
column(width = 6,
highchartOutput("hcontainer", height = "400px")
)
)
)
server.R
get_plot_output_list <- function() {
plot_output_list <- lapply(1:NCOL(df), FUN = function(i) {
plot_output_object <- highchartOutput("hcontainer")
plot_output_object <- renderHighchart({
hc <- highchart() %>%
hc_add_serie(name = "df name", data = df)
return(hc)
})
})
do.call(tagList, plot_output_list) # needed to display properly.
return(plot_output_list)
}
observe({
output$hcontainer <- renderUI({ get_plot_output_list() })
#output$hcontainer <- renderHighchart({ get_plot_output_list() })
})
Hi you can try this solution, it do not use the same function as you but one by #jenesaisquoi (found here), this function create several plots and handle the layout correctly :
# Packages
library("highcharter")
library("shiny")
# data
df <- data.frame(
var1 = rnorm(10),
var2 = rnorm(10),
var3 = rnorm(10),
var4 = rnorm(10),
var5 = rnorm(10),
var6 = rnorm(10),
var7 = rnorm(10)
)
# Fun by #jenesaisquoi (modified with highchartOutput)
makePlotContainers <- function(n, ncol=2, prefix="plot", height=100, width="100%", ...) {
## Validate inputs
validateCssUnit(width)
validateCssUnit(height)
## Construct plotOutputs
lst <- lapply(seq.int(n), function(i)
highchartOutput(sprintf('%s%g', prefix, i), height=height, width=width))
## Make columns
lst <- lapply(split(lst, (seq.int(n)-1)%/%ncol), function(x) column(12/ncol, x))
do.call(tagList, lst)
}
You can use #jenesaisquoi's function directly in the ui, and use lapply in the server for define as many outputs as the number of cols :
# App
ui <- fluidPage(
# Application title
titlePanel("Hello World!"),
# Show plots
makePlotContainers(n = ncol(df), ncol = 3, prefix = "hcontainer", height = "400px")
)
server <- function(input, output) {
lapply(
X = seq_len(ncol(df)),
FUN = function(i) {
output[[paste0("hcontainer", i)]] <- renderHighchart({
highchart() %>%
hc_add_serie(name = paste("df name", i), data = df[[i]])
})
}
)
}
shinyApp(ui = ui, server = server)