将 CSV 文件内容转换为 Markdown
Convert CSV file contents to Markdown
背景
objective是从CSV文件中读取内容,并以Markdowntable格式写入内容。
应用程序使用 R 引擎 Renjin, which does not support knitr, kable, or pandoc。
问题
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|")
然后将所有内容捕获为字符串,连接前导管道,最后 trim 无关的结束管道。也就是说,解决类似于以下输出的问题:
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-like。
问题
在不依赖 third-party 库的情况下将 CSV 文件转换为 Markdown 格式的最有效方法是什么?
有问题的 Markdown 风格如下所示:
|Header|Header|Header|
|---|---|---|
|Data|Data|Data|
|Data|Data|Data|
也欢迎提供有关如何仅写入 header 数据和 table header 分隔符的提示。
既然你想把它放到 markdown 中,我认为可以肯定地说 table 大小是可以管理的,所以性能不是一个因素。 (编辑 #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|"
如果您担心对齐,您可以检查 character
s(也许还有其他人,交给您)。这使用降价 table 格式的对齐行:
(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("\|", "|", 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|1.0|6|160|110|3.9|2.62|16.46|0|1|4|4|"
### ^^^^^^ this is the pipe, interpreted correctly in markdown
当管道位于代码块内时,这不会很好地工作,但这里建议了一个解决方法:
此时,正如@alistaire 所建议的,您正在重新实现 knitr::kable
。就此而言,只需抓住 knitr/R/table.R
) 并使用 kable_markdown
即可为您完成 pipe-escaping。它需要 character matrix
,而不是 data.frame
,所以 kable_markdown(as.matrix(mtcars))
。您不能只获取单个函数,因为它也在该文件中使用了多个辅助函数。您当然可以删减一些函数,包括 kable
本身,它需要其他文件中的函数。
编辑 #2:因为你说 renjin 不支持 *apply
函数(评论表明这是不正确的,但为了参数),这是一个 for
-循环实现,包括对齐和 |
-转义:
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("\|", "|", 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|1.0|6|160|110|3.9|2.62|16.46|0|1|4|4|"
郑重声明,我的 *apply
和 for
-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) simple/straight-forward 很好,并且 (b) 我认为您的 table 足够小,真正的性能不会成为驱动力。
如果您愿意,可以编写自己的 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|
根据需要重写第二行以处理基于类型的对齐。
背景
objective是从CSV文件中读取内容,并以Markdowntable格式写入内容。
应用程序使用 R 引擎 Renjin, which does not support knitr, kable, or pandoc。
问题
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|")
然后将所有内容捕获为字符串,连接前导管道,最后 trim 无关的结束管道。也就是说,解决类似于以下输出的问题:
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-like。
问题
在不依赖 third-party 库的情况下将 CSV 文件转换为 Markdown 格式的最有效方法是什么?
有问题的 Markdown 风格如下所示:
|Header|Header|Header|
|---|---|---|
|Data|Data|Data|
|Data|Data|Data|
也欢迎提供有关如何仅写入 header 数据和 table header 分隔符的提示。
既然你想把它放到 markdown 中,我认为可以肯定地说 table 大小是可以管理的,所以性能不是一个因素。 (编辑 #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|"
如果您担心对齐,您可以检查 character
s(也许还有其他人,交给您)。这使用降价 table 格式的对齐行:
(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("\|", "|", 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|1.0|6|160|110|3.9|2.62|16.46|0|1|4|4|"
### ^^^^^^ this is the pipe, interpreted correctly in markdown
当管道位于代码块内时,这不会很好地工作,但这里建议了一个解决方法:
此时,正如@alistaire 所建议的,您正在重新实现 knitr::kable
。就此而言,只需抓住 knitr/R/table.R
) 并使用 kable_markdown
即可为您完成 pipe-escaping。它需要 character matrix
,而不是 data.frame
,所以 kable_markdown(as.matrix(mtcars))
。您不能只获取单个函数,因为它也在该文件中使用了多个辅助函数。您当然可以删减一些函数,包括 kable
本身,它需要其他文件中的函数。
编辑 #2:因为你说 renjin 不支持 *apply
函数(评论表明这是不正确的,但为了参数),这是一个 for
-循环实现,包括对齐和 |
-转义:
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("\|", "|", 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|1.0|6|160|110|3.9|2.62|16.46|0|1|4|4|"
郑重声明,我的 *apply
和 for
-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) simple/straight-forward 很好,并且 (b) 我认为您的 table 足够小,真正的性能不会成为驱动力。
如果您愿意,可以编写自己的 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|
根据需要重写第二行以处理基于类型的对齐。