将数据框作为参数从 Shiny 应用程序传递到 RMarkdown

Passing a dataframe as a parameter from Shiny app to RMarkdown

我仍在努力解决我正在开发的 Shiny 应用程序的某些方面。目的是用户上传数据的 csv 文件,然后处理生成报告(从 .Rmd 模板),然后用户可以将其下载为可编辑的 Word .doc。

如果我在普通 R session 中渲染 .Rmd,它工作正常。但是,如果从我的 Shiny 应用程序完成,我会收到以下错误:


Warning: Error in unique: object 'report.data' not found
  [No stack trace available]

report.data 应该是通过读取输入 .csv 文件生成的数据帧。令人困惑的是,该应用程序有时确实有效(我认为如果 report.data 在全局环境中已经可用,就会发生这种情况。)。

我尝试在 .Rmd 文件的 header 中定义参数(请参阅下面注释掉的行。)- 如果我这样做,则代码运行时没有错误,但结果是文档是空白的,除了标题。

谁能看出我错在哪里?一如既往地感谢您花时间阅读并回复。

抱歉,我觉得我正在发很多帖子寻求帮助,这些问题在 Shiny 中似乎是非常基本的事情,但我确实搜索过类似的问题,但从来没有找到完全正确的事情!但是一旦我有了这些基本的东西,我应该能够自己取得进步。

为 report.data 生成 .csv 示例输入文件的代码:

library(dplyr)
set.seed(1234)

product1.parameter1.location1 <- data.frame(
  result = rnorm(25, mean = 2.5, sd = 0.2), 
  product = c("Red Aeroplanes"), 
  parameter = c("Parameter 1"), 
  sample.no = c(1:25), 
  location = c("Factory 1")
  )

product1.parameter1.location2 <- data.frame(
  result = rnorm(25, mean = 2.6, sd = 0.1), 
  product = c("Red Aeroplanes"), 
  parameter = c("Parameter 1"), 
  sample.no = c(1:25), 
  location = c("Factory 2")
  )

product1 <- rbind(product1.parameter1.location1, product1.parameter1.location2)

product2.parameter1.location1 <- data.frame(
  result = rnorm(25, mean = 10, sd = 2), 
  product = c("Blue Trollies"), 
  parameter = c("Parameter 1"), 
  sample.no = c(1:25), 
  location = c("Factory 1")
  )

product2.parameter1.location2 <- data.frame(
  result = rnorm(25, mean = 9.5, sd = 0.75), 
  product = c("Blue Trollies"), 
  parameter = c("Parameter 1"), 
  sample.no = c(1:25), 
  location = c("Factory 2"))
product2.parameter1 <- rbind(product2.parameter1.location1, product2.parameter1.location2)

product2.parameter2.location1 <- data.frame(
  result = rnorm(25, mean = 30, sd = 1.8), 
  product = c("Blue Trollies"), 
  parameter = c("Parameter 2"), 
  sample.no = c(1:25), 
  location = c("Factory 1")
  )

product2.parameter2.location2 <- data.frame(
  result = rnorm(25, mean = 25, sd = 0.75), 
  product = c("Blue Trollies"), 
  parameter = c("Parameter 2"), 
  sample.no = c(1:25), 
  location = c("Factory 2"))
product2.parameter2 <- rbind(product2.parameter2.location1, product2.parameter2.location2)

product2 <- rbind(product2.parameter1, product2.parameter2)

product3.parameter1.location1 <- data.frame(
  result = rnorm(35, mean = 2, sd = 0.2), 
  product = c("Brown Carriages"), 
  parameter = c("Parameter 1"), 
  sample.no = c(1:35), 
  location = c("Factory 1")
)

product3.parameter1.location2 <- data.frame(
  result = rnorm(35, mean = 1.9, sd = 0.15), 
  product = c("Brown Carriages"), 
  parameter = c("Parameter 1"), 
  sample.no = c(1:35), 
  location = c("Factory 2"))
product3.parameter1 <- rbind(product3.parameter1.location1, product3.parameter1.location2)

