有没有办法在我的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,我宁愿在函数参数中获取名称。
Answers:
不幸的是,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))
在这里,我使用lapply
的索引x
,但还要传入x
和的名称x
。如您所见,函数参数的顺序可以是任何东西- lapply
将“元素”(此处为索引)传递给多余参数中未指定的第一个参数。在这种情况下,我指定y
和n
,所以只剩i
下...
产生以下内容:
[[1]]
[1] "a 11"
[[2]]
[1] "b 12"
[[3]]
[1] "c 13"
UPDATE更简单的示例,结果相同:
lapply(seq_along(x), function(i) paste(names(x)[[i]], x[[i]]))
此处,函数使用“全局”变量,x
并在每个调用中提取名称。
y
,x
以便(希望)更清楚地知道该函数可以将其参数称为任何东西。还将向量值更改为11,12,13
。
这基本上使用与Tommy相同的解决方法,但是使用Map()
,则无需访问存储列表组件名称的全局变量。
> x <- list(a=11, b=12, c=13)
> Map(function(x, i) paste(i, x), x, names(x))
$a
[1] "a 11"
$b
[1] "b 12"
$c
[1] "c 13
或者,如果您愿意 mapply()
> mapply(function(x, i) paste(i, x), x, names(x))
a b c
"a 11" "b 12" "c 13"
mapply()
,请注意该SIMPLIFY
选项,默认为true。就我而言,当我只想应用一个简单的列表时,这会使整个事情变成一个大矩阵。将其设置为F
(在中mapply()
)使其按预期运行。
R版本3.2的更新
免责声明:这是一个骇人听闻的技巧,在以后的发行版中可能会停止工作。
您可以使用以下方法获取索引:
> lapply(list(a=10,b=20), function(x){parent.frame()$i[]})
$a
[1] 1
$b
[1] 2
注意:[]
必须使用,它才能起作用,因为R欺骗R认为该符号i
(位于的求值框架中lapply
)可能具有更多引用,从而激活了它的惰性复制。没有它,R将不会保留以下内容的单独副本i
:
> lapply(list(a=10,b=20), function(x){parent.frame()$i})
$a
[1] 2
$b
[1] 2
可以使用其他特殊技巧,例如function(x){parent.frame()$i+0}
或function(x){--parent.frame()$i}
。
绩效影响
强制复制会导致性能下降吗?是! 以下是基准:
> x <- as.list(seq_len(1e6))
> system.time( y <- lapply(x, function(x){parent.frame()$i[]}) )
user system elapsed
2.38 0.00 2.37
> system.time( y <- lapply(x, function(x){parent.frame()$i[]}) )
user system elapsed
2.45 0.00 2.45
> system.time( y <- lapply(x, function(x){parent.frame()$i[]}) )
user system elapsed
2.41 0.00 2.41
> y[[2]]
[1] 2
> system.time( y <- lapply(x, function(x){parent.frame()$i}) )
user system elapsed
1.92 0.00 1.93
> system.time( y <- lapply(x, function(x){parent.frame()$i}) )
user system elapsed
2.07 0.00 2.09
> system.time( y <- lapply(x, function(x){parent.frame()$i}) )
user system elapsed
1.89 0.00 1.89
> y[[2]]
[1] 1000000
结论
这个答案仅表明您不应该使用此方法...如果您找到上述类似Tommy's的其他解决方案,不仅代码将更具可读性,并且与以后的版本更兼容,您还可能会失去核心团队努力进行的优化。发展!
旧版本的技巧,不再起作用:
> lapply(list(a=10,b=10,c=10), function(x)substitute(x)[[3]])
结果:
$a
[1] 1
$b
[1] 2
$c
[1] 3
说明:lapply
创建窗体的来电FUN(X[[1L]], ...)
,FUN(X[[2L]], ...)
所以它传递的参数是等X[[i]]
在那里i
是在回路中的电流指标。如果我们在求值前得到它(即,如果使用substitute
),我们将得到未求值的表达式X[[i]]
。这是对[[
函数的调用,带有参数X
(符号)和i
(整数)。所以substitute(x)[[3]]
恰好返回此整数。
有了索引,如果先保存,就可以轻松访问名称:
L <- list(a=10,b=10,c=10)
n <- names(L)
lapply(L, function(x)n[substitute(x)[[3]]])
结果:
$a
[1] "a"
$b
[1] "b"
$c
[1] "c"
或使用第二个技巧::-)
lapply(list(a=10,b=10,c=10), function(x)names(eval(sys.call(1)[[2]]))[substitute(x)[[3]]])
(结果是相同的)。
解释2:sys.call(1)
返回lapply(...)
,因此sys.call(1)[[2]]
该表达式用作的列表参数lapply
。将其传递给eval
创建的合法对象names
可以访问。棘手的,但是有效。
奖励:获得名称的第二种方法:
lapply(list(a=10,b=10,c=10), function(x)eval.parent(quote(names(X)))[substitute(x)[[3]]])
请注意,这X
是的父框架中的有效对象FUN
,并且引用的list参数lapply
,因此我们可以使用eval.parent
。
lapply(list(a=10,b=10,c=10), function(x)substitute(x)[[3]])
返回全部为3。您能解释一下如何选择这3个吗?和差异的原因?它是否等于列表的长度,在这种情况下为3。对不起,这是一个基本问题,但想知道在一般情况下如何应用。
lapply(list(a=10,b=10,c=10), function(x)eval.parent(quote(names(X)))[substitute(x)[[3]]])
作品……我会检查发生了什么。
lapply(list(a=10,b=10,c=10), function(x)eval.parent(quote(names(X)))[substitute(x)[[3]]])
不再起作用,并显示错误,Error in eval.parent(quote(names(X)))[substitute(x)[[3]]] : invalid subscript type 'symbol'
是否有简单的方法来解决此问题?
我有很多次相同的问题...我开始使用另一种方式...而不是使用lapply
,我开始使用mapply
n = names(mylist)
mapply(function(list.elem, names) { }, list.elem = mylist, names = n)
您可以尝试使用imap()
from purrr
软件包。
从文档中:
如果x具有名称,则imap(x,...)是map2(x,names(x),...)的简写,如果没有,则为map2(x,seq_along(x),...)的简写。
因此,您可以通过以下方式使用它:
library(purrr)
myList <- list(a=11,b=12,c=13)
imap(myList, function(x, y) paste(x, y))
这将为您带来以下结果:
$a
[1] "11 a"
$b
[1] "12 b"
$c
[1] "13 c"
Tommy的答案适用于命名向量,但我知道您对列表感兴趣。好像他在做一个终结,因为他在调用环境中引用了“ 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
NULL
?!所以lapply(x, function(x) NULL)
给出相同的答案……
lapply
总是自加名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))
@caracals和@Tommy都是很好的解决方案,这是一个包含list
´和data.frame
´ 的示例。
r
是list
的list
的和data.frame
的(dput(r[[1]]
末)。
names(r)
[1] "todos" "random"
r[[1]][1]
$F0
$F0$rst1
algo rst prec rorac prPo pos
1 Mean 56.4 0.450 25.872 91.2 239
6 gbm1 41.8 0.438 22.595 77.4 239
4 GAM2 37.2 0.512 43.256 50.0 172
7 gbm2 36.8 0.422 18.039 85.4 239
11 ran2 35.0 0.442 23.810 61.5 239
2 nai1 29.8 0.544 52.281 33.1 172
5 GAM3 28.8 0.403 12.743 94.6 239
3 GAM1 21.8 0.405 13.374 68.2 239
10 ran1 19.4 0.406 13.566 59.8 239
9 svm2 14.0 0.385 7.692 76.2 239
8 svm1 0.8 0.359 0.471 71.1 239
$F0$rst5
algo rst prec rorac prPo pos
1 Mean 52.4 0.441 23.604 92.9 239
7 gbm2 46.4 0.440 23.200 83.7 239
6 gbm1 31.2 0.416 16.421 79.5 239
5 GAM3 28.8 0.403 12.743 94.6 239
4 GAM2 28.2 0.481 34.815 47.1 172
11 ran2 26.6 0.422 18.095 61.5 239
2 nai1 23.6 0.519 45.385 30.2 172
3 GAM1 20.6 0.398 11.381 75.7 239
9 svm2 14.4 0.386 8.182 73.6 239
10 ran1 14.0 0.390 9.091 64.4 239
8 svm1 6.2 0.370 3.584 72.4 239
目的是针对unlist
所有列表,将list
名称的顺序作为一列来识别大小写。
r=unlist(unlist(r,F),F)
names(r)
[1] "todos.F0.rst1" "todos.F0.rst5" "todos.T0.rst1" "todos.T0.rst5" "random.F0.rst1" "random.F0.rst5"
[7] "random.T0.rst1" "random.T0.rst5"
取消列出列表,但不列出列表data.frame
。
ra=Reduce(rbind,Map(function(x,y) cbind(case=x,y),names(r),r))
Map
将名称序列放在一列中。Reduce
加入所有data.frame
的。
head(ra)
case algo rst prec rorac prPo pos
1 todos.F0.rst1 Mean 56.4 0.450 25.872 91.2 239
6 todos.F0.rst1 gbm1 41.8 0.438 22.595 77.4 239
4 todos.F0.rst1 GAM2 37.2 0.512 43.256 50.0 172
7 todos.F0.rst1 gbm2 36.8 0.422 18.039 85.4 239
11 todos.F0.rst1 ran2 35.0 0.442 23.810 61.5 239
2 todos.F0.rst1 nai1 29.8 0.544 52.281 33.1 172
PS r[[1]]
:
structure(list(F0 = structure(list(rst1 = structure(list(algo = c("Mean",
"gbm1", "GAM2", "gbm2", "ran2", "nai1", "GAM3", "GAM1", "ran1",
"svm2", "svm1"), rst = c(56.4, 41.8, 37.2, 36.8, 35, 29.8, 28.8,
21.8, 19.4, 14, 0.8), prec = c(0.45, 0.438, 0.512, 0.422, 0.442,
0.544, 0.403, 0.405, 0.406, 0.385, 0.359), rorac = c(25.872,
22.595, 43.256, 18.039, 23.81, 52.281, 12.743, 13.374, 13.566,
7.692, 0.471), prPo = c(91.2, 77.4, 50, 85.4, 61.5, 33.1, 94.6,
68.2, 59.8, 76.2, 71.1), pos = c(239L, 239L, 172L, 239L, 239L,
172L, 239L, 239L, 239L, 239L, 239L)), .Names = c("algo", "rst",
"prec", "rorac", "prPo", "pos"), row.names = c(1L, 6L, 4L, 7L,
11L, 2L, 5L, 3L, 10L, 9L, 8L), class = "data.frame"), rst5 = structure(list(
algo = c("Mean", "gbm2", "gbm1", "GAM3", "GAM2", "ran2",
"nai1", "GAM1", "svm2", "ran1", "svm1"), rst = c(52.4, 46.4,
31.2, 28.8, 28.2, 26.6, 23.6, 20.6, 14.4, 14, 6.2), prec = c(0.441,
0.44, 0.416, 0.403, 0.481, 0.422, 0.519, 0.398, 0.386, 0.39,
0.37), rorac = c(23.604, 23.2, 16.421, 12.743, 34.815, 18.095,
45.385, 11.381, 8.182, 9.091, 3.584), prPo = c(92.9, 83.7,
79.5, 94.6, 47.1, 61.5, 30.2, 75.7, 73.6, 64.4, 72.4), pos = c(239L,
239L, 239L, 239L, 172L, 239L, 172L, 239L, 239L, 239L, 239L
)), .Names = c("algo", "rst", "prec", "rorac", "prPo", "pos"
), row.names = c(1L, 7L, 6L, 5L, 4L, 11L, 2L, 3L, 9L, 10L, 8L
), class = "data.frame")), .Names = c("rst1", "rst5")), T0 = structure(list(
rst1 = structure(list(algo = c("Mean", "ran1", "GAM1", "GAM2",
"gbm1", "svm1", "nai1", "gbm2", "svm2", "ran2"), rst = c(22.6,
19.4, 13.6, 10.2, 9.6, 8, 5.6, 3.4, -0.4, -0.6), prec = c(0.478,
0.452, 0.5, 0.421, 0.423, 0.833, 0.429, 0.373, 0.355, 0.356
), rorac = c(33.731, 26.575, 40, 17.895, 18.462, 133.333,
20, 4.533, -0.526, -0.368), prPo = c(34.4, 52.1, 24.3, 40.7,
37.1, 3.1, 14.4, 53.6, 54.3, 116.4), pos = c(195L, 140L,
140L, 140L, 140L, 195L, 195L, 140L, 140L, 140L)), .Names = c("algo",
"rst", "prec", "rorac", "prPo", "pos"), row.names = c(1L,
9L, 3L, 4L, 5L, 7L, 2L, 6L, 8L, 10L), class = "data.frame"),
rst5 = structure(list(algo = c("gbm1", "ran1", "Mean", "GAM1",
"GAM2", "svm1", "nai1", "svm2", "gbm2", "ran2"), rst = c(17.6,
16.4, 15, 12.8, 9, 6.2, 5.8, -2.6, -3, -9.2), prec = c(0.466,
0.434, 0.435, 0.5, 0.41, 0.8, 0.44, 0.346, 0.345, 0.337),
rorac = c(30.345, 21.579, 21.739, 40, 14.754, 124, 23.2,
-3.21, -3.448, -5.542), prPo = c(41.4, 54.3, 35.4, 22.9,
43.6, 2.6, 12.8, 57.9, 62.1, 118.6), pos = c(140L, 140L,
195L, 140L, 140L, 195L, 195L, 140L, 140L, 140L)), .Names = c("algo",
"rst", "prec", "rorac", "prPo", "pos"), row.names = c(5L,
9L, 1L, 3L, 4L, 7L, 2L, 8L, 6L, 10L), class = "data.frame")), .Names = c("rst1",
"rst5"))), .Names = c("F0", "T0"))
假设我们要计算每个元素的长度。
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软件包(v0.3.0)创建于2019-11-15
第一张图表开始处的下跌是fl幸,请忽略它。
我们看到选择的答案确实更快,并且对于相当数量的迭代,我们的.i()
解决方案确实更慢,与选择的答案相比,开销约为使用的开销的3倍purrr::imap()
,对于30k迭代,开销约为25毫秒,因此每1000次迭代大约损失1毫秒,每百万次损失1秒。我认为这是为方便起见而付出的小费用。
只需编写自己的自定义lapply
函数
lapply2 <- function(X, FUN){
if( length(formals(FUN)) == 1 ){
# No index passed - use normal lapply
R = lapply(X, FUN)
}else{
# Index passed
R = lapply(seq_along(X), FUN=function(i){
FUN(X[[i]], i)
})
}
# Set names
names(R) = names(X)
return(R)
}
然后像这样使用:
lapply2(letters, function(x, i) paste(x, i))