【问题标题】:Convert CSV file contents to Markdown将 CSV 文件内容转换为 Markdown
【发布时间】:2017-06-01 03:24:58
【问题描述】:

背景

目标是读取 CSV 文件并以 Markdown 表格格式写入内容。

应用程序使用 R 引擎 Renjin,不支持 knitrkablepandoc

问题

write.table 命令有一个eol 选项,但没有对应的sol 选项。因此对于以下内容:

f <- read.csv('planning.csv')
write.table(
   format(f, digits=2), "",
   sep="|", row.names=F, col.names=F, quote=F, eol="|\n")

输出如下:

Geothermal|1250.0|Electricity|0.0|
Houses|  13.7|Shelter|4.2|
Compostor|   1.2|Recycling|0.2|

但每一行都应该出现|前缀,如下:

|Geothermal|1250.0|Electricity|0.0|
|Houses|  13.7|Shelter|4.2|
|Compostor|   1.2|Recycling|0.2|

应该可以做类似的事情(注意额外的eol 管道):

write.table(
       format(f, digits=2), "",
       sep="|", row.names=F, col.names=F, quote=F, eol="|\n|")

然后将所有内容捕获为字符串,连接前导管道,最后修剪无关的结束管道。也就是说,修复类似于以下输出的问题:

Geothermal|1250.0|Electricity|0.0|
|Houses|  13.7|Shelter|4.2|
|Compostor|   1.2|Recycling|0.2|
|Fire Station|  -9.6|Protection|0.5|
|Roads|   0.0|Transport|0.9|
|

不过,这种字符串操作看起来不太像 R。

问题

在不依赖第三方库的情况下,将 CSV 文件转换为 Markdown 格式的最有效方法是什么?

有问题的 Markdown 风格看起来像:

|Header|Header|Header|
|---|---|---|
|Data|Data|Data|
|Data|Data|Data|

也欢迎提示如何只写入表头数据和表头分隔符。

【问题讨论】:

  • 在 pandoc google 组中有 significant discussion 关于 CSV 表,包括 pandoc 过滤器。我不知道它的状态,但你可以检查一下。
  • apply(), lapply(), sapply(), mapply(), vapply() 都是基础包中的函数,在仁进内部始终可用。

标签: r csv markdown r-markdown renjin


【解决方案1】:

