organize reactivity with pre-loaded data - r

I have couple of tables stored each in a separate txt file, and also combined in a single Rdat file, something like this:
# generate tables
df.A <- data.frame(colA1=letters[1:20],colA2=LETTERS[1:20], stringsAsFactors = F)
df.B <- data.frame(colB1=rnorm(20),colB2=rnorm(20), stringsAsFactors = F)
# save tables as separate files
write.csv(df.A, 'tableA.txt')
write.csv(df.B, 'tableB.txt')
# I may have more tables
# save all tables in a single Rdat file
save(df.A,df.B, file = 'alldata.RDat')
Now in my shiny markdown document, I want to pre-load those tables from the RDat file, but also give user a possibility to re-load them a) separately from individual files, or b) altogether from the RDat file. Here is the first version I came to:
---
runtime: shiny
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)
```
```{r}
load('alldata.RDat')
```
Tables below:
```{r}
inputPanel(
actionButton('load.A','Reload Table A'),
actionButton('load.B','Reload Table B'),
actionButton('load.Rdat','Load all from Rdat file')
)
tabsetPanel(type = "tabs",
tabPanel("table A", dataTableOutput("toA")),
tabPanel("table B", dataTableOutput("toB"))
)
# next 2 lines render pre-loaded tables before any buttons are pressed:
output$toA <- renderDataTable(df.A, options=list(pageLength=5))
output$toB <- renderDataTable(df.B, options=list(pageLength=5))
observeEvent(input$load.A, {
df.A <- read.csv('tableA.txt');
output$toA <- renderDataTable(df.A, options=list(pageLength=5))
})
observeEvent(input$load.B, {
df.B <- read.csv('tableB.txt');
output$toB <- renderDataTable(df.B, options=list(pageLength=5))
})
observeEvent(input$load.Rdat, {
load('alldata.RDat', verbose = T);
output$toA <- renderDataTable(df.A, options=list(pageLength=5));
output$toB <- renderDataTable(df.B, options=list(pageLength=5))
})
```
It works fine, but I have a feeling it is not the proper way to use reactivity. For example, every time after updating my table object (df.A or dfB), I have to call render explicitly output$toA <- renderDataTable(...) (3 times for each table!).
One solution I had in mind was to put my tables into reactiveValues(), which gave the following:
---
runtime: shiny
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)
```
```{r}
load('alldata.RDat')
```
Tables below:
```{r}
inputPanel(
actionButton('load.A','Reload Table A'),
actionButton('load.B','Reload Table B'),
actionButton('load.Rdat','Load all from Rdat file')
)
tabsetPanel(type = "tabs",
tabPanel("table A", dataTableOutput("toA")),
tabPanel("table B", dataTableOutput("toB"))
)
rv <- reactiveValues(df.A = df.A, df.B = df.B)
output$toA <- renderDataTable(rv$df.A, options=list(pageLength=5))
output$toB <- renderDataTable(rv$df.B, options=list(pageLength=5))
observeEvent(input$load.A, {
rv$df.A <- read.csv('tableA.txt');
})
observeEvent(input$load.B, {
rv$df.B <- read.csv('tableB.txt');
})
observeEvent(input$load.Rdat, {
load('alldata.RDat', verbose = T);
rv$df.A <- df.A;
rv$df.B <- df.B;
})
```
This looks better because I have only one call of each renderDataTable() in my code. But now each of my tables are stored in memory twice - one in df.A (as loaded from 'alldata.RDat') and another one in rv$df.A... Does it make sense at all, or there's a more kosher way to solve these kinds of things?

