How to pass table and plot in Shiny app as parameters to R Markdown?

柔情痞子 提交于 2020-12-04 02:35:52

问题


In this Shiny app, the user can upload a .csv file, get the results as a table and plot. I want to be able to download the results as PDF document.

Input file

#I created the input .csv file to be used in the app from diamonds data.frame
library(ggplot2)
df <-  diamonds[1:5000, ]
head(df)
write.csv(df, "df.csv")

App

library(tidyverse)
library(shiny)
library(rmarkdown)
library(knitr)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(fileInput("file","Upload your file"), 
                 width =2),
    mainPanel(
      width = 10,
      downloadButton("report", "Download report"),
      tableOutput("table"),
      tags$br(),
      tags$hr(),
      plotOutput("plot1"), 
      tags$br(),
      tags$hr(),
      plotOutput("plot2")
    )
  )
)

server <- function(input,output){

  data <- reactive({
    file1 <- input$file
    if(is.null(file1)){return()} 
    read.csv(file1$datapath, header=TRUE, sep=',')
    })


  output$table <- renderTable({
    if (is.null(data())) { return() }

      df <- data() %>% 
      dplyr::select(cut, color, price) %>% 
      dplyr::group_by(cut, color) %>% 
      dplyr::summarise_all(funs(min(.), mean(.), median(.),max(.),sd(.), n() ))  
  })  

  table_rmd <- reactive({
    df <- data() %>% 
      dplyr::select(cut, color, price) %>% 
      dplyr::group_by(cut, color) %>% 
      dplyr::summarise_all(funs(min(.), mean(.), median(.),max(.),sd(.), n() )) 
  })

  output$plot1 <- renderPlot({
    if (is.null(data())) { return() }

    ggplot(data(), aes (x =carat, y = price, col = color))+
      geom_point()+
      facet_wrap(~cut)
    }
  )

  plot_rmd <- reactive({
   chart <- ggplot(data(), aes (x =carat, y = price, col = color))+
      geom_point()+
      facet_wrap(~cut)
   chart
  }
  )

    #https://shiny.rstudio.com/articles/generating-reports.html
    output$report <- downloadHandler(
      filename = "report.pdf",
      content = function(file) {
        tempReport <- file.path(tempdir(), "report.Rmd")
        file.copy("report.Rmd", tempReport, overwrite = TRUE)

        params <- list(table1 = table_rmd(),
                       plot1 = plot_rmd())

        rmarkdown::render(tempReport, output_file = file,
                          params = params,
                          envir = new.env(parent = globalenv())
        )
      }
    )
}  


shinyApp(ui=ui, server = server)

report.Rmd

---
title: "Dynamic report"
output: pdf_document

params:
  table1: NA
  plot1: NA

---


This is the firs plot 

```{r}
params$plot1
```

This is the first table

```{r}
kable(params$table1)
```

I have tried different ways to pass the table and the plot from Shiny as params to R Markdown but none worked.

I will highly appreciate your suggestions to fix this.

Update

I have tried @BigDataScientist's answer and I got this error

"C:/Program Files/RStudio/bin/pandoc/pandoc" +RTS -K512m -RTS report.utf8.md --to latex --from markdown+autolink_bare_uris+ascii_identifiers+tex_math_single_backslash --output pandoc20e043232760.tex --template "C:\PROGRA~1\R\R-35~1.2\library\RMARKD~1\rmd\latex\DEFAUL~3.TEX" --highlight-style tango --pdf-engine pdflatex --variable graphics=yes --variable "geometry:margin=1in" --variable "compact-title:yes" Warning: Error in : Failed to compile C:\Users\user\AppData\Local\Temp\RtmpYvWn8M\file20e042326267.tex. See https://yihui.name/tinytex/r/#debugging for debugging tips. [No stack trace available]

Here is the sessionInfo()

> sessionInfo()
R version 3.5.2 (2018-12-20)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

Matrix products: default

locale:
[1] LC_COLLATE=English_New Zealand.1252  LC_CTYPE=English_New Zealand.1252    LC_MONETARY=English_New Zealand.1252 LC_NUMERIC=C                        
[5] LC_TIME=English_New Zealand.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2.2  forcats_0.3.0   stringr_1.4.0   dplyr_0.7.8     purrr_0.2.5     readr_1.3.1     tidyr_0.8.2     tibble_2.0.1    tidyverse_1.2.1 ggplot2_3.1.0  
[11] shiny_1.2.0    

loaded via a namespace (and not attached):
 [1] tinytex_0.15.2   tidyselect_0.2.5 xfun_0.9         haven_2.0.0      lattice_0.20-38  colorspace_1.4-0 generics_0.0.2   htmltools_0.3.6  yaml_2.2.0      
[10] utf8_1.1.4       rlang_0.4.0      later_0.8.0      pillar_1.3.1     glue_1.3.0       withr_2.1.2      readxl_1.2.0     modelr_0.1.2     bindr_0.1.1     
[19] plyr_1.8.4       cellranger_1.1.0 munsell_0.5.0    gtable_0.2.0     rvest_0.3.2      evaluate_0.12    labeling_0.3     knitr_1.21       httpuv_1.4.5.1  
[28] fansi_0.4.0      broom_0.5.1      Rcpp_1.0.0       xtable_1.8-3     promises_1.0.1   scales_1.0.0     backports_1.1.3  jsonlite_1.6     mime_0.6        
[37] hms_0.4.2        digest_0.6.18    stringi_1.2.4    grid_3.5.2       cli_1.0.1        tools_3.5.2      magrittr_1.5     lazyeval_0.2.1   crayon_1.3.4    
[46] pkgconfig_2.0.2  xml2_1.2.0       rsconnect_0.8.13 lubridate_1.7.4  assertthat_0.2.0 rmarkdown_1.11   httr_1.4.0       rstudioapi_0.9.0 R6_2.3.0        
[55] nlme_3.1-137     compiler_3.5.2 

