我需要使用其他列中以逗号分隔的值进行一些字符串格式化。假设我有一个像这样的数据框:
words <- c('%s + %s equal %s', '%s + %s equal %s')
arguments <- c('1,1,2', '2,2,4')
df <- data.frame(words, arguments)
df
words arguments
1 %s + %s equal %s 1,1,2
2 %s + %s equal %s 2,2,4
我需要这样的结果:
words arguments combined
1 %s + %s equal %s 1,1,2 1 + 1 equal 2
2 %s + %s equal %s 2,2,4 2 + 2 equal 4
知道我该怎么做吗?
words
列非常适合sprintf
。尝试以下方法:
df$combined <- apply(df, 1, function(x) do.call(sprintf,
c(as.list(strsplit(x[2], ',')[[1]]), fmt = x[[1]])))
df
# words arguments combined
#1 %s + %s equal %s 1,1,2 1 + 1 equal 2
#2 %s + %s equal %s 2,2,4 2 + 2 equal 4
我们拆分
arguments
上的 ","
值,使用 do.call
将它们作为 sprintf
的单独参数传递,并创建 combined
字符串。 x[2]
中的apply
指的是arguments
列,而x[1]
则代表words
列。
与 Ronak 的解决方案非常相似,但为了简洁使用了一些
data.table
和 purrr
:
library(purrr)
library(data.table)
df$combind <- map_chr(
seq_len(nrow(df)),
~do.call(sprintf, c(fmt = df$words[.], tstrsplit(df$arguments[.], ",")))
)
# words arguments combind
# 1 %s + %s equal %s 1,1,2 1 + 1 equal 2
# 2 %s + %s equal %s 2,2,4 2 + 2 equal 4
正如@Spacedman 正确指出的那样,之前的解决方案不再有效。尽可能避免 eval-parse 解决方案也是正确的。谢谢@dash2:你说得很好。
所以这里有一个新的 tidyverse 和 R-base 解决方案。
tidyverse
具体:
dplyr
、purrr
、stringr
library(tidyverse)
df |>
mutate(
# split arguments by comma
arguments = str_split(arguments, ","),
# loop over words and arguments and call sprintf via do.call
combind = map2_chr(words, arguments, \(...) do.call("sprintf", as.list(c(...)))))
#> words arguments combind
#> 1 %s + %s equal %s 1, 1, 2 1 + 1 equal 2
#> 2 %s + %s equal %s 2, 2, 4 2 + 2 equal 4
请注意,
do.call
的第二个参数必须是被调用函数的参数列表(在本例中为sprintf
)。这就是为什么 words
的每个项目都与 c
的每个项目组合 (arguments
),然后强制放入列表 (as.list
)。
df$combind <- mapply(\(...) do.call("sprintf", as.list(c(...))), df$words, strsplit(df$arguments, ","))
让我们一步步看看解决方案:
# loop simultaneously over the two columns of df via mapply
# (to see the solution step by step SIMPLIFY is set to FALSE)
mapply(c, df$words, strsplit(df$arguments, ","), SIMPLIFY = FALSE) |>
# now set each vector to a list (to use do.call)
lapply(as.list) |>
# call sprintf via do.call
# (sapply is being used over lapply to force the simplicification of the list into a character vector)
sapply(do.call, what = "sprintf")
#> %s + %s equal %s %s + %s equal %s
#> "1 + 1 equal 2" "2 + 2 equal 4"