You would like to load the data for df.A and df.B from either a csv or Rdat file. This almost certainly requires an intermediate variable, rv as you have correctly created.
The second example you have created is much better than the first, we must avoid multiple render calls to the same output, especially render calls inside an observer.
The code below executes load('alldata.RDat', verbose = T) from within the observer and the initial values of rv are set from within the observer instead of using the global load('alldata.RDat', verbose = T). This prevents the data from being loaded twice.
---
runtime: shiny
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)
```
```{r}
#load('alldata.RDat')
```
Tables below:
```{r}
inputPanel(
actionButton('load.A','Reload Table A'),
actionButton('load.B','Reload Table B'),
actionButton('load.Rdat','Load all from Rdat file')
)
tabsetPanel(type = "tabs",
tabPanel("table A", dataTableOutput("toA")),
tabPanel("table B", dataTableOutput("toB"))
)
rv <- reactiveValues(df.A = NULL, df.B = NULL)
output$toA <- renderDataTable({
req(rv$df.A)
rv$df.A
}, options=list(pageLength=5))
output$toB <- renderDataTable({
req(rv$df.B)
rv$df.B
}, options=list(pageLength=5))
observeEvent(input$load.A, {
rv$df.A <- read.csv('tableA.txt');
})
observeEvent(input$load.B, {
rv$df.B <- read.csv('tableB.txt');
})
observeEvent(c(input$load.Rdat), {
load('alldata.RDat', verbose = T);
rv$df.A <- df.A;
rv$df.B <- df.B;
})
```

Related

Edit a dataframe from R shiny

I have been reviewing all sorts of documents all day but I do not see a clear solution to my problem. The issue I am having is that I want to bring in a file locally (.xlsx) into the shiny rmarkdown file. Once the app is hosted, users will be able to go in and edit the dataframe and upon exiting the app, the updated dataframe will reflect the changes made my the user.
---
title: "Component Health Dashboard"
output:
flexdashboard::flex_dashboard:
orientation: rows
social: menu
runtime: shiny
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = F)
library(flexdashboard)
library(tidyverse)
library(readxl)
```
Dashboard
=======================================================================
## Table
```{r cars, echo= F}
cars<-read_xlsx("cars.xlsx")
DT::renderDataTable(cars, editable = T,rownames = F)
```
I have referenced this question r shiny: updating rhandsontable from another rhandsontable & Using Shiny to update dataframe values and access it in the local environment after Shiny session ends, but it only seems to work when a DF is in the global environment for a UI/Server. I feel like there is a much better way of doing this. Any help in pushing me into the correct direction or other idea is greatly appreciated.
Instead of using flex_dashboard, let me suggest you use shiny, something like the following.
library(shiny)
library(DT)
cars <- data.frame(rbind(c(1,2,3), c(4,5,6), c(7,8,9)))
ui <- fluidPage(
mainPanel(
HTML("<br><br><b>Double click on a cell in this table. Enter a number.</b><br><br>"),
DTOutput("table1"),
HTML("<br><br><b>See the change reflected in this table.</b><br><br>"),
DTOutput("table2")
)
)
server <- function(input, output) {
RV <- reactiveValues(data = NULL)
RV$data <- cars
observeEvent(input$table1_cell_edit, {
row <- input$table1_cell_edit$row
clmn <- input$table1_cell_edit$col + 1
val <- input$table1_cell_edit$value
RV$data[row, clmn] <- as.numeric(val)
})
output$table1 <- renderDT({
datatable(RV$data, options = list(dom = 't'), editable = TRUE, rownames = F)
})
output$table2 <- renderDT({
datatable(RV$data, options = list(dom = 't'), editable = FALSE, rownames = F)
})
}
shinyApp(ui = ui, server = server)

Is there a way to have the output of a function in R be an R markdown chunk?