既然要放到markdown里面,我觉得可以说table size是可控的,所以性能不是一个因素。 (编辑#3:我有一些与行名存在相关的小错误,因此为了简化事情,我将从示例数据中完全删除它们。)

mtcars$rowname <- rownames(mtcars)
rownames(mtcars) <- NULL
mtcars <- mtcars[,c(ncol(mtcars), 1:(ncol(mtcars)-1))]
head(mtcars)
#             rowname  mpg cyl disp  hp drat    wt  qsec vs am gear carb
# 1         Mazda RX4 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
# 2     Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
# 3        Datsun 710 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
# 4    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
# 5 Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
# 6           Valiant 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

现在开始工作:

dashes <- paste(rep("---", ncol(mtcars)), collapse = "|")
txt <- capture.output(
  write.table(mtcars, stdout(), quote = FALSE, sep = "|", row.names = FALSE)
)
txt2 <- sprintf("|%s|", c(txt[1], dashes, txt[-1]))
head(txt2)
# [1] "|rowname|mpg|cyl|disp|hp|drat|wt|qsec|vs|am|gear|carb|"  
# [2] "|---|---|---|---|---|---|---|---|---|---|---|---|"       
# [3] "|Mazda RX4|21|6|160|110|3.9|2.62|16.46|0|1|4|4|"         
# [4] "|Mazda RX4 Wag|21|6|160|110|3.9|2.875|17.02|0|1|4|4|"    
# [5] "|Datsun 710|22.8|4|108|93|3.85|2.32|18.61|1|1|4|1|"      
# [6] "|Hornet 4 Drive|21.4|6|258|110|3.08|3.215|19.44|1|0|3|1|"

如果您担心对齐问题,可以检查characters(也许还有其他人,交给您)。这里使用markdown表格格式的对齐行:

(ischar <- vapply(mtcars, is.character, logical(1)))
# rowname     mpg     cyl    disp      hp    drat      wt    qsec      vs      am    gear    carb 
#    TRUE   FALSE   FALSE   FALSE   FALSE   FALSE   FALSE   FALSE   FALSE   FALSE   FALSE   FALSE 
dashes <- paste(ifelse(ischar, ":--", "--:"), collapse = "|")
txt <- capture.output(write.table(mtcars, stdout(), quote = FALSE, sep = "|", row.names = FALSE))
txt2 <- sprintf("|%s|", c(txt[1], dashes, txt[-1]))
head(txt2)
# [1] "|rowname|mpg|cyl|disp|hp|drat|wt|qsec|vs|am|gear|carb|"  
# [2] "|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|"       
# [3] "|Mazda RX4|21|6|160|110|3.9|2.62|16.46|0|1|4|4|"         
# [4] "|Mazda RX4 Wag|21|6|160|110|3.9|2.875|17.02|0|1|4|4|"    
# [5] "|Datsun 710|22.8|4|108|93|3.85|2.32|18.61|1|1|4|1|"      
# [6] "|Hornet 4 Drive|21.4|6|258|110|3.08|3.215|19.44|1|0|3|1|"

当您终于准备好保存时,请使用cat(txt2, file = "sometable.md")(或writeLines)。

编辑#1:请注意,其他建议的答案(包括我上面的)不涉及内容中的管道符号:

mtcars$mpg[1] <- "2|1.0"
ischar <- vapply(mtcars, is.character, logical(1))
dashes <- paste(ifelse(ischar, ":--", "--:"), collapse = "|")
txt <- capture.output(write.table(mtcars, stdout(), quote = FALSE, sep = "|", row.names = FALSE))
txt2 <- sprintf("|%s|", c(txt[1], dashes, txt[-1]))
head(txt2, n = 3)
# [1] "|rowname|mpg|cyl|disp|hp|drat|wt|qsec|vs|am|gear|carb|"
# [2] "|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|"     
# [3] "|Mazda RX4|2|1.0|6|160|110|3.9|2.62|16.46|0|1|4|4|"    
###                ^ this is the problem

您可以在所有字符(或添加因子)列上手动转义它:

ischar <- vapply(mtcars, is.character, logical(1))
mtcars[ischar] <- lapply(mtcars[ischar], function(x) gsub("\\|", "&#124;", x))
dashes <- paste(ifelse(ischar, ":--", "--:"), collapse = "|")
txt <- capture.output(write.table(mtcars, stdout(), quote = FALSE, sep = "|", row.names = FALSE))
txt2 <- sprintf("|%s|", c(txt[1], dashes, txt[-1]))
head(txt2, n = 3)
# [1] "|rowname|mpg|cyl|disp|hp|drat|wt|qsec|vs|am|gear|carb|" 
# [2] "|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|"      
# [3] "|Mazda RX4|2&#124;1.0|6|160|110|3.9|2.62|16.46|0|1|4|4|"
###                ^^^^^^ this is the pipe, interpreted correctly in markdown

当管道位于代码块中时,这不起作用,但这里建议了一种解决方法:https://stackoverflow.com/a/17320389/3358272

此时,正如@alistaire 所建议的,您在某种程度上重新实现了knitr::kable。就此而言,只需抓住 knitr/R/table.R) 并使用 kable_markdown 即可为您进行管道转义。它需要character matrix,而不是data.frame,所以kable_markdown(as.matrix(mtcars))。您不能只获取单个函数,因为它在该文件中也使用了多个辅助函数。你当然可以修剪一些函数,包括 kable 本身,它需要其他文件中的函数。

编辑#2:既然你说 renjin 不支持 *apply 函数(评论表明这是不正确的,但为了争论,我会继续),这里是 @987654339 @-loop 实现,包括对齐和|-escaping:

mtcars$mpg[1] <- "2|1.0" # just a reminder that it's here
dashes <- rep("--:", length(mtcars))
for (i in seq_along(mtcars)) {
  if (is.character(mtcars[[i]]) || is.factor(mtcars[[i]])) {
    mtcars[[i]] <- gsub("\\|", "&#124;", mtcars[[i]])
    dashes[i] <- ":--"
  }
}
txt <- capture.output(write.table(mtcars, stdout(), quote = FALSE, sep = "|", row.names = FALSE))
txt2 <- sprintf("|%s|", c(txt[1], paste(dashes, collapse = "|"), txt[-1]))
head(txt2, n = 3)
# [1] "|rowname|mpg|cyl|disp|hp|drat|wt|qsec|vs|am|gear|carb|" 
# [2] "|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|"      
# [3] "|Mazda RX4|2&#124;1.0|6|160|110|3.9|2.62|16.46|0|1|4|4|"

