是否有一种方法可以在我的lapply()函数中获得列表索引名?
n = names(mylist)
lapply(mylist, function(list.elem) { cat("What is the name of this list element?\n" })
我以前问过是否可以在lapply()返回的列表中保留索引名,但我仍然不知道是否有一种简单的方法来获取自定义函数中的每个元素名。我希望避免对名称本身调用lapply,我宁愿在函数参数中获得名称。
是否有一种方法可以在我的lapply()函数中获得列表索引名?
n = names(mylist)
lapply(mylist, function(list.elem) { cat("What is the name of this list element?\n" })
我以前问过是否可以在lapply()返回的列表中保留索引名,但我仍然不知道是否有一种简单的方法来获取自定义函数中的每个元素名。我希望避免对名称本身调用lapply,我宁愿在函数参数中获得名称。
当前回答
不幸的是,lapply只给出你传递给它的向量的元素。 通常的解决方法是向它传递向量的名称或索引,而不是向量本身。
但请注意,你总是可以传递额外的参数给函数,所以下面的工作:
x <- list(a=11,b=12,c=13) # Changed to list to address concerns in commments
lapply(seq_along(x), function(y, n, i) { paste(n[[i]], y[[i]]) }, y=x, n=names(x))
这里我在x的下标上使用lapply,但也传递了x和x的名称。正如你所看到的,函数参数的顺序可以是任何东西——lapply将“元素”(这里是索引)传递给额外的参数中未指定的第一个参数。在这种情况下,我指定了y和n,所以只剩下I了……
这会产生以下结果:
[[1]]
[1] "a 11"
[[2]]
[1] "b 12"
[[3]]
[1] "c 13"
更简单的例子,相同的结果:
lapply(seq_along(x), function(i) paste(names(x)[[i]], x[[i]]))
这里,函数使用“全局”变量x并在每次调用中提取名称。
其他回答
我的答案与Tommy和caracals的方向相同,但避免了将列表保存为额外对象。
lapply(seq(3), function(i, y=list(a=14,b=15,c=16)) { paste(names(y)[[i]], y[[i]]) })
结果:
[[1]]
[1] "a 14"
[[2]]
[1] "b 15"
[[3]]
[1] "c 16"
这将列表作为FUN的命名参数(而不是lapply)。Lapply只需要遍历列表的元素(在更改列表长度时,要注意将第一个参数更改为Lapply)。
注意:将列表直接作为附加参数提供给lapply也可以:
lapply(seq(3), function(i, y) { paste(names(y)[[i]], y[[i]]) }, y=list(a=14,b=15,c=16))
假设我们想计算每个元素的长度。
mylist <- list(a=1:4,b=2:9,c=10:20)
mylist
$a
[1] 1 2 3 4
$b
[1] 2 3 4 5 6 7 8 9
$c
[1] 10 11 12 13 14 15 16 17 18 19 20
如果目的仅仅是标记结果元素,那么lapply(mylist,length)或更低的值可以工作。
sapply(mylist,length,USE.NAMES=T)
a b c
4 8 11
如果目标是在函数内部使用标签,则mapply()通过遍历两个对象非常有用;列表元素和列表名称。
fun <- function(x,y) paste0(length(x),"_",y)
mapply(fun,mylist,names(mylist))
a b c
"4_a" "8_b" "11_c"
@ferdinand-kraft给了我们一个很棒的技巧,然后告诉我们我们不应该使用它 因为它没有记录,也因为性能开销。
我对第一点没有太多的争论,但我想指出的是,开销 很少会担心。
让我们定义活动函数,这样我们就不必调用复杂表达式 parent.frame()$i[]但只有.i(),我们还将创建.n()来访问 名称,它应该适用于基函数和purrr函数(可能也适用于大多数其他函数)。
.i <- function() parent.frame(2)$i[]
# looks for X OR .x to handle base and purrr functionals
.n <- function() {
env <- parent.frame(2)
names(c(env$X,env$.x))[env$i[]]
}
sapply(cars, function(x) paste(.n(), .i()))
#> speed dist
#> "speed 1" "dist 2"
现在让我们对一个简单的函数进行基准测试,该函数将向量的项粘贴到它们的下标, 使用不同的方法(此操作当然可以使用paste(vec, seq_along(vec))向量化,但这不是这里的重点)。
我们定义了一个基准测试函数和一个绘图函数,并将结果绘制如下:
library(purrr)
library(ggplot2)
benchmark_fun <- function(n){
vec <- sample(letters,n, replace = TRUE)
mb <- microbenchmark::microbenchmark(unit="ms",
lapply(vec, function(x) paste(x, .i())),
map(vec, function(x) paste(x, .i())),
lapply(seq_along(vec), function(x) paste(vec[[x]], x)),
mapply(function(x,y) paste(x, y), vec, seq_along(vec), SIMPLIFY = FALSE),
imap(vec, function(x,y) paste(x, y)))
cbind(summary(mb)[c("expr","mean")], n = n)
}
benchmark_plot <- function(data, title){
ggplot(data, aes(n, mean, col = expr)) +
geom_line() +
ylab("mean time in ms") +
ggtitle(title) +
theme(legend.position = "bottom",legend.direction = "vertical")
}
plot_data <- map_dfr(2^(0:15), benchmark_fun)
benchmark_plot(plot_data[plot_data$n <= 100,], "simplest call for low n")
benchmark_plot(plot_data,"simplest call for higher n")
由reprex包于2019年11月15日创建(v0.3.0)
第一张图表开头的下跌是偶然的,请忽略它。
我们可以看到,所选的答案确实更快,对于相当数量的迭代,我们的I()解决方案确实更慢,与所选答案相比,开销大约是使用purrr::imap()的开销的3倍,30k次迭代大约为25毫秒,因此每1000次迭代大约损失1毫秒,每百万次损失1秒。在我看来,这是为了方便而付出的小代价。
汤米的答案适用于命名向量,但我知道你对列表感兴趣。似乎他在绕一圈因为他引用了调用环境中的x。这个函数只使用传递给函数的参数,因此不假设传递对象的名称:
x <- list(a=11,b=12,c=13)
lapply(x, function(z) { attributes(deparse(substitute(z)))$names } )
#--------
$a
NULL
$b
NULL
$c
NULL
#--------
names( lapply(x, function(z) { attributes(deparse(substitute(z)))$names } ))
#[1] "a" "b" "c"
what_is_my_name <- function(ZZZ) return(deparse(substitute(ZZZ)))
what_is_my_name(X)
#[1] "X"
what_is_my_name(ZZZ=this)
#[1] "this"
exists("this")
#[1] FALSE
同样的问题我已经遇到过很多次了…… 我开始用另一种方法…我不再使用lapply,而是开始使用mapply
n = names(mylist)
mapply(function(list.elem, names) { }, list.elem = mylist, names = n)