product3.parameter2.location1 <- data.frame(
  result = rnorm(35, mean = 4, sd = 0.4), 
  product = c("Brown Carriages"), 
  parameter = c("Parameter 2"), 
  sample.no = c(1:35), 
  location = c("Factory 1")
)

product3.parameter2.location2 <- data.frame(
  result = rnorm(35, mean = 3.8, sd = 0.5), 
  product = c("Brown Carriages"), 
  parameter = c("Parameter 2"), 
  sample.no = c(1:35), 
  location = c("Factory 2"))

product3.parameter2 <- rbind(product3.parameter2.location1, product3.parameter2.location2)

product3.parameter3.location1 <- data.frame(
  result = rnorm(35, mean = 10, sd = 1.8), 
  product = c("Brown Carriages"), 
  parameter = c("Parameter 3"), 
  sample.no = c(1:35), 
  location = c("Factory 1")
)

product3.parameter3.location2 <- data.frame(
  result = rnorm(35, mean = 10, sd = 2), 
  product = c("Brown Carriages"), 
  parameter = c("Parameter 3"), 
  sample.no = c(1:35), 
  location = c("Factory 2"))

product3.parameter3 <- rbind(product3.parameter3.location1, product3.parameter3.location2)

product3 <- rbind(product3.parameter1, product3.parameter2, product3.parameter3)

write.csv(product1, "product1.csv", row.names = FALSE)
write.csv(product2, "product2.csv", row.names = FALSE)
write.csv(product3, "product3.csv", row.names = FALSE)

report.data <- rbind(product1, product2, product3) %>% mutate(identifier = paste(product, parameter, sep = " ")) 
write.csv(report.data, "all.data.csv", row.names = FALSE)

app.R代码:

#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
#    http://shiny.rstudio.com/
#

library(shiny)

# Define UI for application that draws a histogram
ui <- fluidPage(

    # Application title
  titlePanel("R Shiny app"),

  # Sidebar with file input
  sidebarLayout(
    sidebarPanel(
      fileInput(
        inputId = "file1",
        label = "Select file(s)",
        multiple = TRUE,
        accept = NULL,
        width = NULL,
        buttonLabel = "Browse...",
        placeholder = "No file(s) selected"
      ),
      downloadButton("report", "Generate report")
    ),

        # Show a plot of the generated distribution
        mainPanel(
           plotOutput("distPlot")
        )
    )
)

# Define server logic required to draw a histogram
server <- function(input, output) {
  
  output$report <- downloadHandler(
    reactive(file <- input$file1),
    # For PDF output, change this to "report.pdf"
    filename = "report.doc",
    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(), "wordreport.Rmd")
      file.copy("wordreport.Rmd", tempReport, overwrite = TRUE)
      # Knit the document, passing in the `params` list, and eval it in a
      # child of the global environment (this isolates the code in the document
      # from the code in this app).
      params <- list(report.data = input$file1)
      rmarkdown::render(tempReport, output_file = "wordreport.doc",
                        params = params,
                        envir = new.env(parent = globalenv()))
      file.copy("wordreport.doc", file)
    }
    )

}

# Run the application 
shinyApp(ui = ui, server = server)

.Rmd 文件(与参数声明相关的行被注释掉):

---
title: "Comparison Report  for [CATEGORY] in [MONTH/YEAR]"
output: word_document
toc: yes
#params:
  #report.data: report.data
---

```{r setup, include=FALSE, comment = "", results = 'asis', echo = FALSE}
library(dplyr)
library(ggplot2)
library(purrr)
knitr::opts_chunk$set(echo = FALSE)
```
#report.data <- params$report.data
```
 my_plot <- function(df) {
    ggplot(df, aes(x = sample.no, y = result)) +
    geom_point(aes(colour = location)) +
    geom_hline(aes(yintercept = mean(result)), colour = "black", linetype = "dotted") +
    geom_hline(aes(yintercept = mean(result) + 1.96 * sd(result)), colour = "red", linetype = "dashed") +
    geom_hline(aes(yintercept = mean(result) - 1.96 * sd(result)), colour = "red", linetype = "dashed") +
    theme_classic() +
    theme(legend.title = element_blank()) +
    labs(
      title = paste0("Comparison for ", unique(df$identifier)),
      x = "Sample number",
      y = "Result") +
      #caption = paste0("Caption here.")) +
    expand_limits(y = 0) +
    coord_cartesian(xlim = c(0, max(df$sample.no) + 2)) +    
    theme(
      plot.caption=element_text(size=12, hjust = 0, margin = margin(t=20)),
      plot.margin = margin(b=50)
    )
}

```

