Acceda a los nombres de índice de Lapply dentro de FUN

Resuelto Robert Kubrick asked hace 54 años • 12 respuestas

¿Hay alguna manera de obtener el nombre del índice de la lista en mi función lapply()?

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

Pregunté antes si es posible conservar los nombres de los índices en la lista devuelta por lapply() , pero todavía no sé si hay una manera fácil de recuperar el nombre de cada elemento dentro de la función personalizada. Me gustaría evitar llamar a lapply en los nombres mismos, prefiero obtener el nombre en los parámetros de la función.

Robert Kubrick avatar Jan 01 '70 08:01 Robert Kubrick
Aceptado

Desafortunadamente, lapplysolo te da los elementos del vector que le pasas. La solución habitual es pasarle los nombres o índices del vector en lugar del vector en sí.

Pero tenga en cuenta que siempre puede pasar argumentos adicionales a la función, por lo que lo siguiente funciona:

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

Aquí utilizo lapplylos índices de x, pero también paso xlos nombres de x. Como puede ver, el orden de los argumentos de la función puede ser cualquier cosa: lapplypasará el "elemento" (aquí el índice) al primer argumento no especificado entre los adicionales. En este caso especifico yy n, así que solo queda i...

Lo que produce lo siguiente:

[[1]]
[1] "a 11"

[[2]]
[1] "b 12"

[[3]]
[1] "c 13"

ACTUALIZAR Ejemplo más simple, mismo resultado:

lapply(seq_along(x), function(i) paste(names(x)[[i]], x[[i]]))

Aquí la función utiliza una variable "global" xy extrae los nombres en cada llamada.

Tommy avatar Mar 30 '2012 20:03 Tommy

Básicamente, esto utiliza la misma solución que Tommy, pero con Map(), no es necesario acceder a las variables globales que almacenan los nombres de los componentes de la lista.

> 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

O, si lo prefieresmapply()

> mapply(function(x, i) paste(i, x), x, names(x))
     a      b      c 
"a 11" "b 12" "c 13"
caracal avatar Dec 12 '2013 14:12 caracal

ACTUALIZACIÓN para R versión 3.2

Descargo de responsabilidad: este es un truco complicado y puede dejar de funcionar en las próximas versiones.

Puedes obtener el índice usando esto:

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

$b
[1] 2

Nota: se []requiere para que esto funcione, ya que engaña a R haciéndole pensar que el símbolo i(que reside en el marco de evaluación de lapply) puede tener más referencias, activando así su duplicación diferida. Sin él, R no mantendrá copias separadas de i:

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

$b
[1] 2

Se pueden utilizar otros trucos exóticos, como function(x){parent.frame()$i+0}o function(x){--parent.frame()$i}.

Impacto en el rendimiento

¿La duplicación forzada provocará una pérdida de rendimiento? ¡Sí! aquí están los puntos de referencia:

> 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

Conclusión

Esta respuesta simplemente muestra que NO debes usar esto... No solo tu código será más legible si encuentras otra solución como la de Tommy anterior, y más compatible con versiones futuras, sino que también corres el riesgo de perder las optimizaciones en las que el equipo central ha trabajado arduamente. ¡desarrollar!


Los trucos de las versiones antiguas ya no funcionan:

> lapply(list(a=10,b=10,c=10), function(x)substitute(x)[[3]])

Resultado:

$a
[1] 1

$b
[1] 2

$c
[1] 3

Explicación: lapplycrea llamadas del formulario FUN(X[[1L]], ...), FUN(X[[2L]], ...)etc. Entonces, el argumento que pasa es X[[i]]dónde iestá el índice actual en el bucle. Si obtenemos esto antes de que sea evaluado (es decir, si usamos substitute), obtenemos la expresión no evaluada X[[i]]. Esta es una llamada a [[una función, con argumentos X(un símbolo) y i(un número entero). Entonces substitute(x)[[3]]devuelve precisamente este número entero.

Teniendo el índice, puedes acceder a los nombres de forma trivial, si lo guardas primero así:

L <- list(a=10,b=10,c=10)
n <- names(L)
lapply(L, function(x)n[substitute(x)[[3]]])

Resultado:

$a
[1] "a"

$b
[1] "b"

$c
[1] "c"

O usando este segundo truco :-)

lapply(list(a=10,b=10,c=10), function(x)names(eval(sys.call(1)[[2]]))[substitute(x)[[3]]])

(el resultado es el mismo).

Explicación 2: sys.call(1)devuelve lapply(...), por lo que esa sys.call(1)[[2]]es la expresión utilizada como argumento de lista para lapply. Pasar esto a evalcrea un objeto legítimo al que namesse puede acceder. Difícil, pero funciona.

Bonificación: una segunda forma de obtener los nombres:

lapply(list(a=10,b=10,c=10), function(x)eval.parent(quote(names(X)))[substitute(x)[[3]]])

Tenga en cuenta que Xes un objeto válido en el marco principal de FUNy hace referencia al argumento de lista de lapply, por lo que podemos acceder a él con eval.parent.

Ferdinand.kraft avatar Aug 29 '2013 14:08 Ferdinand.kraft