I created an R shiny application that has a dygraph based on a data table that is dynamically subsetted by a checkboxGroupInput. My problem is, when I attempt to load large amounts of data (millions of records), it loads very slowly and/or crashes.
After doing some more research, I stumbled upon a "lazy-load" technique from here. Based on my understanding, this technique essentially downsamples the data by only loading the number of data points equal to the width of the dygraph window. As the user zooms in, it will drill down and load more data within the dyRangeSelector max/min dates. I suspect this will solve my problem, because it will load significantly less data at any given dygraph interaction. However, all of the examples provided in this link were in Javascript, and I'm having trouble translating it to R.
I also attempted to treat the GraphDataProvider.js file as a dygraph plugin, but I was unable to get it to work properly.
A couple of quick notes on my implementation:
Each element of data_dict in the server is an xts object.
The do.call.cbind function call in the server is based off of this SO implementation, and it is very fast.
My current setup is essentially like this (I refactored it to make it generic):
Data Setup:
library(shiny)
library(shinydashboard)
library(dygraphs)
library(xts)
library(data.table)
start <- as.POSIXlt("2018-07-09 00:00:00","UTC")
end <- as.POSIXlt("2018-07-11 00:00:00","UTC")
x <- seq(start, end, by=0.5)
data <- data.frame(replicate(4,sample(0:1000,345601,rep=TRUE)))
data$timestamp <- x
data <- data[c("timestamp", "X1", "X2", "X3", "X4")]
data <- as.data.table(data)
filters <- c("X1","X2","X3","X4")
data_dict <- vector(mode="list", length=4)
names(data_dict) <- filters
data_dict[[1]] <- as.xts(data[,c('timestamp','X1')]); data_dict[[2]] <- as.xts(data[,c('timestamp','X2')])
data_dict[[3]] <- as.xts(data[,c('timestamp','X3')]); data_dict[[4]] <- as.xts(data[,c('timestamp','X4')])
# Needed to quickly cbind the xts objects
do.call.cbind <- function(lst){
while(length(lst) > 1) {
idxlst <- seq(from=1, to=length(lst), by=2)
lst <- lapply(idxlst, function(i) {
if(i==length(lst)) { return(lst[[i]]) }
return(cbind(lst[[i]], lst[[i+1]]))})}
lst[[1]]}
UI:
header <- dashboardHeader(title = "App")
body <- dashboardBody(
fluidRow(
column(width = 8,
box(
width = NULL,
solidHeader = TRUE,
dygraphOutput("graph")
)
),
column(width = 4,
box(
width = NULL,
checkboxGroupInput(
"data_selected",
"Filter",
choices = filters,
selected = filters[1]
),
radioButtons(
"data_format",
"Format",
choices=c("Rolling Averages","Raw"),
selected="Rolling Averages",
inline=TRUE
)
)
)
)
)
ui <- dashboardPage(
header,
dashboardSidebar(disable=TRUE),
body
)
Server:
server <- function(input, output) {
# Reactively subsets the dataset based on checkboxGroupInput filters
the_data <- reactive({
data <- do.call.cbind(data_dict[input$data_selected]) # Column bind multiple xts objects
})
output$graph <- renderDygraph({
graph <- dygraph(the_data()) %>%
dyRangeSelector(c("2018-07-10 00:00:00","2018-07-10 02:00:00")) %>%
dyOptions(useDataTimezone = TRUE,connectSeparatedPoints = TRUE)
if(input$data_format == "Rolling Averages") graph <- graph %>% dyRoller(rollPeriod = 100)
graph
})
}
Make App:
shinyApp(ui, server)
I would appreciate any help I can get on this, this has stumbled me for a while now. Thank you!
Related
I'm struggling to update a reactive variable, that is created with eventReactive(), in an observeEvent() with new data.
The background is following: I have a data.frame df with some variables (x and y) and number of observations depending on the selected city (created randomly for this example).
x and y are initialized with zeros.
Because I need to further process df, I pass df to city_df in an eventReactive().
So far, so good. Next, I want to add new data to city_df. The computation of this new data is dependent on the "compute" actionButton (input$compute), wherefore I update city_df in an observeEvent(). I manage to read the data stored in city_df, but I am struggling to overwrite its content.
Actually, I am a bit unsure if this is possible at all, but I hope that some of you could give me a hint on how to update the reactive variable city_df with the new data in this observeEvent() and have its output evaluated in the app(?).
library(shiny)
# global variables
cities <- c("Nairobi", "Kansas", "Uppsala", "Sangon", "Auckland", "Temuco")
# ui
ui <- fluidPage(
fluidPage(
fluidRow(
column(2,
selectInput("city", "Select city",
choices = cities,
selected = sample(cities,
size = 1)
),
actionButton("compute",
"Compute")),
column(8,
verbatimTextOutput("the_city"))
))
)
# server
server <- function(input, output, session) {
# create variable
city_df <- eventReactive(input$city, {
len <- round(runif(1, 20, 50), 0)
df <- data.frame(city = rep(input$city, len))
# initialize x and y with zeros
df <- cbind(df,
data.frame(x = rep.int(0, len),
y = rep.int(0, len)))
})
output$the_city <- renderText({
paste(city_df())
})
observeEvent(input$compute, {
# grab data
test <- city_df()
# compute new data
test$x <- runif(dim(test)[1], 11, 12)
test$y <- runif(dim(test)[1], 100, 1000)
# and how to send this values back to city_df?
})
}
# run app
shinyApp(ui, server)
The actual app is far more complex--so forgive me if this MWE app seems a bit overly complicated to achieve this usually simple task (I hope I managed to represent the more complex case in the MWE).
Instead of a data.frame, I am parsing layers of a GeoPackage and append some variables initialized with zeros. The selected layer is displayed in a Leaflet map. On pressing the "compute" button, a function computes new data that I wish to add to the layer to then have it displayed on the map.
The alternative solution I have on mind is to write the new data to the GeoPackage and then, reread the layer. However, I would appreciate if I could avoid this detour as loading the layer takes some time...
Many thanks :)
Rather than using an eventReactive, if you use a proper reactiveVal, then you can change the value whenever you like. Here's what that would look like
server <- function(input, output, session) {
# create variable
city_df <- reactiveVal(NULL)
observeEvent(input$city, {
len <- round(runif(1, 20, 50), 0)
df <- data.frame(city = rep(input$city, len))
# initialize x and y with zeros
df <- cbind(df,
data.frame(x = rep.int(0, len),
y = rep.int(0, len)))
city_df(df)
})
output$the_city <- renderText({
paste(city_df())
})
observeEvent(input$compute, {
# grab data
test <- city_df()
test$x <- runif(dim(test)[1], 11, 12)
test$y <- runif(dim(test)[1], 100, 1000)
city_df(test)
})
}
So calling city_df() get the current value and calling city_df(newval) updates the variable with a new value. We just swap out the eventReactive with observeEvent and do the updating ourselves.
hope someone can help with this one:
My code below was returning a dinamic table as I intend, but when I created the for loop to assign individually random numbers to each [k,p] position in the table all the calculations stop and I see an empty UI.
If instead of
rv$MCProbTable[[k,p]] = round(as.numeric(stats::runif(1,0,100)), 3)
I use for example
rv$MCProbTable[[k,p]] = 2
I see "2" allocated in each position in the table, what is close to what I want and shows that everything is working up to the runif application. So is it an issue with the function? Or most likely it is the reactive for loop that isn't set properly?
Thanks and hope this is an easy one!
## LIBRARIES
library(tidyverse)
library(stats)
library(data.table)
library(triangle)
library(base)
library(matrixStats)
library(ggplot2)
library(ggthemes)
library(readxl)
library(httr)
library(writexl)
library(shiny)
ExcelTemplate <- tempfile(fileext = ".xlsx")
GET(url = "https://www.openmontecarlo.com/SampleS.xlsx",write_disk(ExcelTemplate))
defaultRR <- read_xlsx(ExcelTemplate)
## DEFINE UI
ui <- fluidPage(
numericInput("ISvModels", label = h3("Select how many scenarios to run in the model:"),value=10,min=5,max=10000),
hr(),
mainPanel(
fluidRow(
p("vModels"),
textOutput("vModelsText"),
p("vtModels"),
textOutput("vtModelsText"),
p("Scenarios"),
textOutput("SnScenarios"),
h3(strong("ProbTable")),
br(),
dataTableOutput("SMCProbTable"),
hr()
)
)
)
server <- function(input, output, session) {
## CREATE DEFAULT RR AND REACTIVE VARIABLES
rv <- reactiveValues(
vModels = 10,
nScenarios = 10,
vtModelScenarios = NULL,
RRDT = data.frame(),
nRisks = 1,
MCProbTable = data.frame(),
p = 1,
k = 1
)
## PROCESS MODELS INPUT
observe({rv$vModels <- as.numeric(unlist(input$ISvModels))})
output$vModelsText <- renderPrint({unlist(rv$vModels)})
qtModels <- reactive({length(unlist(rv$vModels))})
output$qtModelsText <- renderPrint({unlist(qtModels())})
vtModels <- reactive({paste0("M",1:qtModels()," n = ",rv$vModels," scenarios")})
output$vtModelsText <- renderPrint({unlist(vtModels())})
## RR TABLE
observe({rv$nRisks <- nrow(defaultRR)})
###############################################################################################################
## CALCULATE SCENARIOS - CREATE DATA TABLES AND TEXT VECTORS FOR EACH SCENARIO
## Vectors for SINGLE scenario
observe({rv$nScenarios <- rv$vModels})
observe({rv$vtModelScenarios <- paste0("Scenario ",1:rv$nScenarios,"/",rv$vModels)})
## CREATE PROBABILITY TABLE AND CALCULATE PROBABILITIES
observe({rv$MCProbTable <- data.frame(matrix(nrow=rv$nRisks,ncol=rv$nScenarios))})
observe({rownames(rv$MCProbTable) <- rv$RRDT$ID})
observe({colnames(rv$MCProbTable) <- rv$vtModelScenarios})
observe({
for (p in 1:rv$nScenarios){
for (k in 1:rv$nRisks){
rv$MCProbTable[[k,p]] = round(as.numeric(stats::runif(1,0,100)), 3)
}
}
})
output$SMCProbTable <- renderDataTable({rv$MCProbTable})
}
# Run the app ----
shinyApp(ui = ui, server = server)
In your for loop, use isolate()
for (p in 1:rv$nScenarios){
for (k in 1:rv$nRisks){
isolate(
rv$MCProbTable[k,p] <- round(as.numeric(stats::runif(1,0,100)), 3)
)
}
}
I am trying to insert additional information into a reactable in R - one which has about 3600 rows. I've tried nesting a plot under each row (similar to this, but with nested plots instead of sub-tables). The only way I could make this work was to use plotly within reactable, like so:
library(reactable)
library(magrittr)
library(plotly)
my_diamonds <- diamonds
my_diamonds$cats <- cut(my_diamonds$price, 850)
my_diamonds <- my_diamonds[ order(my_diamonds$cut, my_diamonds$cats), ]
data <- unique(my_diamonds[, c("cut", "cats")])
reactable(data,
details = function(index) {
diam_data <- my_diamonds[my_diamonds$cut == data$cut[index] & my_diamonds$cats == data$cats[index], ]
plot_ly(diam_data,
x = ~1:nrow(diam_data),
y = ~y,
type = 'scatter',
mode = 'lines') # %>% toWebGL()
}
)
But sadly, for this amount of data, this takes forever to output the table, and anything I've tried to make it faster (such as toWebGL()) changes nothing. All I really care about is the speed, and having some sort of visualisation associated with each row - I don't particularly care if it's plotly or something else.
A second option would be to use an in-line HTML widget for each row (shown here). In my example, this could be done if adding:
data_parcels <- split(my_diamonds, list(my_diamonds$cats, my_diamonds$cut), drop = T)
data$nested_points <- sapply(data_parcels, '[[', 'y')
data$sparkline <- NA
library(sparkline)
reactable(data,
columns = list(
sparkline = colDef(cell = function(value, index) {
sparkline(data$nested_points[[index]])
})
))
This isn't quite as slow as the plotly option, but still very slow in the larger scheme of things. Any ideas on how to speed up either example, anyone?
PaulM and I have worked on a solution together, and managed to speed up one of the options: the one involving in-line sparklines. As it turned out based on some profiling work, what was making the process particularly slow wasn't drawing the sparklines in itself, rather the subsequent work of translating them from R so that they could be incorporated into the HTML reactable table.
So to bypass that slow translation process entirely, we wrote a code template that would get wrapped around the data points to be plotted. This is what we then served directly to reactable, alongside an html = TRUE argument, for the code to be interpreted as such, rather than as regular text.
The final hurdle after that was to ensure that the sparklines (one per row) were still on display even if a user sorted a column or navigated to a different page of results - normally the sparklines would disappear on interacting with the table in this way. For this, we ensured that that the reactable would be redrawn 10ms after any click.
Here is an example wrapped in shiny that shows all this in action, alongside the old (slow) version. For me, the sped up version renders in about 0.5s roughly, whereas the old one - about 13s.
library(reactable)
library(magrittr)
library(plotly)
library(sparkline)
library(shiny)
library(shinycssloaders)
library(shinyWidgets)
if (interactive()) {
# Init objects
t0 <- NULL
t1 <- NULL
my_diamonds <- diamonds
my_diamonds$cats <- cut(my_diamonds$price, 850)
my_diamonds <- my_diamonds[ order(my_diamonds$cut, my_diamonds$cats), ]
data <- unique(my_diamonds[, c("cut", "cats")])
data_parcels <- split(my_diamonds, list(my_diamonds$cats, my_diamonds$cut), drop = T)
data$nested_points <- sapply(data_parcels, '[[', 'y')
data$sparkline <- NA
ui <- shinyUI(
basicPage(
br(),
radioGroupButtons(
inputId = "speedChoice",
label = "Speed",
choices = c("Fast", "Slow"),
status = "danger"
),
br(),
verbatimTextOutput("timeElapsed"),
br(),
shinycssloaders::withSpinner(
reactableOutput("diamonds_table")
),
# Small JS script to re-render a reactable table so that the sparklines show
# after the user has modified the table (sorted a col or navigated to a given page of results)
tags$script('document.getElementById("diamonds_table").addEventListener("click", function(event){
setTimeout(function(){
console.log("rerender")
HTMLWidgets.staticRender()
}, 10);
})
')
)
)
server <- function(input, output, session) {
output$diamonds_table <- renderReactable({
if (input$speedChoice == "Fast") {
t0 <<- Sys.time()
part1 <- '<span id="htmlwidget-spark-' # + ID
part2 <- '" class="sparkline html-widget"></span><script type="application/json" data-for="htmlwidget-spark-' # + ID
part3 <- '">{"x":{"values":[' # + values
part4 <- '],"options":{"height":20,"width":60},"width":60,"height":20},"evals":[],"jsHooks":[]}</script>'
out <- list(length = nrow(data))
for (i in 1:nrow(data)) {
vals <- paste0(data$nested_points[[i]], collapse = ',')
out[[i]] <- paste0(part1, i, part2, i, part3, vals, part4)
}
data$sparkline <- out
tab <- reactable(data,
columns = list(
sparkline = colDef(html = TRUE,
cell = function(value, index) {
return(htmltools::HTML(value))
}
)
)
) %>%
spk_add_deps() %>%
htmlwidgets::onRender(jsCode = "
function(el, x) {
HTMLWidgets.staticRender();
console.log('render happening')
}")
t1 <<- Sys.time()
return(tab)
} else {
# Classic, but slow version:
t0 <<- Sys.time()
tab <- reactable(data,
columns = list(
sparkline = colDef(cell = function(value, index) {
data$nested_points[[index]] %>%
sparkline::sparkline()
}
)
)
)
t1 <<- Sys.time()
return(tab)
}
})
output$timeElapsed <- renderText({
input$speedChoice # Connect to reactable update cycle
return(t1 - t0)
})
}
shinyApp(ui = ui, server = server)
}
We have created a shiny application where either the user can upload a big dataset (RData file over 200MB) or they can pick one from us. Then there are three different tabs where the user can filter the data (tab for numerics, tab for categorics)
So currently I have 3 reactive functions to serve that purpose. But downside is that the object is kept three times in memory. Is there a more efficient way to do this?
Please find a simplified example app below:
note: in this app you only see 1 filter per tab. normally its more like this:
My_Filtered_Data[Species %in% input$filter1 &
x %in% input$x &
y %in% input$y &
z %in% input$z] #etc.
I was looking at reactiveValues but couldn't really find how it works.
Reason I don't want to have it in 1 reactive is that everytime I change one of the filters on one of the sheets, the entire filtering process starts again and that is quite time consuming. I'd prefer to have one dataset that that gets updated with only the filter that is used at that time. That's the reason I included the different reactives
## app.R ##
library(shinydashboard)
library(data.table)
CustomHeader <- dashboardHeader(title='datatest')
iris<-iris
ui <- function(request) {
dashboardPage(
CustomHeader,
## Sidebar content
dashboardSidebar(
sidebarMenu(
menuItem("filter1 & Import", tabName = "filter1", icon = icon("dashboard")),
menuItem("filter2", tabName = "filter2", icon = icon("th")),
menuItem("filter3", tabName = "filter3", icon = icon("th"))
)
),
## Body content
dashboardBody(
tabItems(
# First tab content
tabItem(tabName = "filter1",
fluidRow(box(width = 3,
selectInput(inputId = 'filter1','filter1:species',choices = unique(iris$Species))))
),
tabItem(tabName = "filter2",
fluidRow(box(width = 3,
sliderInput(inputId = 'filter2','filter2:Max.Sepal.Length',min = 0,max = 10,value = 10)
))
),
tabItem(tabName = "filter3",
fluidRow(box(width = 3,
sliderInput(inputId = 'filter3','filter3:Min.Sepal.Width',min = 0,max = 10,value = 0)
),
box(width=9,dataTableOutput('mydata')))
)
)
)
)
}
server <- function(input, output) {
My_Uploaded_Data <- reactive({
My_Uploaded_Data<-data.table(iris)
My_Uploaded_Data
})
My_Filtered_Data <- reactive({
My_Filtered_Data<-My_Uploaded_Data()
My_Filtered_Data[Species %in% input$filter1]
})
My_Filtered_Data2 <- reactive({
My_Filtered_Data2<-My_Filtered_Data()
My_Filtered_Data2[Sepal.Length < input$filter2]
})
My_Filtered_Data3 <- reactive({
My_Filtered_Data3<-My_Filtered_Data2()
My_Filtered_Data3[Sepal.Width > input$filter3]
})
output$mydata<-renderDataTable({
My_Filtered_Data3()
})
}
shinyApp(ui, server)
I was hoping something like tthis would work in reactiveValues
react_vals <- reactiveValues(data = NULL)
observe(react_vals$data <- MyLoadedData())
observe(react_vals$data <- react_vals$data[Species %in% input$filter1])
observe(react_vals$data <- react_vals$data[Sepal.Length < input$filter2])
observe(react_vals$data <- react_vals$data[Sepal.Width > input$filter3])
EDIT: I also would like to include bookmarks: https://shiny.rstudio.com/articles/advanced-bookmarking.html and it seems you need reactiveValues for that. So another reason for me to move away from all these reactives/eventReactive
Instead of storing datasets in the reactive variables, just store the rows which qualify. That way, each reactive value is only replaced when it's filter changes; they aren't linked together. The output just uses the rows which pass all filters.
At the top of the program, change iris to a data.table:
library(shinydashboard)
library(data.table)
CustomHeader <- dashboardHeader(title = 'datatest')
iris <- iris
setDT(iris) # Added
Then use this for the server logic:
server <- function(input, output) {
filter1_rows <- reactive({
iris[Species %in% input$filter1, which = TRUE]
})
filter2_rows <- reactive({
iris[Sepal.Length < input$filter2, which = TRUE]
})
filter3_rows <- reactive({
iris[Sepal.Width > input$filter3, which = TRUE]
})
output$mydata <- renderDataTable({
final_rows <- intersect(filter1_rows(), filter2_rows())
final_rows <- intersect(final_rows, filter3_rows())
iris[final_rows]
})
}
This uses the often-overlooked which argument for data.table[...], which means only the row numbers of the subsetted table should be returned.
I think your problem has nothing to do with shiny and/or reactive programming. It's a "classic time vs memory" situation. Basically speaking you have only two options: Store "partially" filtered objects or not.
If you do store them, you use a lot of memory but can return the object instantly. If not, you need only store the original object but you have to filter it everytime again. There is nothing in between. You just cannot create an object that is different from the original (i.e. filtered) but takes no additional memory, not even with reactiveValues.
Of course you can do tradeoffs, e.g. creating an intermediate object for the first filter and computing the second and the third filter on-the-fly, but that does not change the underlying problem.
~~EDIT~~~
The answer below worked, please see my code to be complete and question answered for anyone who needs the help in the future.
~~~~~~~~
I am trying to create a dashboard in R (first one!) that will create a map that shows thousands of routes taken between the package initial location and the package end location (travelling all over the world). I would then like to have filters in order to show different routes based on criteria (and if possible, give a box to tell how many routes are there based on the selection).
I was able to make the dashboard and the map with all the lines and it looks great. The issue now is I cannot seem to figure out how to create the filters to be interactive. My problem is I am currently creating the lines and plotting them in the For Loop so they are not being saved anywhere.
I have three filters: Department (which I call SBU), Manufacturing plant (which I call Plant and is a sub set of SBU), and Customer.
I.e You can have SBU A with all Plants associated with SBU A and look at Customer Y. You will then see those specific routes associated with this.
I.e. You can have SBU B with Plant K and look at all Customers
Unfortunately, I cannot give out the raw data.
Any help would be greatly appreciated as I am very new to R!
library(shiny)
library(shinydashboard)
library(maps)
library(geosphere)
library(maps)
library(mapproj)
library(geosphere)
library(ggrepel)
library(scales)
###########################/ui.R/##################################
#Setting drive where files are located
setwd("C:/R Files")
#Pulling in outside Data Files
Network <- read.csv("Network Codes.csv")
Data <- read.csv("Raw Data2.csv")
#Header
header <- dashboardHeader(
title = "Intake Routes")
#SideBar
sidebar <- dashboardSidebar(
#SBU Selection List
selectInput(inputId = "SBU", "SBU:", selected="ALL",
choices = unique(as.character(Network$SBU))),
#Plant Selection List
uiOutput("Plant"),
#Customer Selection List
selectInput(inputId = "Customer", "Customer:", multiple = TRUE, selected="ALL",
choices = unique(as.character(Data$Customer.Name.Standard))))
#Body
body <- dashboardBody(
plotOutput(outputId = "map")
)
#Builds Dashboard Page
ui <- dashboardPage(header, sidebar, body)
###########################/server.R/###############################
server <- function(input, output) {
##INPUT##
#Dependant Plant SideBar List Dependant on SBU
output$Plant <- renderUI({
selectInput(inputId = "Plant", "Plant:", multiple = TRUE,
choices = as.character(Network[Network$SBU == input$SBU, "Plant.Name"]))
})
#Reactive data set based on inputs
Reactive_Data1 <- reactive({
if (input$SBU == "ALL") {Data}
else {Data[Data$SBU == input$SBU,]}
})
Reactive_Data2 <- reactive({
if (input$Plant == "ALL") {Reactive_Data1()}
else {Reactive_Data1()[Reactive_Data1()$Plant == (input$Plant),]}
})
Reactive_Data3 <- reactive({
if (input$Customer == "ALL") {Reactive_Data2()}
else {Reactive_Data2()[Reactive_Data2()$Customer.Name.Standard == input$Customer,]}
})
output$map <- renderPlot({
#Map coordinates
xlim <- c(-170,170)
ylim <- c(-55,75)
map("world", col="#f2f2f2", fill=TRUE, bg="white", lwd=0.05, xlim=xlim, ylim=ylim)
npoints <- 20
nroutes <- nrow(Data)
for(i in 1:nroutes){
inter <- gcIntermediate(c(Data$Ship.From.Longitude[i],
Data$Ship.From.Latitude[i]),
c(Data$Ship.To.Longitude[i],
Data$Ship.To.Latitude[i]),
n=npoints, addStartEnd = T, breakAtDateLine = T)
if (is.list(inter)) {
inter1 <- inter[[1]]
inter2 <- inter[[2]]
lines(inter1, col = "green", lwd=0.50)
lines(inter2, col = "blue", lwd=0.50)}
else {
lines(inter, col = "grey", lwd=0.50)}
}
})
}
#Combines Dashboard and Data together
shinyApp(ui, server)
You need to make the dataset "reactive" to your inputs and then keep using and referring to that reactive dataset so it updates each time your inputs change.
Here is an example of making a new reactive variable called reactive_dat based on a filtered version of your Data variable.
reactive_dat <- reactive({
Data[Data$SBU == input$SBU, ]
})