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

发布于 2025-01-12 14:02:16 字数 9537 浏览 1 评论 0原文

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

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


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

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

我尝试在 .Rmd 文件的标头中定义参数(请参阅下面的注释行。) - 如果我这样做,则代码运行时不会出现错误,但生成的 Word 文档是空白的,除了标题。

谁能看到我哪里出错了?一如既往,感谢您花时间阅读本文并回复。

抱歉,我觉得我正在创建很多线程来寻求有关 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
```

I'm still struggling with some aspects of a Shiny app I'm working on. The intention is that the user uploads a csv file of data, which is then processed to generate a report (from a .Rmd template), which the user can then download as an editable Word .doc.

The .Rmd works fine if I render it in a normal R session. However, if done from my Shiny app, I get the following error:


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

report.data should be the dataframe produced by reading the input .csv file. Confusingly, the app does sometimes work (I think this occurs if report.data is already available in the global environment.).

I've tried defining the params in the header of the .Rmd file (see the commented out lines below.) - if I do this then the code runs without an error, but the resulting word document is blank, except for the title.

Can anyone see where I'm going wrong? Thank you, as ever, for your time in reading this and replying.

And apologies, I feel like I'm making a lot of threads asking for help with what seem to be quite basic things in Shiny, but I do search for similar questions and never find things that are quite right! But once I have these basic things in place I should be able to make progress by myself.

Code to generate a .csv file of example input for report.data:

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)

The app.R code:

#
# 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)

The .Rmd file (with the lines relating to params declaration commented out):

---
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
```

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

深居我梦 2025-01-19 14:02:16

您的代码有几个问题。我将一一检查一下

downloadHandler() 中的无效参数

您正在将 reactive 类的对象传递给 downloadHandler()contentType 参数代码>.

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

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

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

正确引用 Rmd 参数

当您想要从 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() 的返回值来代替,它保存生成文件的绝对路径。这不容易出错,如果您事先不知道生成文件的文件扩展名,则特别有用

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

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

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

最后一句话

尝试让你的编码项目更有条理,并将未来 SO 问题的范围限制为一次一个问题。创建“最小的可重现示例”一开始可能看起来很乏味,但是这样做有几个优点:

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

There are several issues with your code. I'll go over them one by one

Invalid parameter in downloadHandler()

You are passing an object of class reactive to the contentType parameter of downloadHandler().

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

It seems that this messes up the whole logic of downloadHandler() and leads to "server error" messages on the client side with no errors or warnings from shiny.

This line needs to be removed in order to download files successfully

Reference the Rmd-parameter correctly

When you want to access the parameter from the Rmd report, you will need to use params$report.data. Just using report.data will lead to the following error: object 'report.data' not found.

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

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

Fix the path to the generated file

You are knitting the Rmd inside the temporary directory, which is generally a good idea. However, getting the paths right is not always that easy. In your case, I used the following

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

The reason your version didn't work is that the generated report is created inside the temporary directory alogside tmpReport. See the reference documentation of ?rmarkdown::render for more details.

I used the return value of rmarkdown::render() instead which holds an absolute path to the generated file. This is less error prone and especially useful if you do not know the file extension of the generated file in advance

Use read.csv to convert the uploaded file into a data.frame

Shiny doesn't automatically convert uploaded csv files into dataframes. You need to define a parsing logic to do that.

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

One final word

Try to get more organized with your coding projects and limit the scope of future SO questions to one issue at a time. Creating "minimal reproducible examples" might seem tedious at first, but there are several advantages in doing that

  • Other people can read the questions and answers and reuse them in their own projects easily without dissecting a wall of code
  • It is much easier to answer those questions. With questions like this, the SO community usually only provides comments because answering them properly requires a lot of effort
  • Minimizing and isolating problems is a skill that will help you to figure out issues in your future coding projects much more easily
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文