web-dev-qa-db-fra.com

Accéder aux noms d'index lapply dans FUN

Est-il possible d’obtenir le nom d’index de la liste dans ma fonction lapply ()?

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

J'ai demandé à avant s'il est possible de conserver les noms d'index dans la liste lapply () renvoyée , mais je ne le fais toujours pas. savoir s'il existe un moyen simple d'extraire chaque nom d'élément dans la fonction personnalisée. Je voudrais éviter d'appeler lapply sur les noms eux-mêmes, je préfère obtenir le nom dans les paramètres de la fonction.

149
Robert Kubrick

Malheureusement, lapply ne vous donne que les éléments du vecteur que vous lui transmettez. La solution habituelle consiste à lui transmettre les noms ou les indices du vecteur au lieu du vecteur lui-même.

Mais notez que vous pouvez toujours transmettre des arguments supplémentaires à la fonction, ainsi:

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

Ici, j'utilise lapply sur les index de x, mais je passe également dans x et les noms de x. Comme vous pouvez le constater, l'ordre des arguments de la fonction peut être n'importe quoi - lapply passera dans "l'élément" (ici l'index) au premier argument et non spécifié parmi les extra. Dans ce cas, je spécifie y et n, donc il ne reste que i ...

Qui produit ce qui suit:

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

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

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

UPDATE Exemple plus simple, même résultat:

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

Ici, la fonction utilise la variable "globale" x et extrait les noms dans chaque appel.

143
Tommy

Cela utilise essentiellement la même solution que Tommy, mais avec Map(), il n'est pas nécessaire d'accéder à des variables globales qui stockent les noms des composants de liste.

> 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

Ou, si vous préférez mapply()

> mapply(function(x, i) paste(i, x), x, names(x))
     a      b      c 
"a 11" "b 12" "c 13"
44
caracal

PDATE pour la version 3.2

Avertissement: il s'agit d'un truc hacky qui risque de ne plus fonctionner dans les prochaines versions.

Vous pouvez obtenir l'index en utilisant ceci:

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

$b
[1] 2

