R Shiny app ggplot2 times out - r

I am building an app to better understand the differences of the lognormal and the normal distribution. The app should display a using ggplot2 a histogram of simulated data (either normal or lognormal) and fit a normal , a lognormal density and a kernel density to the fake data. For some reason the app below wont display the ggplot2 graph.
# Define UI for application that draws a histogram
library(shiny)
library(ggplot2)
library(stats)
library(gridExtra)
set.seed(15)
ui <- fluidPage(
# Application title
titlePanel("Curve fit with different distributions"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("mean",
"Mean value:",
min = 1,
max = 250,
value = 10)
,
sliderInput("spread",
"Standard deviation:",
min = 0,
max = 25,
step=0.1,
value = 2.5)
,
sliderInput("n",
"How many datapoints:",
min = 10,
max = 10000,
value = 2500)
,
selectInput("dist",
"Which data distribution?" ,
list("Normal"="dnorm" ,
"Lognormal"="dlnorm"
)
)),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot", height = "80%"))
)
)
# Define server logic required to draw a histogram with normal and log normal density
server <- function(input, output) {
sim_data<-reactive({
if(is.null(input$dist) |is.null(input$spread) | is.null(input$mean)) {
return(NULL)
}
mlog<-log(input$mean )
lspread <- log(input$spread)
dat <- data.frame(xn = rnorm(input$n, mean = input$mean, sd = input$spread), ln=rlnorm(input$n, meanlog =mlog , sdlog = lspread))
return(dat)
})
output$distPlot <- renderPlot({
if(is.null(sim_data()) |is.null(input$dist) ){
return(NULL)
}
# generate bins based on input$bins from ui.R
if(input$dist == "dnorm"){
hist_plot<- ggplot(sim_data(), aes(x = xn)) +
geom_histogram(aes(y =..density..),
colour = "black",
fill = "white") +
stat_function(fun = dnorm, colour ="#377EB8", args = list(mean = mymean, sd = mysd))+
stat_function(fun = dlnorm, colour ="#E41A1C", args = list(mean = mylmean, sd = mylsd))+
geom_density(colour="black")+
theme_minimal()
}
else{
hist_plot<- ggplot(sim_data(), aes(x = ln)) +
geom_histogram(aes(y =..density..),
colour = "black",
fill = "white") +
labs(title=distname) +
theme_minimal()+
stat_function(fun = dnorm, colour ="#377EB8", args = list(mean = mymean, sd = mysd))+
stat_function(fun = dlnorm, colour ="#E41A1C", args = list(mean = mylmean, sd = mylsd))+
geom_density(colour="black")+
theme_minimal()
}
if(input$dist == "dnorm"){
box_plot<- ggplot(sim_data(), aes(x="",y = xn)) +
geom_boxplot()+
theme_minimal()
}
else{
box_plot<- ggplot(sim_data(), aes(x="",y = ln)) +
geom_boxplot(
)+
theme_minimal()
}
p=grid.arrange(hist_plot+
theme_minimal(),box_plot+
theme_minimal(), ncol=1,nrow=2, heights = c(4,2))
plot(p)
})
}
# Run the application
shinyApp(ui = ui, server = server)

Hi your problem is here
plotOutput("distPlot", height = "80%")
at the moment you are telling the plot to have the height of 80% of nothing which is nothing. Change the height to 400px for example and it will all work.
Shiny notices that the plot is not visual so it is not even calculating the plot ate the moment.

Related

How to use rayshader / rgl in shiny?