回答1:


You can pass an R object as part of the params list to a parametrized Rmd. Here is an example in a regular interactive session (no Shiny). report.Rmd is the same as in the question. df and pl are generated by some R script and are available in your environment. Then you can wrap them in list and pass them as params to render.

library(rmarkdown)
library(ggplot2)

df <- head(iris)
pl <- ggplot(iris, aes(x = Sepal.Width)) + geom_histogram(color = "white")

render(
  input = "report.Rmd",
  params = list("table1" = df, "plot1" = pl),
  output_file = "rendered-from-session.pdf"
)

Screen shot of rendered-from-session.pdf

Whether this is a good strategy is another question. If the code making the tables and plots is the same for the app and Rmd, then it is better to have that in a separate script sourced both by the app and Rmd. That way you only need to edit one document when you want to change table/plot code. In this case, you would pass the arguments to plotting functions rather than the plots themselves. The downside of this is if you have many different plots that require many different parameters to keep track of. Or if the calculations / plotting takes long, so you don't want to do it twice.

Coming back to your Shiny app. In fact, your app code works for me as is through Shiny as well. I can make the pdf (even though table_rmd() reactive is not explicitly returning df). So it most likely is an issue with pandoc or latex not figuring out where the temporary file/folder is. Since you are still testing, I would try to save to a known location rather than a tempdir to see if this isn't some kind of permissions issue.

You could modify your handler like so. Remove the calls to tempdir and give the full path to report.Rmd to render. You could also try to give a full path for output_file. The first case should work just fine. In the second case ("PATH/TO/OUTPUT" instead of file passed to output_file), the browser might give you a download error, but the pdf should still render in the background with the file name you provided.

  output$report <- downloadHandler(
    filename = "report.pdf",
    content = function(file) {
      # tempReport <- file.path(tempdir(), "report.Rmd")
      # file.copy("report.Rmd", tempReport, overwrite = TRUE)

      params <- list(table1 = table_rmd(),
                     plot1 = plot_rmd())

      rmarkdown::render(input = "PATH/TO/report.Rmd", 
                        output_file = file,
                        params = params,
                        envir = new.env(parent = globalenv())
      )
    }
  )

This might at least confirm that your code is working, except for the tempdir() stuff. If you have an option, try your app on a linux machine or a mac.




回答2:


Not sure if you can pass table and plots as parameters in/to rmarkdown. (Please also consider teofil´s answer here).

All standard R types that can be parsed by yaml::yaml.load() can be included as parameters, including character, numeric, integer, and logical types.

Source: https://bookdown.org/yihui/rmarkdown/params-declare.html

So you have two options.

  • Pass the relevant data as a parameter and include the code to produce the plot/table in the markdown template (here: report.Rmd).
  • if, for some reason, one would insist on defining the code for the plot/table on the shiny side you could send this code as a character and use eval(parse(text = params$...)) to evaluate it on rmarkdown side.

I could imagine you dont want to specify the code for plot/table twice in shiny + rmarkdown, but i guess you would have to choose between both options and the first one is probably cleaner. (But feel free to leave the question open in case someone else has another idea).

Reproducbile example: (including an example for both options - on the basis of your code)

report.Rmd

---
title: "Dynamic report"
output: pdf_document
params:
  plotData: NA
  tableData: NA
  plotCode: NA
---

```{r}
params$tableData
```

```{r}
eval(parse(text = params$plotCode))
```


```{r}
library(ggplot2)
ggplot(params$plotData, aes (x = carat, y = price, col = color)) +
  geom_point() +
  facet_wrap(~cut)
```

app.R

library(shiny)
library(ggplot2)

df <-  diamonds[1:5000, ]
head(df)
write.csv(df, "df.csv")

#setwd("....") #be sure to be in same directory
shinyApp(
  ui = fluidPage(
    sliderInput(inputId = "slider", label = "Slider", min = 1, max = 100, value = 50),
    fileInput(inputId = "file", label = "Upload your file"),
    downloadButton(outputId = "report", label = "Generate report")
  ),
  server = function(input, output) {
    data <- reactive({
      file1 <- input$file
      if(is.null(file1)){return()}
      read.csv(file1$datapath, header = TRUE, sep = ',')
    })

    table_rmd <- reactive({
      data() %>% 
        dplyr::select(cut, color, price) %>% 
        dplyr::group_by(cut, color) %>% 
        dplyr::summarise_all(funs(min(.), mean(.), median(.),max(.),sd(.), n() )) 
    })


    output$report <- downloadHandler(
      filename = "report.pdf",
      content = function(file) {

        tempReport <- file.path(tempdir(), "report.Rmd")
        file.copy("report.Rmd", tempReport, overwrite = TRUE)

        params <- list(plotData = data(), tableData = table_rmd(), plotCode = "plot(1)")

        rmarkdown::render(tempReport, output_file = file,
                          params = params,
                          envir = new.env(parent = globalenv())
        )
      }
    )
  }
)


来源:https://stackoverflow.com/questions/57802225/how-to-pass-table-and-plot-in-shiny-app-as-parameters-to-r-markdown

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!