使用For循环、dplyr和R Shiny开发Excel下载器的报错咨询
问题根因
- 你同时允许用户上传csv和xlsx文件,但读取时固定用
read.csv(),如果用户上传xlsx文件会读取失败,得到结构异常的空数据,后续取Line Id列时就会触发「下标越界」「if参数长度为0」的报错。
- 你同时允许用户上传csv和xlsx文件,但读取时固定用
- 两个reactive
spenddom和tabcreator里的for循环没有累积结果,每次循环都会覆盖上一次的ddf和tabname,最终只返回最后一次循环的结果,完全没法满足一个Line对应一个工作表的需求。
- 两个reactive
writexl/openxlsx包的写入多工作表功能,需要传入命名列表,列表每个元素对应一个工作表的内容,列表名对应工作表名,你当前传入的是单个数据框和单个工作表名,逻辑完全不匹配。
- 重复读取上传文件三次,没必要的同时也增加了IO错误概率。
修复后的完整代码
library(openxlsx) library(readxl) library(writexl) library(dplyr) library(magrittr) library(lubridate) library(shiny) ui <- fluidPage( titlePanel("Domain Performance"), sidebarLayout( sidebarPanel( fileInput('file1', 'Upload file', accept=c('text/csv', 'text/comma-separated-values,text/plain', '.csv','.xlsx')) ), mainPanel( downloadButton('sortspend',"Spend"), tableOutput("outdata1") ) )) server <- function(input, output) { options(shiny.maxRequestSize=90*1024^2) # 公共读取文件的reactive,自动适配csv/xlsx raw_data <- reactive({ req(input$file1) file_path <- input$file1$datapath file_ext <- tools::file_ext(input$file1$name) if(file_ext == "csv"){ df <- read.csv(file_path, check.names = FALSE) } else if(file_ext %in% c("xlsx", "xls")){ df <- read_excel(file_path) } return(df) }) output$outdata1 <- renderTable({ head(raw_data(), 10) # 只显示前10行避免页面过长 }) # 处理得到多工作表数据的命名列表 line_sheet_list <- reactive({ df <- raw_data() line_ids <- unique(df$`Line Id`) # 初始化空列表存结果 res_list <- list() for (i in seq_along(line_ids)){ current_line_id <- line_ids[i] ddf <- df %>% select(`Line Id`, Domain, `Advertiser Spending`, Impressions, Clicks, Conversion, Line) %>% filter(`Line Id` == current_line_id) %>% group_by(Line, Domain) %>% summarise(Spend = sum(`Advertiser Spending`), Imp = sum(Impressions), Clicks = sum(Clicks), Conversions = sum(Conversion), .groups = "drop") %>% mutate(CPA = ifelse(Conversions == 0, 0, Spend/Conversions)) %>% # 避免转化为0时报除零错 arrange(desc(Spend)) # 把结果加入列表,列表名就是工作表名 res_list[[as.character(current_line_id)]] <- ddf } return(res_list) }) output$sortspend <- downloadHandler( filename = function(){ paste0("domainperfdl_", Sys.Date(), ".xlsx") }, content = function(fname){ # 命名列表直接传入自动生成多工作表 write_xlsx(line_sheet_list(), path = fname) }, contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) } shinyApp(ui=ui, server=server)
额外优化说明
- 新增文件类型判断逻辑,不管传csv还是xlsx都能正确读取
- 循环时用列表累积所有Line的处理结果,自动对应工作表名
- 新增除零保护,当某条域名的转化数为0时CPA直接赋值为0,避免运行警告
- 输出文件名自动加日期,避免重复覆盖
内容的提问来源于stack exchange,提问作者vinidapooh
相关产品推荐
相关产品推荐