Remarque: le paramètre [] Est nécessaire pour que cela fonctionne, car cela incite R à penser que le symbole i (résidant dans le cadre d'évaluation de lapply) peut avoir plus de références, activant ainsi la duplication paresseuse de celui-ci. Sans cela, R ne conservera pas de copies séparées de i:

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

$b
[1] 2

D'autres astuces exotiques peuvent être utilisées, comme function(x){parent.frame()$i+0} ou function(x){--parent.frame()$i}.

Impact sur les performances

La duplication forcée entraînera-t-elle une perte de performances? Oui! voici les repères:

> 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

Cette réponse montre simplement que vous ne devez PAS utiliser ceci ... Non seulement votre code sera plus lisible si vous trouvez une autre solution comme celle de Tommy ci-dessus, et plus compatible avec les versions futures, vous risquez également de perdre les optimisations que l'équipe principale a travaillées d'arrache-pied. développer!


Les astuces des anciennes versions ne fonctionnent plus:

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

Résultat:

$a
[1] 1

$b
[1] 2

$c
[1] 3

Explication: lapply crée des appels de la forme FUN(X[[1L]], ...), FUN(X[[2L]], ...) etc. L'argument transmis est donc X[[i]]i est l'index actuel dans la boucle. Si nous obtenons ceci avant il est évalué (c'est-à-dire si nous utilisons substitute), nous obtenons l'expression non évaluée X[[i]] . Ceci est un appel à la fonction [[, Avec les arguments X (un symbole) et i (un entier). Donc, substitute(x)[[3]] renvoie exactement cet entier.

Avec l'index, vous pouvez accéder aux noms de manière triviale, si vous le sauvegardez d'abord comme ceci:

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

Résultat:

$a
[1] "a"

$b
[1] "b"

$c
[1] "c"

Ou en utilisant cette seconde astuce: :-)

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

(le résultat est le même).

Explication 2: sys.call(1) renvoie lapply(...), de sorte que sys.call(1)[[2]] est l'expression utilisée comme argument de liste pour lapply. Le fait de passer ceci à eval crée un objet légitime auquel names peut accéder. Difficile, mais ça marche.

Bonus: une deuxième façon d'obtenir les noms:

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

Notez que X est un objet valide dans le cadre parent de FUN et référence l'argument de liste de lapply afin que nous puissions y arriver avec eval.parent .

36
Ferdinand.kraft

J'ai souvent eu le même problème ... J'ai commencé à utiliser une autre méthode ... Au lieu d'utiliser lapply, j'ai commencé à utiliser mapply

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

Vous pouvez essayer d'utiliser imap() de purrr package.

de la documentation:

imap (x, ...) est un raccourci pour map2 (x, noms (x), ...) si x porte des noms ou map2 (x, seq_along (x), ...) si ce n'est pas le cas.

Donc, vous pouvez l'utiliser de cette façon:

library(purrr)
myList <- list(a=11,b=12,c=13) 
imap(myList, function(x, y) paste(x, y))

Ce qui vous donnera le résultat suivant:

$a
[1] "11 a"

$b
[1] "12 b"

$c
[1] "13 c"
10
Kevin Zarca

Il suffit de boucler les noms.

sapply(names(mylist), function(n) { 
    doSomething(mylist[[n]])
    cat(n, '\n')
}
9
incitatus451

La réponse de Tommy s’applique aux vecteurs nommés, mais j’ai eu l’idée que les listes vous intéressaient. Et il semble qu'il se soit arrêté parce qu'il faisait référence à "x" dans l'environnement d'appel. Cette fonction utilise uniquement les paramètres qui ont été transmis à la fonction et n'émet donc aucune hypothèse sur le nom des objets transmis:

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
5
42-

Ma réponse va dans le même sens que Tommy's et caracals, mais évite de devoir sauvegarder la liste en tant qu’objet supplémentaire.

lapply(seq(3), function(i, y=list(a=14,b=15,c=16)) { paste(names(y)[[i]], y[[i]]) })

Résultat:

[[1]]
[1] "a 14"

[[2]]
[1] "b 15"

[[3]]
[1] "c 16"

Cela donne la liste comme argument nommé à FUN (au lieu de lapply). lapply n'a qu'à parcourir les éléments de la liste (veillez à changer ce premier argument en lapply lorsque vous modifiez la longueur de la liste).

Remarque: Donner la liste directement à lapply comme argument supplémentaire fonctionne également:

lapply(seq(3), function(i, y) { paste(names(y)[[i]], y[[i]]) }, y=list(a=14,b=15,c=16))
4
Julian

@Caracals et @Tommy sont de bonnes solutions. Voici un exemple incluant list´s et data.frame´s.
r est un list de list´s et data.frame´s (dput(r[[1]] à la fin).

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

L'objectif est de unlist toutes les listes, en mettant la séquence de noms de list sous forme de colonnes pour identifier le cas.

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"

Décompressez les listes mais pas le data.frame ´s.

ra=Reduce(rbind,Map(function(x,y) cbind(case=x,y),names(r),r))

Map place la séquence de noms sous forme de colonne. Reduce rejoindre tous les 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

P.S. 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"))
3
xm1

Disons que nous voulons calculer la longueur de chaque élément.

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

Si le but est simplement de nommer les éléments résultants, alors lapply(mylist,length) ou inférieure fonctionne.

sapply(mylist,length,USE.NAMES=T)

 a  b  c 
 4  8 11 

Si l'objectif est d'utiliser l'étiquette à l'intérieur de la fonction, alors mapply() est utile en parcourant deux objets; les éléments de la liste et les noms de la liste.

fun <- function(x,y) paste0(length(x),"_",y)
mapply(fun,mylist,names(mylist))

     a      b      c 
 "4_a"  "8_b" "11_c" 
0
rmf