在 FUN 中访问 lapply 索引名称

Posted

技术标签:

【中文标题】在 FUN 中访问 lapply 索引名称【英文标题】:Access lapply index names inside FUN 【发布时间】:2012-04-14 13:29:02 【问题描述】:

有没有办法在我的 lapply() 函数中获取列表索引名称?

n = names(mylist)
lapply(mylist, function(list.elem)  cat("What is the name of this list element?\n" )

我问before 是否可以在 lapply() returned 列表中保留索引名称,但我仍然不知道是否有一种简单的方法来获取其中的每个元素名称自定义函数。我想避免在名称本身上调用 lapply,我宁愿在函数参数中获取名称。

【问题讨论】:

还有一个技巧,属性。见这里:***.com/questions/4164960/… 这有点类似于 DWin 的,但不同。 :) 【参考方案1】:

不幸的是,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,但也传入xx 的名称。正如你所看到的,函数参数的顺序可以是任何东西——lapply 将把“元素”(这里是索引)传递给额外的参数中指定的第一个参数not。在这种情况下,我指定了yn,所以只剩下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 并在每次调用中提取名称。

【讨论】:

自定义函数中'i'参数是如何初始化的? 知道了,所以 lapply() 确实适用于 seq_along 返回的元素。我很困惑,因为自定义函数参数被重新排序。通常迭代的列表元素是第一个参数。 更新了答案并将第一个函数更改为使用y 而不是x,以便(希望)更清楚地知道该函数可以调用它的任何参数。还将向量值更改为11,12,13 @RobertKubrick - 是的,我可能试图一次显示太多东西......你可以为参数命名任何东西并以任何顺序排列它们。 @DWin - 我认为它是正确的(也适用于列表);-) ...但请证明我错了!【参考方案2】:

这基本上使用与 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() 内)使其按预期运行。【参考方案3】:

R 3.2 版更新

免责声明:这是一个 hacky 技巧,可能会在下一个版本中停止工作。

您可以使用以下方法获取索引:

> lapply(list(a=10,b=20), function(x)parent.frame()$i[])
$a
[1] 1

$b
[1] 2

注意:[] 是此工作所必需的,因为它欺骗 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+0function(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 那样的另一个解决方案,你的代码不仅会更具可读性,而且与未来的版本更兼容,你还可能会失去核心团队的优化努力发展!


旧版本的技巧,不再有效:

> 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]]])

注意XFUN的父框架中的一个有效对象,并且引用了lapply的list参数,所以我们可以用eval.parent得到它。

【讨论】:

代码lapply(list(a=10,b=10,c=10), function(x)substitute(x)[[3]]) 全部返回为 3。您能解释一下这个 3 是如何选择的吗?和差异的原因?它是否等于列表的长度,在这种情况下,3。对不起,如果这是一个基本问题,但想知道如何在一般情况下应用它。 @Anusha,确实,该表单不再起作用了...但是lapply(list(a=10,b=10,c=10), function(x)eval.parent(quote(names(X)))[substitute(x)[[3]]]) 起作用了...我会检查发生了什么。 @Ferdinand.kraft, 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' 有没有简单的方法来解决这个问题? 非常感谢@Ferdinand.kraft【参考方案4】:

我遇到过很多次同样的问题... 我已经开始使用另一种方式......我没有使用lapply,而是开始使用mapply

n = names(mylist)
mapply(function(list.elem, names)  , list.elem = mylist, names = n)

【讨论】:

我也喜欢这个,但这个答案是a previous one的副本。【参考方案5】:

您可以尝试使用来自purrr 包的imap()

来自文档:

imap(x, ...) 是 map2(x, names(x), ...) 的简写,如果 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"

【讨论】:

【参考方案6】:

只需循环输入名称即可。

sapply(names(mylist), function(n)  
    doSomething(mylist[[n]])
    cat(n, '\n')

【讨论】:

这当然是最简单的解决方案了。 @flies:是的,除了在函数内部硬编码变量mylist 是不好的做法。最好还是做function(mylist, nm) ...【参考方案7】:

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 中的名称添加到结果之后 是的。同意这是本练习的教训。【参考方案8】:

我的回答与 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))

【讨论】:

【参考方案9】:

@caracals 和 @Tommy 都是很好的解决方案,这是一个示例,包括 list´s 和 data.frame´s。rlistlist´s 和 @987654326 @´s(最后是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´s。

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

附: 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"))

【讨论】:

【参考方案10】:

假设我们要计算每个元素的长度。

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" 

【讨论】:

【参考方案11】:

@ferdinand-kraft 给了我们一个绝妙的技巧,然后告诉我们不应该使用它 因为它没有记录,而且性能开销很大。

我不能对第一点争论太多,但我想指出,开销 应该很少担心。

让我们定义活动函数,这样我们就不必调用复杂的表达式 parent.frame()$i[] 但只有.i(),我们还会创建.n() 来访问 名称,它应该适用于 basepurrr 功能(可能还有大多数其他功能)。

.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 package (v0.3.0) 于 2019 年 11 月 15 日创建

第一个图表开头的下跌是侥幸,请忽略它。

我们看到选择的答案确实更快,并且对于相当数量的迭代,我们的 .i() 解决方案确实更慢,与选择的答案相比,开销大约是使用 purrr::imap() 的开销的 3 倍,并且数量大约 30k 次迭代需要 25 毫秒,所以我每 1000 次迭代损失大约 1 毫秒,每百万次损失 1 秒。在我看来,这是为了方便起见的一小笔费用。

【讨论】:

【参考方案12】:

只需编写您自己的自定义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))

【讨论】:

这根本不健壮,谨慎使用

以上是关于在 FUN 中访问 lapply 索引名称的主要内容,如果未能解决你的问题,请参考以下文章

访问 lapply 列名

将多个参数传递给 lapply 的 FUN

lapply和do.call有什么区别?

使用R语言将不同长度的向量合并为数据框

如何修改多个数据框而不列出它们然后使用 lapply?

如何在闪亮中使用 lapply 存储输出?