用 openxlsx 定制报表后生成 PDF 文件
楚新元 / 2021-08-20
用 R 清洗加工数据非常方便,但是 R 输出的都是规整的数据框。现实当中每天要报的报表还有一些“乱七八糟”的要素,比如标题、报告日期、数据单位、制表、复核、保密等级等。当然有了最重要的数据部分,导出到 Excel 文件里手动添加剩余的哪些“乱七八糟”的要素也是可以的,如果这个报表只制作一次,我也更倾向于这样做,发挥 R 和 Excel 各自的优势。但是如果这个报表是每天都要报送的日报呢?我是绝对不能容忍每天手动做重复性的工作的,用 R 全自动化处理绝对是最佳选项。下面我们看一个例子:
加载相关 R 包
library(dplyr)
library(kableExtra)
library(openxlsx)
library(lubridate)
生成示例数据
R里面清洗加工数据这块的教程太多了,这里不是我要讲的重点,因此,这里直接随机生成一个数据框。
v = rnorm(168, 100, 5)
m = matrix(v, ncol = 8)
daily = as.data.frame(m)
colnames(daily) = paste0("x", 1:8)
daily %>%
kable() %>%
kable_styling(font_size = 12)
定制个性化报表
# 定义报告期
report_period = c("2020-04-08")
# 计算报告期年、月、日
report_year = as.numeric(substr(report_period, 1, 4))
report_month = as.numeric(substr(report_period, 6, 7))
report_day = as.numeric(substr(report_period, 9, 10))
# 数据导入到Excel文件中
## 新建一个工作簿wb、新建一个工作表daily
wb = createWorkbook()
addWorksheet(wb, "daily", gridLines = FALSE)
## 设置全局列宽、行高、冻结活动单元格
setColWidths(
wb, "daily",
cols = 1:8,
widths = c(26, rep(15.5, 5), 12, 18)
)
setRowHeights(
wb, "daily",
rows = 1:(nrow(daily) + 4),
heights = c(20, 35, rep(20, nrow(daily) + 2))
)
## 打印设置
pageSetup(
wb, "daily",
orientation = "landscape",
scale = 97,
left = 0.7,
right = 0.7,
top = 0.75,
bottom = 0.75,
header = 0.3,
footer = 0.3,
fitToWidth = TRUE,
fitToHeight = TRUE,
paperSize = 9,
printTitleRows = NULL,
printTitleCols = NULL
)
## 保密提示
secret = c("★内部资料、严格保密")
style_secret = createStyle(
halign = "right",
valign = "center",
wrapText = TRUE,
fontColour = "red",
fontSize = 11,
fontName = "Arial"
)
mergeCells(
wb, "daily",
rows = 1:1,
cols = 1:8
)
addStyle(
wb, "daily",
style = style_secret,
rows = 1:1,
cols = 1:8
)
writeData(wb, "daily", secret, startRow = 1)
## 大标题设置
title = paste0(
year(report_period), "年" ,
month(report_period), "月",
day(report_period),
"日经营数据日报"
)
style_title = createStyle(
halign = "center",
valign = "center",
wrapText = TRUE,
textDecoration = c("bold"),
fontColour = "black",
fontSize = 20,
fontName = "Arial"
)
mergeCells(
wb, "daily",
rows = 2:2,
cols = 1:8
)
addStyle(
wb, "daily",
style = style_title,
rows = 2:2,
cols = 1:8
)
writeData(wb, "daily", title, startRow = 2)
## 报告期设置
date = paste0("报告日期:", Sys.Date())
style_date = createStyle(
halign = "right",
valign = "center",
wrapText = TRUE,
fontColour = "black",
fontSize = 11,
fontName = "Arial")
mergeCells(wb, "daily", rows = 3:3, cols = 6:7)
addStyle(
wb, "daily", style = style_date,
rows = 3:3, cols = 6:7
)
writeData(wb, "daily", date, startRow = 3, startCol = 6)
## 数据单位设置
unit = "单位:亿元、%"
style_unit = createStyle(
halign = "center",
valign = "center",
wrapText = TRUE,
fontColour = "black",
fontSize = 11,
fontName = "Arial")
mergeCells(wb, "daily", rows = 3:3, cols = 8:8)
addStyle(
wb, "daily",
style = style_unit,
rows = 3:3,
cols = 8:8
)
writeData(wb, "daily", unit, startRow = 3, startCol = 8)
## 表头部分设置
style_header = createStyle(
textDecoration = "Bold",
halign = "center",
valign = "center",
wrapText = TRUE,
border = "TopBottomLeftRight",
borderColour = "black",
fontColour = "white",
fgFill = "#4F81BD",
fontSize = 11,
fontName = "Arial"
)
## 数据部分设置
style_data = createStyle(
valign = "center",
border = "TopBottomLeftRight",
borderColour = "black",
fontSize = 11,
fontName = "Arial",
numFmt = "0.00"
)
addStyle(
wb, "daily",
style = style_data,
rows = 5:(nrow(daily) + 4),
cols = 1:8,
gridExpand = T
)
writeData(
wb, "daily",
daily,
headerStyle = style_header,
startRow = 4
)
## 保存工作簿
saveWorkbook(
wb, overwrite = TRUE,
paste0(report_period, "daily.xlsx")
)
导出为PDF格式
其实导出到 Excel 基本已经大功告成了,可是有的单位要求发布的报告必须是 PDF 格式的。如果单纯的需要PDF格式文件,我们可以考虑直接从示例数据导出到 PDF,而不需要先生成 Excel 文件再转为 PDF 格式,但是这里有个问题,就是数据部分倒是容易,但是报表里那一堆“乱七八糟”的要素,比如标题、报告日期、数据单位、制表、复核、保密等级等要素该如何放进去呢?位置又该怎么精准控制呢?通过已经定制好的 Excel 文件再转为 PDF 虽然有点绕远路了,但是能满足报表要求,所以这里仍然选择数据–> Excel 文件–> PDF 文件。
从 Excel 文件–> PDF 文件,这也不难,打开 Excel 后利用虚拟打印机打印为PDF格式即可,手工操作工作量也是可以忽略的了。但是,对于一个完美主义极客而言这是不够的,因为将来有可能一次生成的不是一张报表,所以,为了将来不时之需,这里仍难考虑通过程序解决。R 里面直接调用虚拟打印机将 Excel 工作表打印成 PDF 实现起来估计有点困难,利用 JAVA 实现的网上倒是有教程 JAVA 调用打印机输出 PDF 文件。因为 Excel 从 2007 开始可以直接将 Excel 工作表转为 PDF,这里我们考虑用 R 调用 VBA 实现。
# 调用 VBA 将 Excel 文件转为 PDF 格式
library(RDCOMClient)
# 定义 Excel 文件路径
xlFile = paste(
"path/to/", # 这里使用绝对路径
"daily.xlsx",
sep = report_period
)
xlApp = COMCreate("Excel.Application")
xlApp[["Visible"]] = TRUE
wb = xlApp[["Workbooks"]]$Open(Filename = xlFile)
sht = wb[["Worksheets"]]$Item(1)
sht$Select()
pdfFile = paste(
"path/to/", "daily.pdf", sep = report_period # 这里使用绝对路径
)
if (file.exists(pdfFile) == TRUE) file.remove(pdfFile)
xlApp[["ActiveSheet"]]$ExportAsFixedFormat(
IgnorePrintAreas = FALSE,
Type = 0, # 输出为 PDF
Filename = pdfFile
)
xlApp$Quit() # 关闭 Excel
特别需要注意的是:
-
RDCOMClient 包目前只支持 Windows 系统。
-
RDCOMClient 包貌似不支持相对路径。
-
如果已经生成了 PDF 文件,如果再运行一遍程序,原先生成的 PDF 文件不会被覆盖,执行
ExportAsFixedFormat
函数会报错,所以执行ExportAsFixedFormat
函数前,必须删除之前已经生成的 PDF 文件再运行程序。