Unfortunately, lapply
only gives you the elements of the vector you pass it.
The usual work-around is to pass it the names or indices of the vector instead of the vector itself.
But note that you can always pass in extra arguments to the function, so the following works:
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))
Here I use lapply
over the indices of x
, but also pass in x
and the names of x
. As you can see, the order of the function arguments can be anything - lapply
will pass in the "element" (here the index) to the first argument not specified among the extra ones. In this case, I specify y
and n
, so there's only i
left...
Which produces the following:
[[1]]
[1] "a 11"
[[2]]
[1] "b 12"
[[3]]
[1] "c 13"
UPDATE Simpler example, same result:
lapply(seq_along(x), function(i) paste(names(x)[[i]], x[[i]]))
Here the function uses "global" variable x
and extracts the names in each call.
This basically uses the same workaround as Tommy, but with Map()
, there's no need to access global variables which store the names of list components.
> 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
Or, if you prefer mapply()
> mapply(function(x, i) paste(i, x), x, names(x))
a b c
"a 11" "b 12" "c 13"
UPDATE for R version 3.2
Disclaimer: this is a hacky trick, and may stop working in the the next releases.
You can get the index using this:
> lapply(list(a=10,b=20), function(x){parent.frame()$i[]})
$a
[1] 1
$b
[1] 2
Note: the []
is required for this to work, as it tricks R into thinking that the symbol i
(residing in the evaluation frame of lapply
) may have more references, thus activating the lazy duplication of it. Without it, R will not keep separated copies of i
:
> lapply(list(a=10,b=20), function(x){parent.frame()$i})
$a
[1] 2
$b
[1] 2
Other exotic tricks can be used, like function(x){parent.frame()$i+0}
or function(x){--parent.frame()$i}
.
Performance Impact
Will the forced duplication cause performance loss? Yes! here are the benchmarks:
> 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
Conclusion
This answer just shows that you should NOT use this... Not only your code will be more readable if you find another solution like Tommy's above, and more compatible with future releases, you also risk losing the optimizations the core team has worked hard to develop!
Old versions' tricks, no longer working:
> lapply(list(a=10,b=10,c=10), function(x)substitute(x)[[3]])
Result:
$a
[1] 1
$b
[1] 2
$c
[1] 3
Explanation: lapply
creates calls of the form FUN(X[[1L]], ...)
, FUN(X[[2L]], ...)
etc. So the argument it passes is X[[i]]
where i
is the current index in the loop. If we get this before it's evaluated (i.e., if we use substitute
), we get the unevaluated expression X[[i]]
. This is a call to [[
function, with arguments X
(a symbol) and i
(an integer). So substitute(x)[[3]]
returns precisely this integer.
Having the index, you can access the names trivially, if you save it first like this:
L <- list(a=10,b=10,c=10)
n <- names(L)
lapply(L, function(x)n[substitute(x)[[3]]])
Result:
$a
[1] "a"
$b
[1] "b"
$c
[1] "c"
Or using this second trick: :-)
lapply(list(a=10,b=10,c=10), function(x)names(eval(sys.call(1)[[2]]))[substitute(x)[[3]]])
(result is the same).
Explanation 2: sys.call(1)
returns lapply(...)
, so that sys.call(1)[[2]]
is the expression used as list argument to lapply
. Passing this to eval
creates a legitimate object that names
can access. Tricky, but it works.
Bonus: a second way to get the names:
lapply(list(a=10,b=10,c=10), function(x)eval.parent(quote(names(X)))[substitute(x)[[3]]])
Note that X
is a valid object in the parent frame of FUN
, and references the list argument of lapply
, so we can get to it with eval.parent
.
I've had the same problem a lot of times...
I've started using another way... Instead of using lapply
, I've started using mapply
n = names(mylist)
mapply(function(list.elem, names) { }, list.elem = mylist, names = n)
You could try using imap()
from purrr
package.
From the documentation:
imap(x, ...) is short hand for map2(x, names(x), ...) if x has names, or map2(x, seq_along(x), ...) if it does not.
So, you can use it that way :
library(purrr)
myList <- list(a=11,b=12,c=13)
imap(myList, function(x, y) paste(x, y))
Which will give you the following result:
$a
[1] "11 a"
$b
[1] "12 b"
$c
[1] "13 c"
Just loop in the names.
sapply(names(mylist), function(n) {
doSomething(mylist[[n]])
cat(n, '\n')
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With