Deploying R Markdown File with Shiny Component - r

Building off my previous question, I'm having trouble creating a DEPLOYABLE R Markdown file that has a Shiny component JUST to allow user to upload an Excel workbook.
Essentially, I want to run Python code on top of the data that the user provides.
This doesn't seem to deploy correctly to RConnect (it just times out):
---
output: html_document
---
library(dplyr)
library(miniUI)
library(shiny)
library(XLConnect)
launch_shiny <- function() {
ui <- miniPage(
gadgetTitleBar("Input Data"),
miniContentPanel(
fileInput(inputId = "my.file", label = NULL,
multiple = FALSE)
)
)
server <- function(input, output, session) {
wb <- reactive({
new.file <- input$my.file
loadWorkbook(
filename = new.file$datapath,
create = FALSE,
password = NULL
)
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
df_lst <- reactive({
# read all sheets into a list
lapply(getSheets(wb()),
function(sheet){
readWorksheet(object = wb(),
sheet = sheet)
})
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
observeEvent(input$done, {
# get the list of dfs from the app
stopApp(c(df_lst()))
})
}
runGadget(ui, server)
}
test <- launch_shiny()
If I add runtime: shiny at the beginning, it would fail as runGadget uses runApp, which is also used by shiny; however, I need to make sure that I can return a variable (in this case, list of data frames) to the global environment, which is why I was using stopApp.
What are my options at this point?

Related

How run a shiny app inside of a function?

I would like to run a function that has a shiny app inside, but I can't.
Running this example separately, I first remove column one from my input data frame; then I run shiny to change whatever is necessary in the data frame and, when I close the window, a new object is saved with the changes; and finally I create a new column in the data frame.
This is an example script, but I would like that, when executing the function, the shiny window opens and some things are changed in the data frame for the user interactively. Could someone help?
library(shiny)
library(rhandsontable)
my_function <- function(x){
select <- x[,-1]
ui <- fluidPage(
fluidRow(
column(
width = 12,
rHandsontableOutput("myTable")
)))
server <- function(input, output, session) {
# dummy dataframe
df = select
# convert it to a "rhansontable" object
output$myTable <- renderRHandsontable({rhandsontable(df)
})
observeEvent(input$myTable, {
test_df = hot_to_r(input$myTable)
assign('my_data_frame',test_df,envir=.GlobalEnv)
# browser() # uncomment for debugging
})
}
shinyApp(ui, server)
my_data_frame2 <- my_data_frame %>%
mutate(new_column_test = "hello")
return(my_data_frame2)
}
my_function(mtcars)
Hi you almost made it you don't want to return anything but add the data simply using assign
library(shiny)
library(rhandsontable)
myapp_function <- function(data) {
ui <- basicPage(
actionButton("quit", label = "Close"),
actionButton("create", label = "Create copy"),
textInput("name","Set dataframe name", value = "my_data_frame"),
rHandsontableOutput("myTable")
)
server <- function(input, output, session) {
output$myTable <- renderRHandsontable({
rhandsontable(data)
})
observeEvent(input$create, {
assign( input$name, hot_to_r(input$myTable), envir=.GlobalEnv)
})
observeEvent(input$quit,{
stopApp()
})
}
## launch app
shinyApp(ui, server,options=c(shiny.launch.browser = .rs.invokeShinyPaneViewer))
}
## test
myapp_function(iris)
myapp_function(mtcars)
myapp_function(PlantGrowth)
I would suggest to create the ui and server outside of the myapp_function - otherwise it will become a very large function...also creating a function inside another function is not the best practise.

Shiny - importing and exporting without user interaction by only specifying one file and its path

All the shiny tutorials I see import multiple data manually via fileInput() then export manually.
Currently, I just have a single R script files that I manually change the few variables each time I run it.
For example, at directory C:/Users/Users/Project/000-0000, I want to update 000-0000_result1 and 000-0000_result2 using information from 000-0000_NewData.
#### Variables I change
file_name <- "C:/Users/Users/Project/000-0000/000-0000_NewData.csv"
parameterNum <- 3
#### Rest of the codes that I never change
setwd(dirname(file_name)
projectID <- str_extract(file_name, "[^_]+") #would be 000-0000 in this case
dat0 <- read_csv(file_name)
prev_result1 <- read_csv(str_c(projectID, "_result1"))
prev_result2 <- read_csv(str_c(projectID, "_result2"))
... #data step using parameterNum
write_csv(new_result1, str_c(projectID, "_result1"))
write_csv(new_result2, str_c(projectID, "_result2"))
I want to create a Shiny app where I can just specify the file_name with fileInput("dat0","Upload a new data") and numericInput() then run the rest of the script.
I do not want to manually select multiple files then export them, because I have a lot of _result files mixed with other files sharing the same filetypes.
I was looking at input$dat0$datapath but it seems that shiny creates a tmp folder with only files loaded through fileInput()
Is my plan possible using Shiny? I am using flexdashboard, but I also welcome and will try to adjust standard Shiny answer on my own.
Perhaps something like this:
library(shiny)
library(tidyverse)
ui <- fluidPage(
textInput('file_name', 'Path to filename', value = "C:/Users/Users/Project/000-0000/000-0000_NewData.csv"),
numericInput('parameterNum', 'Insert Parameter Number',value = 3, min = 0),
actionButton(inputId = 'save', label = 'Write csvs')
)
server <- function(input, output, session) {
observe({
setwd(dirname(input$file_name))
})
projectID <- reactive({
str_extract(inpt$file_name, "[^_]+")
})
prev_result1 <- reactive({
read_csv(str_c(projectID(), "_result1"))
#some calculation
})
prev_result2 <- reactive({
read_csv(str_c(projectID(), "_result2"))
#some calculation
})
observeEvent(input$save, {
write_csv(prev_result1(), str_c(projectID(), "_result1"))
write_csv(prev_result2(), str_c(projectID(), "_result2"))
})
}
shinyApp(ui, server)

Passing reactive data to global environment

I want to use Shiny within RMarkdown for users to upload data (xlsx file).
Then I want to pass all the worksheets as R data frames (w/o reactivity) to run rest of the RMarkdown file.
I mainly want to convert them into data frames so I can use reticulate to run Python code as well.
I've tried this, and it doesn't seem to quite work:
library(dplyr)
library(miniUI)
library(shiny)
library(XLConnect)
launch_shiny <- function() {
ui <- miniPage(
gadgetTitleBar("Input Data"),
miniContentPanel(
fileInput(inputId = "my.file", label = NULL, multiple = FALSE)
)
)
server <- function(input, output, session) {
wb <- reactive({
new.file <- input$my.file
loadWorkbook(
filename = new.file$datapath,
create = FALSE,
password = NULL
)
})
observeEvent(input$done, {
stopApp(c(wb()))
})
}
runGadget(ui, server)
}
test <- launch_shiny()
df1 <- readWorksheet(object = test, sheet = "sheet1")
df2 <- readWorksheet(object = test, sheet = "sheet2")
It throws this error:
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘readWorksheet’ for signature ‘"list", "character"’
I can return one sheet at a time using stopApp(readWorksheet(object = wb(), sheet = "sheet1")), but I can't seem to return an entire workbook or multiple data frames at the same time.
I don't really want to read in xlsx, save each sheet as csv in working directory, then read those files in again.
Would anyone have a good suggestion on how to get around this?
The documentation of fileInput() states in the details:
datapath
The path to a temp file that contains the data that was
uploaded. This file may be deleted if the user performs another upload
operation.
Meaning that the datapath given in the input variable is a temporary file that is no longer accessible after you close the App, which is what the function readWorksheet will try to do.
So you'll have to read the sheets in the server and return the dataframes somehow.
I did that by defining a second reactive value which is basically a list of dataframes returned by applying lapply on all the sheets in wb, in this case test will be this list of data frames.
There might be other ways (more efficient, or suits your purpose better) to do this, but here it is:
library(dplyr)
library(miniUI)
library(shiny)
library(XLConnect)
launch_shiny <- function() {
ui <- miniPage(
gadgetTitleBar("Input Data"),
miniContentPanel(
fileInput(inputId = "my.file", label = NULL,
multiple = FALSE)
)
)
server <- function(input, output, session) {
wb <- reactive({
new.file <- input$my.file
loadWorkbook(
filename = new.file$datapath,
create = FALSE,
password = NULL
)
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
df_lst <- reactive({
# read all sheets into a list
lapply(getSheets(wb()),
function(sheet){
readWorksheet(object = wb(),
sheet = sheet)
})
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
observeEvent(input$done, {
# get the list of dfs from the app
stopApp(c(df_lst()))
})
}
runGadget(ui, server)
}
test <- launch_shiny()

Is there a way to run arbitrary code on objects created in a R Shiny app?

My users would like to run some R scripts using the objects that my Shiny App creates. E.g. if my app creates a new data frame, they would like to run their own analysis using the new data frame.
Is there a way to do that?
Maybe some console-like (interactive) feature in R Shiny?
I found this Access/use R console when running a shiny app, but wondering if there is any other way to do it besides building your own server.
Any input is great appreciated. Thank you!
Here is an example of a very basic console on Shiny. It is based on Dean Attali's code here. The idea is to execute arbitrary code from a textInput with the eval function using the same environment that shiny is using. To test the idea, the variable myDat was created inside the server function and can be used by the user. It should also work with other objects created later. I also enabled the "Enter" key to press the [Run] button using JavaScript, so you don't need click on the button.
It is recommended to enable this console only to trusted users, it is a complete open access to any R command and can be potentially a serious security issue.
library(shiny)
ui <- fluidPage(
# enable the <enter> key to press the [Run] button
tags$script(HTML(
'$(document).keyup(function(event) {
if (event.keyCode == 13) {
$("#run").click();
}
});'
)),
textInput("expr", label = "Enter an R expression",
value = "myDat"),
actionButton("run", "Run", class = "btn-success"),
div( style = "margin-top: 2em;",
uiOutput('result')
)
)
server <- function(input, output, session) {
shinyEnv <- environment()
myDat <- head(iris)
r <- reactiveValues(done = 0, ok = TRUE, output = "")
observeEvent(input$run, {
shinyjs::hide("error")
r$ok <- FALSE
tryCatch(
{
r$output <- isolate(
paste(
capture.output(
eval(parse(text = input$expr), envir = shinyEnv)
),
collapse = '\n'
)
)
r$ok <- TRUE
}
,
error = function(err) {
r$output <- err$message
}
)
r$done <- r$done + 1
})
output$result <- renderUI({
if (r$done > 0 ) {
content <- paste(paste(">", isolate(input$expr)), r$output, sep = '\n')
if (r$ok) {
pre(content)
} else {
pre( style = "color: red; font-weight: bold;", content)
}
}
})
}
shinyApp(ui = ui, server = server)
If you want to make a data frame available to the user in the global environment after running the app, you can use assign(). The following example uses the logic of a shiny widget that can be added as an add-in to RStudio:
shinyApp(
ui = fluidPage(
textInput("name","Name of data set"),
numericInput("n","Number observations", value = 10),
actionButton("done","Done")
),
server = function(input, output, session){
thedata <- reactive({
data.frame(V1 = rnorm(input$n),
V2 = rep("A",input$n))
})
observeEvent(input$done,{
assign(input$name, thedata(), .GlobalEnv)
stopApp()
})
}
)
Keep in mind though that your R thread is continuously executing when a shiny app is running, so you only get access to the global environment after the app stopped running. This is how packages with a shiny interface deal with it.
If you want users to be able to use that data frame while the app is running, you can add a code editor using eg shinyAce. A short example of a shiny App using shinyAce to execute arbitrary code:
library(shinyAce)
shinyApp(
ui = fluidPage(
numericInput("n","Number observations", value = 10),
aceEditor("code","# Example Code.\n str(thedata())\n#Use reactive expr!"),
actionButton("eval","Evaluate code"),
verbatimTextOutput("output")
),
server = function(input, output, session){
thedata <- reactive({
data.frame(V1 = rnorm(input$n),
V2 = rep("A",input$n))
})
output$output <- renderPrint({
input$eval
return(isolate(eval(parse(text=input$code))))
})
}
)
But the package comes with some nice examples, so take a look at those as well.

Calling the filename from a reactive dataset in shiny r

I am currently writing a shiny app which imports a dataset and displays a manipulated version. To work on the shiny methods I am currently working on a simplified version which displays the imported dataset. I currently assign the imported dataset to a reactive value, and then use the render table as follows:-
shinyServer(function(input, output) {
DATA<-reactive({
input$filein
})
output$Dataset <- renderTable({
DATA()
})
})
The interface then produces a table with the following columns:-
name, size, type, datapath.
What I had in mind was to call the datapath variable, and use read.csv to call it within the renderTable function. I tried using:-
DATA()$datapath
However that doesn't seem to produce any result. Are there any other ways to extract this data within Shiny? I contemplated using vector indices as you would using regular R code however I am unsure as to whether or not that'll work within Shiny.
Here is an example for files in the current working directory. The example file I used was a minimal csv file (see bottom). Please note however that this is indeed limited to files in your working directory. If you want other files to be loaded you will need to have a further component to specify the path (possibly in the selectInput).
library(shiny)
library(tools)
runApp(
list(
ui = pageWithSidebar(
headerPanel("File Info Test"),
sidebarPanel(
p("Demo Page."),
selectInput("filein", "Choose File", choices=c("test.csv"))
),
mainPanel(
tableOutput("myTableInfo"),
tableOutput("myTable")
)
),
server = function(input, output){
mydata <- reactive({
read.csv(input$filein)
})
file_info <- reactive({
validate(
need(!is.null(input$filein), "please select file"
)
)
name <- input$filein
size <- file.info(input$filein)[['size']]
type <- file_ext(input$filein)
datapath <- file_path_as_absolute(input$filein)
cbind(name, size, type, datapath)
})
output$myTableInfo <- renderTable({
file_info()
})
output$myTable <- renderTable({
mydata()
})
}
)
)
test.csv
X1,X2,X3
1,2,3
4,5,6

Resources