I'm trying to use these nice plots from the rayshader package in my shiny app but all I receive is a black window where the plot should be.
server:
output$ray <- renderPlot({
ggdiamonds = ggplot(df_surf, aes(Inj_D, Inj_L)) +
stat_density_2d(aes(fill = stat(nlevel))
, geom = "polygon"
, n = 200
, bins = 50
,contour = TRUE) +
facet_wrap(Tube~.) +
scale_fill_viridis_c(option = "A") + theme_bw()
options("cores"=2)
plot_gg(ggdiamonds, multicore = TRUE)
render_camera(zoom=0.5,theta=-30,phi=30)
# render_snapshot(clear = FALSE)
# rgl::rglwidget()
})
ui:
mainPanel(
plotOutput("ray", height = 700)
)
edit: Ok, I got it working by simply adding rglwidget() to the server part..
I also changed renderPlot() to renderRglwidget() and plotOutput() to rglwidgetOutput(). Additionally, I put options(rgl.useNULL = TRUE) into the first line of server.R and global.R. I'll leave this post so someone else might stumble over this.
In case it's too confusing:
ui:
mainPanel(
rglwidgetOutput("ray", height = 700)
)
server:
output$ray <- renderRglwidget({
try(rgl.close())
# size, shape, facets..?
ggdiamonds = ggplot(df_surf, aes(Inj_D, Inj_L)) +
stat_density_2d(aes(fill = stat(nlevel))
, geom = "polygon"
, n = 200
, bins = 50
,contour = TRUE) +
facet_wrap(Tube~.) +
scale_fill_viridis_c(option = "A") + theme_bw()
options("cores"=2)
plot_gg(ggdiamonds, multicore = TRUE)
rglwidget()
})

R Shiny: create non-reactive background in plotOutput

I'm trying to build a shiny app where I can change a plot interactively. I want the plot to change within miliseconds and as the changes only include the addition of some points this is actually possible.
The reproducible example contains an abstraction of this idea. The first example plots a scatterplot and I can interactively change the number of points. This happens basically immediately. I'll refer to this part of the plot as the "reactive layer".
library(shiny)
ui <- fluidPage(
sliderInput(inputId = "slider_input", label = "Reactive values:", min = 1, max = 100, value = 10),
plotOutput(outputId = "plotx")
)
quick_server <- function(input, output, session){
output$plotx <- renderPlot({
# reactive layer
plot(
x = sample(x = -4:4, size = input$slider_input, replace = T),
y = sample(x = -4:4, size = input$slider_input, replace = T)
)
})
}
shinyApp(ui = ui, server = quick_server)
The problem is that the plot that I want to change interactively always includes a "slow non reactive layer" of many datapoints that are unreactive and never change. Due to the size of this data set and renderPlot() always replotting it the speed with which I interactively change the "reactive layer" decreases dramatically.
library(shiny)
ui <- fluidPage(
sliderInput(inputId = "slider_input", label = "Reactive values:", min = 1, max = 100, value = 10),
plotOutput(outputId = "plotx")
)
slow_server <- function(input, output, session){
base_data <- reactiveVal(value = data.frame(x = rnorm(n = 200000), y = rnorm(n = 200000)))
output$plotx <- renderPlot({
# slow non reactive layer
plot(x = base_data()$x, y = base_data()$y)
# reactive layer
points(
x = sample(x = -4:4, size = input$slider_input, replace = T),
y = sample(x = -4:4, size = input$slider_input, replace = T),
col = "red",
cex = 5,
pch = 19
)
})
}
shinyApp(ui = ui, server = slow_server)
As the base data and the resulting plot (here a cloud of points) never changes it is quite annyoing that renderPlot() always replots everything (both "layers"). Is there a way to "isolate" the plot created from the non reactive data set? Such that the layer that is not reactive is not replotted?
I have already tried to work with ggplot2 and create a steady layer
big_data_frame <- data.frame(x = rnorm(n = 200000), y = rnorm(n = 200000))
steady_layer <- reactiveVal(value = geom_point(data = big_data_frame, mapping = aes(x = x, y = y))
And then created the plot like this
output$plotx <- renderPlot({
small_df <-
data.frame(
x = sample(x = -4:4, size = input$slider_input, replace = T),
y = sample(x = -4:4, size = input$slider_input, replace = T)
)
ggplot() +
steady_layer() +
geom_point(data = small_df, mapping = aes(x = x, y = y)
})
But this doesn't help as it is the replotting process that takes time not creating the ggplot layer itself.
Although I can imagine that the solution might be to create a .png of the big plot and use it as the background of the HTML and CSS for the output$plotx by making it a reactive UI I have not managed to manipulate the HTML and CSS successfully.
Any help is appreciated. Thanks so much in advance!
You need to understand how renderPlot works. It uses the R png function first to create a png and then sends it to the client browser. When the data of the plot changes, this png is recreated. So, replot part of the png is not possible. So adding points to an existing plot will always use the time of slow points + new points, so under the current shiny mechanism, it is not possible not to recalculate these slow points. A possible option is to use plotly with proxy . It is made of HTML and Javascript, so yes, you can do it with partial updating. See the link for details, not repeating here. For my personal experience, it is fast not so fast as milliseconds-level as you want.
So here I have a smart trick for you: why do we update on the same plot? We can use one plot as background and it is slow, but we only render it one time, and we will never touch it again. Then we update another plot that has only a few points and we stack this plot on top of the slow plot.
Here is how:
add some CSS tricks to do the stacking
rendering the slow plot
rendering the the quick plot with transparency
library(shiny)
library(ggplot2)
ui <- fluidPage(
sliderInput(inputId = "slider_input", label = "Reactive values:", min = 1, max = 100, value = 10),
div(
class = "large-plot",
plotOutput(outputId = "plot_bg"),
plotOutput(outputId = "plotx")
),
tags$style(
"
.large-plot {
position: relative;
}
#plot_bg {
position: absolute;
}
#plotx {
position: absolute;
}
"
)
)
slow_server <- function(input, output, session){
base_data <- reactiveVal(value = data.frame(x = rnorm(n = 200000), y = rnorm(n = 200000)))
output$plot_bg <- renderPlot({
ggplot(base_data()) +
geom_point(aes(x,y)) +
scale_x_continuous(breaks = -4:4) +
scale_y_continuous(breaks = -4:4) +
xlim(-5, 5) +
ylim(-5, 5)
})
output$plotx <- renderPlot({
data.frame(
x = sample(x = -4:4, size = input$slider_input, replace = T),
y = sample(x = -4:4, size = input$slider_input, replace = T)
) %>%
ggplot() +
geom_point(aes(x,y), color = "red", size = 3) +
scale_x_continuous(breaks = -4:4) +
scale_y_continuous(breaks = -4:4) +
theme(
panel.background = element_rect(fill = "transparent"),
plot.background = element_rect(fill = "transparent", color = NA),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.background = element_rect(fill = "transparent"),
legend.box.background = element_rect(fill = "transparent")
)+
xlim(-5, 5) +
ylim(-5, 5)
}, bg="transparent")
}
shinyApp(ui = ui, server = slow_server)

