Получите доступ к именам индексов внутри FUN - PullRequest
146 голосов
/ 31 марта 2012

Есть ли способ получить имя индекса списка в моей функции lapply ()?

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

Я спрашивал до , возможно ли сохранить имена индексов в lapply () вернул список, но я до сих пор не знаю, есть ли простой способ получить имя каждого элемента внутри пользовательской функции.Я хотел бы избежать вызова lapply для самих имен, я бы предпочел получить имя в параметрах функции.

Ответы [ 10 ]

141 голосов
/ 31 марта 2012

К сожалению, 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 и извлекает имена в каждом вызове.

41 голосов
/ 12 декабря 2013

Это в основном использует тот же обходной путь, что и 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"
36 голосов
/ 29 августа 2013

ОБНОВЛЕНИЕ для версии R 3.2

Отказ от ответственности: это хитрый трюк, и он может перестать работать в следующих выпусках.

Вы можете получить индекс, используяthis:

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

Заключение

Этот ответ просто показывает, что вы НЕ должны использовать это ... Не только ваш код будет более читабельным, если вынайдите другое решение, подобное описанному выше Томми, и более совместимое с будущими выпусками, вы также рискуете потерять оптимизации, над которыми усердно работала основная команда!


Уловки старых версий, больше не работающие:

> 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, иссылается на аргумент списка lapply, поэтому мы можем получить его с помощью eval.parent.

17 голосов
/ 23 апреля 2015

У меня была одна и та же проблема много раз ... Я начал использовать другой способ ... Вместо использования lapply, я начал использовать mapply

n = names(mylist)
mapply(function(list.elem, names) { }, list.elem = mylist, names = n)
8 голосов
/ 07 ноября 2017

Вы можете попробовать использовать imap() из пакета purrr.

Из документации:

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"
8 голосов
/ 15 января 2016

Просто зациклите имена.

sapply(names(mylist), function(n) { 
    doSomething(mylist[[n]])
    cat(n, '\n')
}
5 голосов
/ 31 марта 2012

Ответ Томми относится к именованным векторам, но я понял, что вас интересуют списки. И кажется, что он делал обход, потому что он ссылался на «х» из вызывающей среды. Эта функция использует только те параметры, которые были переданы функции, и поэтому не делает никаких предположений относительно имени объектов, которые были переданы:

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
4 голосов
/ 04 ноября 2014

Мой ответ идет в том же направлении, что и у Томми и Каракалов, но избегает необходимости сохранять список в качестве дополнительного объекта.

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))
3 голосов
/ 02 мая 2018

И @caracals, и @Tommy - хорошие решения, и этот пример включает list ´s и data.frame ´s.
r - это list из list ´s и data.frame ´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 ´s.

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

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"))
0 голосов
/ 17 августа 2016

Просто напишите свою собственную 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))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...