郑重声明,我的*applyfor-loop 实现的性能实际上是相同的,而@alistaire 的解决方案的速度是原来的两倍多(使用mtcars):

Unit: microseconds
              expr      min        lq      mean    median        uq      max neval
     apply_noalign  917.881  947.9665 1031.9288  971.3060 1041.5050 1999.499   100
       apply_align  945.960  975.1350 1083.2856  995.7390 1063.7500 3523.101   100
 apply_align_pipes 1110.429 1148.5360 1255.5460 1176.9815 1275.2600 1905.778   100
           forloop 1188.104 1217.0950 1309.2549 1261.2205 1342.3600 2979.010   100
         alistaire  451.830  473.7105  511.5778  496.1370  518.5645  827.443   100
   alistaire_pipes  593.687  626.6900  718.6898  652.7645  700.5360 5460.970   100

我将他的原始函数用于alistaire,并为alistaire_pipes 添加了一个简单的gsub。可能有一种更有效的方法,但 (a) 简单/直接是好的,并且 (b) 我认为您的表足够小,真正的性能不会成为驱动力。

【讨论】:

  • 我最初没有看到对 *apply 函数的限制,对此感到抱歉。我很高兴第一个作品。 (如果需要,用for 循环替换applys 实际上非常简单。)
  • 您在Edit #2中的说法不正确:仁进完全支持*apply函数。 @alistaire 提供的答案使用函数式Reduce(),在 Renjin 中也可以正常工作。最后,mtcars 数据集没有字符列,因此您在这里使用 ischar 是多余的。
  • OP 说的,我相信这一点并且没有验证,我的错(我不使用 renjin 所以不知道)。我将一列更改为字符(第 3 个代码块),所以 ischar 总体上不是多余的,尽管我发现我错过了第二个块中的一个错误,该错误会使这一点更清晰。
  • Dave Jarvis,我修复了一些小错误(由@mjkallen 的评论提出),它们可能会或可能不会成为您使用的一个因素。另外,根据他的评论,如果 *apply 函数对您有用,我建议您实际上基于我的 apply 解决方案之一或 alistaire 的函数。
【解决方案2】:

如果您愿意,可以编写自己的kable 版本;主要是paste

x <- read.csv(system.file('misc', 'exDIF.csv', package = 'utils'))

md_table <- function(df){
    paste0('|', paste(names(df), collapse = '|'), '|\n|', 
           paste(rep('---', length(df)), collapse = '|'), '|\n|', 
           paste(Reduce(function(x, y){paste(x, y, sep = '|')}, df), collapse = '|\n|'), '|')
}

cat(md_table(x))
#> |Var1|Var2|
#> |---|---|
#> |2.7|A|
#> |3.14|B|
#> |10|A|
#> |-7|A|

cat(md_table(head(mtcars)))
#> |mpg|cyl|disp|hp|drat|wt|qsec|vs|am|gear|carb|
#> |---|---|---|---|---|---|---|---|---|---|---|
#> |21|6|160|110|3.9|2.62|16.46|0|1|4|4|
#> |21|6|160|110|3.9|2.875|17.02|0|1|4|4|
#> |22.8|4|108|93|3.85|2.32|18.61|1|1|4|1|
#> |21.4|6|258|110|3.08|3.215|19.44|1|0|3|1|
#> |18.7|8|360|175|3.15|3.44|17.02|0|0|3|2|
#> |18.1|6|225|105|2.76|3.46|20.22|1|0|3|1|

如果愿意,可以根据类型重写第二行以处理对齐方式。

【讨论】:

    猜你喜欢
    • 2015-02-08
    • 1970-01-01
    • 2013-07-11
    • 2021-10-30
    • 2012-08-07
    • 1970-01-01
    • 2011-11-10
    • 2018-03-28
    • 2020-07-08
    相关资源
    最近更新 更多