Changing the fill of mosaic plot in Shiny

I have the following shiny app:
library(shiny)
library(ggplot2)
library(dplyr)
library(networkD3)
library(ggmosaic)
#Loading data
Category <- c("Bankpass", "Bankpass", "Bankpass", "Moving", "Moving")
Subcategory <- c("Stolen", "Lost", "Login", "Address", "New contract")
Weight <- c(10,20,13,40,20)
Duration <- as.character(c(0.2,0.4,0.5,0.44,0.66))
Silence <- as.character(c(0.1,0.3,0.25,0.74,0.26))
df <- data.frame(Category, Subcategory, Weight, Duration, Silence)
ui <- fluidPage(
tags$div(class="header",
selectInput("measure", "", c("Duration", "Silence"))
),
mainPanel(
tags$div(class = "dashboard_main",
tags$div(class="dashboard_main_left", plotOutput("secondPlot"))
)
)
)
server <- function(input, output){
output$secondPlot <- renderPlot({
ggplot(data = df) +
geom_mosaic(aes(weight = Weight, x = product(Category), fill=Duration),
offset = 0, na.rm=TRUE) +
theme(axis.text.x=element_text(angle=-25, hjust= .1)) +
theme(axis.title.x=element_blank()) +
scale_fill_manual(values=c("#e8f5e9", "#c8e6c9", "#a5d6a7", "#81c784", "#66bb6a"))
})
}
shinyApp(ui = ui, server= server)
I would like to make the second plot interactive now. So if you select the Duration the fill in the plot "secondPlot" should be Duration and if you you select "Silence" the fill should be "Silence".
However when I change the relevante code of the graph to:
ggplot(data = df) +
geom_mosaic(aes(weight = Weight, x = product(Category), fill=input$measure),
offset = 0, na.rm=TRUE) +
theme(axis.text.x=element_text(angle=-25, hjust= .1)) +
theme(axis.title.x=element_blank())
I dont see the colour gradients anymore. Any thoughts on what goes wrong here?
You should use aes_string inside geom_mosaic. Try this:
server <- function(input, output){
df$prodcat <- product(df$Category)
output$secondPlot <- renderPlot({
ggplot(data = df) +
geom_mosaic(aes_string(weight = "Weight", x = "prodcat", fill=input$measure),
offset = 0, na.rm=TRUE) +
theme(axis.text.x=element_text(angle=-25, hjust= .1)) +
theme(axis.title.x=element_blank()) +
scale_fill_manual(values=c("#e8f5e9", "#c8e6c9", "#a5d6a7", "#81c784", "#66bb6a"))
})
}