I'm working on a project to make it easier to create flex/shiny dashboards from qualtrics surveys. I'd really like to be able to write a couple functions that would let co-workers who have less experience with R be able to make similar documents without having to know Rmarkdown syntax.
For example, if someone wanted to make a one page dashboard with a scatterplot, I'd like to be able to have them use a couple functions like (make_dashboard, make_page) etc:
make_dashboard(
title = "Qualtrics Report Dashboard",
page 1 = make_page(header = "Page 1", format = "column", render = "plot",
data = survey_data, variables = c("var1", "var2"))
)
which would then create a rmd file with this:
---
title: "Qualtrics Report Dashboard"
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: scroll
runtime: shiny
---
Page 1
=====================================
renderPlot( {
ggplot(data = survey_data, mapping = aes_string(x = var1,
y = var2)) +
geom_point() +
labs(x = get_label(get(var1, survey_data)),
y = get_label(get(var2, survey_data)))
}
)
I haven't gotten very far with trying to write these functions / implement this logic, because I'm not even sure if I'm thinking about it in the right way - is it possible to create rmarkdown chunks with functions like this?
I've looked at other posts 1 and 2 about child documents in knitr, but I don't really want every chunk to be the same, rather have the person be able to change certain aspects (e.g. type of plot, data, etc.).
Not sure if this will be useful to anyone else, but I ended up using whisker (https://github.com/edwindj/whisker), which can render strings into documents to construct an Rmd in the style of flexdashboard.
TLDR: Essentially I made functions that create strings of text matching the building blocks of flexdashboard. With whisker, you can pass in variables by encasing words in the string with two bracket parentheses and then assigning their values with a list of var_name = value for each variable in the string, e.g.
template <- "My name is {{name}}."
d <- list(name = "Emily")
cat(whisker.render(template, data = d))
print(d)
My name is Emily
I used a combination of this and the str_c from stringr to construct strings for different elements of the flexdashboard, allowing the user to input variables like title, variable for plots, etc. that then could be rendered into the string using whisker. Then, I joined all of those strings together and render it into an Rmd file. Honestly, I am not sure this is actually easier for people who don't know R to use, and I'll probably end up doing something different, but I wanted to share in case anyone is thinking about this.
Example: running the chunk below creates a file called "test_dashboard.Rmd" with the text format for a flexdashboard with a 1 input sidebar and a single page with one plot.
```
make_dashboard(title = "Test Dashboard",
sidebar = make_sidebar(sidebar_title = "here is the input",
input_type = "multi-select",
input_name = "Interesting Var #1"),
page1 = make_page(page_title = "Cool Plots!",
element_one = make_plot(plot_title = "this is my plot",
type = "bivariate",
vars = c("cool_var1",
"cool_var2"))),
fn = "test_dashboard")
```
OUTPUT:
```
---
title: Test Dashboard
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: scroll
runtime: shiny
---
\```{r setup, include=FALSE}
library(flexdashboard)
library(tidytext)
library(tidyverse)
library(janitor)
library(DT)
library(gghighlight)
library(knitr)
library(shiny)
library(qualtRics)
library(curl)
library(sjlabelled)
library(naniar)
library(scales)
library(lme4)
library(MASS)
library(snakecase)
\```
\```{r global, include=FALSE}
#setting global options for table scrolling and plot theme
options(DT.options = list(scrollY="100vh"))
theme_set(theme_minimal())
#this fetches all of your survey info
surveys <- all_surveys()
#this saves the survey responses into
docusign_survey <- fetch_survey(surveyID = surveys$id[1],
verbose = TRUE,
label = TRUE,
breakout_sets = TRUE,
force_request = TRUE)
#this saves the question text into a dataframe
questions <- survey_questions(surveyID = surveys$id[1])
rename_df <- rename_variables(docusign_survey)
#this renames all of the variables
docusign_survey <- docusign_survey %>%
rename_at(as.vector(rename_df$old_name), ~ as.vector(rename_df$new_labels))
#new variables
new_var <- rename_df$new_labels
#which are multi_select?
multi_select <- rename_df %>%
filter(ms == 1) %>%
dplyr::select(new_labels)
#relabel those NAs as No
docusign_survey <- docusign_survey %>%
purrr::modify_at(multi_select$new_labels, na_to_y)
\```
Sidebar {.sidebar}
=====================================
here is the input
\```{r}
selectInput("p_var_1", label = "Interesting Var #1",
choices = new_var,
multiple = TRUE)
\```
Cool Plots!
=====================================
Column {.tabset}
-------------------------------------
### this is my plot
\```{r}
renderPlot( {
make_bivariate_plot(docusign_survey, input$cool_var1, input$cool_var2)
})
\```
```
Functions
make_dashboard()
I saved the parts that will repeat every time, probably will want to make them editable for changes in scrolling, etc. but just trying to make proof of concept at the moment.
```
make_dashboard <- function(title, sidebar, page1, fn){
load("data/top_matter.rda")
load("data/libraries.rda")
load("data/main_chunk.rda")
initial_bit <- stringr::str_c(top_matter, libraries, main_chunk, sep = "\n\n")
intermediate <- stringr::str_c(initial_bit, sidebar, sep = "\n\n")
total <- stringr::str_c(intermediate, page1, sep = "\n\n")
data <- list(title = title)
out_fn <- paste0("./", fn, ".Rmd")
writeLines(whisker.render(total, data), con = out_fn)
}
```
make_sidebar()
```
make_sidebar <- function(sidebar_title, input_type, input_name){
top_sidebar <-
'Sidebar {.sidebar}
=====================================
'
sidebar_text <- str_c(top_sidebar, sidebar_title, sep = "\n\n")
if(input_type == "multi-select"){
ms <- "TRUE"
} else {
ms <- "FALSE"
}
input_one <- make_select_input(input_name, ms)
sidebar_total <- str_c(sidebar_text, "```{r}", input_one, "```", sep = "\n\n")
return(sidebar_total)
}
```
make_page()
```
make_page <- function(page_title, element_one){
top_page <-
'{{page_title}}
=====================================
Column {.tabset}
-------------------------------------'
add_element <- stringr::str_c(top_page, element_one, sep = "\n\n")
data <- list(page_title = page_title)
page <- whisker.render(add_element, data = data)
return(page)
}
```
make_plot()
```
make_plot <- function(plot_title, type = c("univariate", "bivariate"), vars){
top_plot_piece <-' {{plot_title}}
\```{r}
renderPlot( {
'
if(type == "univariate"){
plot_piece <-
'make_univariate_plot(docusign_survey, input${{vars}})
})
\```'
total_plot <- stringr::str_c(top_plot_piece, plot_piece, sep = "\n\n")
data <- list(plot_title = plot_title,
vars = vars)
plot_chunk <- whisker.render(total_plot, data = data)
} else{
plot_piece <-
'make_bivariate_plot(docusign_survey, input${{var_1}}, input${{var_2}})
})
\```'
total_plot <- stringr::str_c(top_plot_piece, plot_piece, sep = "\n\n")
data <- list(plot_title = plot_title,
var_1 = vars[1],
var_2 = vars[2])
plot_chunk <- whisker.render(total_plot, data = data)
}
return(plot_chunk)
}
```

In shiny / flexdashboards how can I change the values of columns through an input where multiple=TRUE?

I just can't seem to figure out how to change the value of columns in a dataframe within flexdashboard/shiny when multiple = TRUE. In the example below I can change x and y by selecting them individually. What I want to accomplish is when I have both x and y selected df$x becomes 3 and df$y becomes 2.
But when are both selected only the first one selected is changed.
---
title: "Test"
output:
flexdashboard::flex_dashboard:
orientation: columns
social: menu
source_code: embed
runtime: shiny
---
```
library(flexdashboard)
library(shiny)
library(DT)
df <- data.frame(
x = 1:10,
y = 1:10)
selectInput("var", label = "Change:",
choices = list("no change", "x","y"),
multiple =TRUE,
selected = "no change"
)
renderDataTable({
if(input$var == "no change") {df <- df}
if(input$var %in% "x") {df$x <- 3}
if(input$var %in% "y") {df$y <- 2}
return(df)
})
```
Any help would be much appreciated.
For context, below is snapshot of the output from the code above, on which there is no app output:
Image 0
You were almost there--just missing else before the last two if's, and the corresponding line breaks (similarly, see this).
---
title: "Please remember to make [reprex posts](https://stackoverflow.com/help/minimal-reproducible-example) :)"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(flexdashboard)
library(shiny)
library(DT)
library(tidyverse)
```
## R Markdown
This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.
When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:
```{r now-including-else}
df <- data.frame(
x = 1:10,
y = 1:10)
selectInput("var", label = "Change:",
choices = list("no change", "x","y"),
multiple =TRUE,
selected = "no change"
)
renderDataTable({
if(input$var == "no change") {
df <- df
}
else if(input$var %in% "x") {
df$x <- 3
}
else if(input$var %in% "y") {df$y <- 2}
return(df)
})
```
P.S. Reprex posts.
Some snapshots of the output from the code suggested:
Image 1
Image 2
Image 3

How to pass "reactive" data into R Markdown as a parameter?

I want to be able to pass a reactive data.table to my Rmarkdown document that will sort the table and print the top, say 5, sorted entries.
I keep running into "Error : x must be a data.frame or data.table" at setorderv. I suspect the reactive is upsetting the is.data.table checks.
How do I simply pass a copy of the data to the Rmd document? And remove the reactivity wrapper?
Here is a simplified example cobbled from reactivity and downloadHandler stock examples. I took at look at this on SO which suggested using observe but I am not sure how to implement this. Thanks I appreciate some help on this.
RMARKDOWN - SOtestreport.Rmd
---
title: "Top in Segment Report"
output: html_document
params:
n: 5
dt: NA
top.field: NA
---
```{r setup, echo = FALSE}
library(data.table)
library(knitr) # for kable
```
Data report as at: `r format(Sys.time(), "%a %b %d %Y %X")`
```{r, echo = FALSE}
table.str <- paste0("Showing Top ", params$n ," tables for these fields: ",
params$top.field)
# is this the best way of having dynamic labels?
```
`r table.str`
```{r, echo = FALSE}
# tried this
# test.dt <- copy(dt)
# normally a function GenerateTopTable with limit sort field etc.
setorderv(dt, params$top.field, -1) # -1 is descending. THIS IS WHERE IT CRASHES
out <- dt[1:params$n,]
```
```{r toptable, echo = FALSE}
kable(out, digits = 3)
```
app.R
# SO test report
# mini example based on https://shiny.rstudio.com/gallery/reactivity.html
library(shiny)
library(datasets)
server <- function(input, output) {
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
output$caption <- renderText({
input$caption
})
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
# PROBLEM BIT WHERE I AM ADDING ON GALLERY EXAMPLE
# handler from https://stackoverflow.com/questions/37347463/generate-report-with-shiny-app-and
output$topreport <- downloadHandler(
filename = "topreport.html",
content = function(file) {
# Copy the report file to a temporary directory before processing it, in
# case we don't have write permissions to the current working dir (which
# can happen when deployed).
tempReport <- file.path(tempdir(), "SOtestreport.Rmd")
file.copy("SOtestreport.Rmd", tempReport, overwrite = TRUE)
# Set up parameters to pass to Rmd document
# TRIED THIS TOO
# my.dt <- as.data.table(datasetInput())
my.dt <- datasetInput()
my.params <- list(n = input$obs,
dt = my.dt,
top.field = names(my.dt)[1])
rmarkdown::render(tempReport, output_file = file,
params = my.params,
envir = new.env(parent = globalenv())
)
}
)
I fixed this example - dt in Rmd was not prefaced with params$!
setorderv(params$dt, params$top.field, -1) # -1 is descending
out <- params$dt[1:params$n,]
I still looking for answers on whether this is the right approach above (in passing parameters in Shiny to RMarkdown apps).
I also want to point out to others that these answers were also useful:
use of ReactiveValues by #NicE https://stackoverflow.com/a/32295811/4606130
What's the difference between Reactive Value and Reactive Expression?
I also made use of copying the dataset to break the reactivity, i.e.
my.dt <- copy(params$dt)
#do this before passing into render params list
Another tip in RStudio is not to use the internal Viewer Pane but to run externally in the browser. This fixed where the temporary file was saved issues. You can choose to always Run Externally under Run App in image below:

How can I build multiple inputs into my shiny app with both updating and multiple selection?

I am building a flex dashboard / shiny app with a datatable and trying to build in two inputs as selections for this datatable with an "All" choice for each selection. First question is how can I limit the second selection "user" by the selection of the first choice "team"?
Then, using these inputs, I'd like to subset my data to any combination of the two selections ex. Team All, user "Darwin D" would return a single line datatable with his name, team and other metrics to be added etc.
All code for a flex markdown document below:
---
title: "example"
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: fill
runtime: shiny
---
```{r setup, include=FALSE}
library(shiny)
library(shinydashboard)
library(flexdashboard)
library(magrittr)
library(feather)
library(anytime)
library(data.table)
library(DT)
library(datasets)
Name <- c("Allan A","Barbara B","Charles C","Darwin D","Evelyn E","Frank F","Greg G","Hans H")
Team <- c(1,2,3,3,2,1,2,2)
users <- data.frame(Name,Team)
remove(Name,Team)
```
Inputs {.sidebar}
=======================================================================
### Input Variables
```{r global_input}
# input variable to call selection, name of field, selections/options variable
dateRangeInput('dateRange',
label = 'Date range input: yyyy-mm-dd',
start = Sys.Date() - 8,
end = Sys.Date() - 1,
min = "2013-01-01",
max = Sys.Date() -1
)
selectInput("teaminput","Team", c("All",unique(users$Team)))
observe({
if( input$teaminput == "All" ) {
subDT <- copy( users )
} else {
subDT <- users[ users$Team == input$teaminput, ]
}
updateSelectInput(
"userinput",
label = "User Name",
choices = c( "All", unique(subDT$Name ) )
)
})
```
### Intake Coordinator KPIs
```{r daily_table}
# reactive data object based on inputs above
daily_dt <- reactive({
if(input$teaminput == "All"){
subDT
} else{
subset(subDT$Team == input$teaminput)
}
})
# render DT datatable object with sorts/search
renderDataTable(daily_dt())
```
You might want to use 2 reactive, the first one to filter the data.frame by Team, the second to filter the result of the first one by Name :
---
title: "example"
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: fill
runtime: shiny
---
```{r setup, include=FALSE}
library(DT)
users <- data.frame(
Name = c("Allan A","Barbara B","Charles C","Darwin D","Evelyn E","Frank F","Greg G","Hans H"),
Team = c(1,2,3,3,2,1,2,2), stringsAsFactors = FALSE)
```
Inputs {.sidebar}
=======================================================================
### Input Variables
```{r global_input}
selectInput("teaminput","Team",c("All", unique(users$Team)), selected="All")
selectInput("userinput","User Name", c("All", unique(users$Name) ), selected="All")
teamFiltered <- reactive(users[input$teaminput=="All" | users$Team==input$teaminput,])
observe(updateSelectInput(session,"userinput", choices = c("All", unique(teamFiltered()$Name)), selected="All"))
```
Results
=======================================================================
### Intake Coordinator KPIs
```{r daily_table}
userFiltered <- reactive(teamFiltered()[input$userinput=="All" | teamFiltered()$Name==input$userinput,])
renderDataTable(userFiltered())
```
Note I can't test this unless you provide a reproducible example, but something along these lines should work. You need a reactive function in your server, which includes a subsetting step, and an updateSelectInput call. This will update the input in your ui any time the reactive is triggered.
observe({
if( input$team == "All" ) {
subDT <- copy( DT )
} else {
subDT <- DT[ Team == input$team, ]
}
updateSelectInput(
"user",
label = "User Name",
choices = c( "All", unique( subDT$Name ) )
)
})
So any time input$team is changed, we create a subset based on that selection, and use that subset to update the user input field.

Resources