```{r, comment = "", results = 'asis', echo = FALSE}

purrr::map(unique(report.data$identifier),
                           function(x) {
                             #section heading
                             cat("#", (x), "\n")
                             cat("\n\n")
                             # filter data before passing it to the plot function
                             report.data %>% 
                               dplyr::filter(identifier == x) %>%
                               my_plot() %>% print()
                             cat("\n\n")
                             no.outofbounds <- report.data %>% 
                               dplyr::filter(identifier == x) %>%
                               mutate(outofbounds = ifelse(result > mean(result)+1.96*sd(result)|result < mean(result)-1.96*sd(result), TRUE, FALSE)) %>% 
                               dplyr::filter(outofbounds == TRUE) %>% 
                               nrow()
                             ifelse(no.outofbounds > 0, paste(cat(no.outofbounds), " results greater than 1.96 standard deviations away from the mean."), "All results within 1.96 standard deviations of the mean.") %>% 
                               cat()
                             cat("\n\n")
                             CV <- report.data %>% 
                               dplyr::filter(identifier == x) %>%
                               summarise(CV = sd(result)/mean(result) * 100) %>% 
                               round(2)
                             cat("\n\n")
                             paste("The all-site/factor CV for this parameter is ", CV, "%.") %>% 
                               cat()
                             cat("\n\n")
                             cat("APPROVED/REJECTED.")
                             cat("\n\n")
                             
                           }
) -> results
```

您的代码存在几个问题。我会一一过一遍

downloadHandler() 中的参数无效

您正在将 class reactive 的对象传递给 downloadHandler()contentType 参数。

downloadHandler(
  reactive(file <- input$file1),     ## <--- here
  filename = "report.doc",
  content = function(file) {
    # ...
  }
)

这似乎打乱了 downloadHandler() 的整个逻辑,并导致客户端出现“服务器错误”消息,而 shiny 没有任何错误或警告。

需要删除此行才能成功下载文件

正确引用 Rmd-parameter

当您想从 Rmd 报告中访问参数时,您需要使用 params$report.data。仅使用 report.data 将导致以下错误:object 'report.data' not found.

---
output: word_document
params:
  report.data: NULL
---

```{r}
report.data <- params$report.data
# ...
```

修复生成文件的路径

您正在临时目录中编织 Rmd,这通常是个好主意。然而,获得正确的路径并不总是那么容易。在你的情况下,我使用了以下内容

rendered_report <- rmarkdown::render(
  tempReport, output_file = "wordreport.doc",
  params = params,
  envir = new.env(parent = globalenv())
)
file.copy(rendered_report, file)

您的版本不起作用的原因是生成的报告是在临时目录 alogside tmpReport 中创建的。有关详细信息,请参阅 ?rmarkdown::render 的参考文档。

我使用了 rmarkdown::render() 的 return 值,它保存了生成文件的绝对路径。如果您事先不知道生成文件的文件扩展名,这不太容易出错并且特别有用

使用read.csv将上传的文件转换成data.frame

Shiny 不会自动将上传的 csv 文件转换为数据帧。你需要定义一个解析逻辑来做到这一点。

params <- list(report.data = read.csv(input$file1$datapath))

最后一句话

尝试让您的编码项目更有条理,并将未来的 SO 问题的范围一次限制为一个问题。创建“最小可重现示例”乍一看似乎很乏味,但这样做有几个好处

  • 其他人可以阅读问题和答案并轻松地在自己的项目中重用它们,而无需剖析代码墙
  • 回答这些问题要容易得多。像这样的问题,SO社区通常只提供评论,因为正确回答它们需要很多努力
  • 最大限度地减少和隔离问题是一种技能,可以帮助您更轻松地找出未来编码项目中的问题