How do I resolve this error that comes in R,Shiny inspite of specifying the environment?

I am trying to display a map and a plot here in a tab interface using Shiny package in R. Inspite of setting the environment by calling the environment(), I am getting this error. The code snippet is given below:
server.R :
shinyServer(function(input, output, session) {
output$box <- renderPlot({
filtered<- aleast.scores[aleast.scores$team == input$typeInput , ]
ggplot(data=filtered) +
geom_bar(mapping=aes(x=score, fill=team), binwidth=1) +
#facet_grid(team~.) +
theme_bw() + scale_color_brewer() +
labs(title="MOBILE PHONE REVIEW")
})
output$map <- renderPlot({
long <-locations_f$longitude
lat <- locations_f$latitude
worldMap <- map_data("world")
zp1 <- ggplot(worldMap,environment=environment())
zp1 <- zp1 + geom_path(aes(x = long, y = lat, group=group), #Draw map
colour = gray(2/3), lwd = 1/3)
filtered<- locations_f[locations_f$team == input$typeInput , ]
zp1 <- zp1 + geom_point(data = filtered, #Add points indicating users
aes(x = long, y = lat, color=type),
alpha = 1, size = 1.5)#+facet_grid(team~.)# +
zp1<-zp1+theme_bw() # + scale_color_brewer()
zp1 <- zp1 + theme_minimal()
print(zp1)
})
})
UI.R
library(shiny)
# Define UI for random distribution application
shinyUI(fluidPage(
# Application title
titlePanel("Tabsets"),
# Sidebar with controls to select the random distribution type
# and number of observations to generate. Note the use of the
# br() element to introduce extra vertical spacing
sidebarLayout(
sidebarPanel(
radioButtons("typeInput", "Product type",
choices = c("motorola","nexus","iphone")),
br()
),
# Show a tabset that includes a plot, summary, and table view
# of the generated distribution
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("plot", plotOutput("box")),
tabPanel("Map", plotOutput("map"))
)
)
)
))
And This is the error I am getting
Warning: Error in exists: argument "env" is missing, with no default
Stack trace (innermost first):
68: output$box
1: shiny::runApp
Also the program worked fine when the first graph (MOBILE PHONE REVIEW) was alone plotted without any tab interface
You did not provide a reproducible example, so I made up some data, and the app ran fine. Try updating your packages.
I made some changes:
replaced geom_bar with geom_histogram to deal with the Warning: 'geom_bar() no longer has a 'binwidth' parameter. Please use 'geom_histogram()' instead.
replaced long and lat in geom_point(data = filtered, aes(x = longitude, y = latitude, color=type), alpha = 1, size = 1.5) since you want the colours to be from the filtered data
server.R
aleast.scores <- data.frame(score = runif(100, min = 0, max = 10), team = sample(c("motorola","nexus","iphone"), 100, replace = TRUE))
locations_f <- data.frame(latitude = runif(100, min = -35, max = 35), longitude = runif(100, min = -120, max = 150), team = sample(c("motorola","nexus","iphone"), 100, replace = TRUE), type = sample(c("good phone", "crap phone"), 100, replace = TRUE))
library(ggplot2)
shinyServer(function(input, output, session) {
output$box <- renderPlot({
filtered<- aleast.scores[aleast.scores$team == input$typeInput , ]
ggplot(data=filtered) +
geom_histogram(mapping=aes(x=score, fill=team), binwidth=1) +
#facet_grid(team~.) +
theme_bw() + scale_color_brewer() +
labs(title="MOBILE PHONE REVIEW")
})
output$map <- renderPlot({
long <-locations_f$longitude
lat <- locations_f$latitude
worldMap <- map_data("world")
zp1 <- ggplot(worldMap,environment=environment())
zp1 <- zp1 + geom_path(aes(x = long, y = lat, group=group), #Draw map
colour = gray(2/3), lwd = 1/3)
filtered<- locations_f[locations_f$team == input$typeInput , ]
zp1 <- zp1 + geom_point(data = filtered, #Add points indicating users
aes(x = longitude, y = latitude, color=type),
alpha = 1, size = 1.5)#+facet_grid(team~.)# +
zp1<-zp1+theme_bw() # + scale_color_brewer()
zp1 <- zp1 + theme_minimal()
print(zp1)
})
})
ui.R
shinyUI(fluidPage(
# Application title
titlePanel("Tabsets"),
# Sidebar with controls to select the random distribution type
# and number of observations to generate. Note the use of the
# br() element to introduce extra vertical spacing
sidebarLayout(
sidebarPanel(
radioButtons("typeInput", "Product type",
choices = c("motorola","nexus","iphone")),
br()
),
# Show a tabset that includes a plot, summary, and table view
# of the generated distribution
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("plot", plotOutput("box")),
tabPanel("Map", plotOutput("map"))
)
)
)
))

Select range in interactive shiny plots

I am trying to create an interactive visualisation of data in shiny. The visualisation shows the distribution (or histogramm) of parts of a series. For example, the following code creates a series and two selections (two is fixed) of parts of the series, which is then displayed using ggplot:
library(ggplot2)
set.seed(123)
dat <- data.frame(x = 1:1000,
y = cumsum(rnorm(1000, mean = 0.1)))
sel1 <- 200:400 # selection 1
sel2 <- 700:900 # Selection 2
# create a plot of the series
ggplot() + geom_line(data = dat, aes(x = x, y = y)) +
geom_rect(aes(xmin = sel1[1], xmax = sel1[length(sel1)],
ymin = -Inf, ymax = Inf), alpha = 0.5, fill = "red") +
geom_rect(aes(xmin = sel2[1], xmax = sel2[length(sel2)],
ymin = -Inf, ymax = Inf), alpha = 0.5, fill = "blue")
# Histogramm preparation
# create another df that contains the selection of the two selections
pdat <- rbind(data.frame(y = dat[dat$x %in% sel1, 2],
sel = 1),
data.frame(y = dat[dat$x %in% sel2, 2],
sel = 2))
# plot the histograms
ggplot(pdat, aes(x = y, fill = as.factor(sel))) +
geom_histogram(alpha = 0.5, position = "dodge")
which creates:
Now I want the user to be able to move the areas (preferably by dragging the shaded areas in plot 1 around!) using shiny.
I played around with the (new) interactive options of shiny (more info here, look for section "Interactive plots"). I think I can remember that there is an option to specify an area, which the user is able to drag around, but I can't find it anymore.
Any ideas?
As mentioned in the comments do look into rCharts and dygraphs, below is the example taken from tutorials with some modifications. Please note that the dygraphs require a timeseries object to plot, refer to official docs for more information. The summary statistics can be performed by a package of your choice. Also note that the shaded regions are user specified...
rm(list = ls())
library(shiny)
library(dygraphs)
library(xts)
library(rCharts)
index <- as.Date(c(seq(Sys.time(), length.out = 1000, by = "days")))
dat <- data.frame(x = index,y = cumsum(rnorm(1000, mean = 0.1)))
dat <- xts(dat[,-1], order.by=dat[,1])
ui <- fluidPage(
titlePanel("Shaded Regions using dygraphs and rCharts by Pork Chop"),
sidebarLayout(
sidebarPanel(
sliderInput("range_one", "Range One:",min = 100, max = 1000, value = c(200,300)),
sliderInput("range_two", "Range Two:",min = 100, max = 1000, value = c(500,600)),width=3),
mainPanel(
column(12,dygraphOutput("dygraph")),
column(12,showOutput("summary", "Highcharts"))
)
)
)
server <- function(input, output) {
output$dygraph <- renderDygraph({
dygraph(dat, main = "Sample Data") %>%
dyShading(from = index[input$range_one[1]], to = index[input$range_one[2]], color = "#FFE6E6") %>%
dyShading(from = index[input$range_two[1]], to = index[input$range_two[2]], color = "#CCEBD6")
})
output$summary <- renderChart2({
Selection1 <- dat[input$range_one[1]:input$range_one[2]]
Selection2 <- dat[input$range_two[1]:input$range_two[2]]
subset_data <- data.frame(merge(Selection1,Selection2))
a <- rCharts:::Highcharts$new()
a$chart(type = "column")
a$title(text = "Summary Stats")
a$yAxis(title = list(text = "Count"))
a$data(subset_data)
a$exporting(enabled=T)
a$set(width = 1200,height = "100%",slider = TRUE)
return(a)
})
}
shinyApp(ui, server)
I think I found a solution that is able to use interactive ggplot's in a shiny environment. The code looks like this:
library(shiny)
library(ggplot2)
ifna <- function(x, elseval = NA) ifelse(is.na(x) || is.null(x), elseval, x)
# two plots: as described in the question
ui <- fluidPage(
uiOutput("plotui"),
plotOutput("plot2")
)
server = function(input, output) {
set.seed(123)
dat <- data.frame(x = 1:1000,
val = cumsum(rnorm(1000, mean = 0.1)))
base <- 200:400 # Base Selection
# reactive expressions to get the values from the brushed area
selmin <- reactive(round(ifna(input$plot_brush$xmin, elseval = 700), 0))
selmax <- reactive(round(ifna(input$plot_brush$xmax, elseval = 900), 0))
# include the brush option: direction = "x" says that y values are fixed (min and max)
output$plotui <- renderUI({
plotOutput("plot", height = 300,
brush = brushOpts(id = "plot_brush", direction = "x",
fill = "blue", opacity = 0.5)
)
})
# render the first plot including brush
output$plot <- renderPlot({
ggplot() + geom_line(data = dat, aes(x = x, y = val)) +
geom_rect(aes(xmin = base[1], xmax = base[length(base)],
ymin = -Inf, ymax = Inf), alpha = 0.5, fill = "red") +
geom_rect(aes(xmin = 700, xmax = 900,
ymin = -Inf, ymax = Inf), alpha = 0.1, fill = "blue") +
ylab("Value") + xlab("t")
})
# render the second plot reactive to the brushed area
output$plot2 <- renderPlot({
# prepare the data
pdat <- rbind(data.frame(y = dat[dat$x %in% base, "val"],
type = "Base"),
data.frame(y = dat[dat$x %in% selmin():selmax(), "val"],
type = "Selection"))
ggplot(pdat, aes(x = y, fill = type)) +
geom_histogram(alpha = 0.5, position = "dodge") +
scale_fill_manual(name = "", values = c("red", "blue")) +
theme(legend.position = "bottom") + ylab("Frequency") + xlab("Value")
})
}
# run the app
shinyApp(ui, server)
Which gives something like this (the dark-blue box is interactive, as in you can push it around and the lower graph updates!
Picture of Shiny App

Resources