Подмножество с отрицательными индексами: лучшие практики?

#r

#r

Вопрос:

Допустим, у меня есть функция для подмножества (это всего лишь минимальный пример).:

 f <- function(x, ind = seq(length(x))) {
  x[ind]
}
 

(Примечание: можно было бы использовать только seq(x) вместо seq(length(x)) , но я не нахожу это очень понятным.)

Итак, если

 x <- 1:5
ind <- c(2, 4)
ind2 <- which(x > 5) # integer(0)
 

У меня есть следующие результаты:

 f(x) 
[1] 1 2 3 4 5
f(x, ind)
[1] 2 4
f(x, -ind)
[1] 1 3 5
f(x, ind2)
integer(0)
f(x, -ind2)
integer(0)
 

Для последнего результата мы хотели бы получить все x , но это распространенная причина ошибок (как упоминалось в книге Advanced R).

Итак, если я хочу создать функцию для удаления индексов, я использую:

 f2 <- function(x, ind.rm) {
  f(x, ind = `if`(length(ind.rm) > 0, -ind.rm, seq(length(x))))
}
 

Тогда я получу то, что хотел:

 f2(x, ind)
[1] 1 3 5
f2(x, ind2)
[1] 1 2 3 4 5
 

Мой вопрос:
Могу ли я сделать что-то более чистое, и это не требует seq(length(x)) явной передачи f2 , но напрямую использует значение f параметра по умолчанию ind , когда ind.rm есть integer(0) ?

Комментарии:

1. Наилучшей практикой было бы не использовать `if` , как будто это была функция.

2. @HongOoi Тем не менее, это так.

3. ind = if(length(ind.rm) > 0) -ind.rm else seq(length(x))

Ответ №1:

Если вы ожидаете, что у вас будет много «пустых» отрицательных индексов, вы можете повысить производительность в этих случаях, если сможете избежать индексации, используемой x[seq(x)] as, а не just x . Другими словами, если вы можете объединить f и f2 во что-то вроде:

 new_f <- function(x, ind.rm){
  if(length(ind.rm)) x[-ind.rm] else x
}
 

В случае пустых отрицательных индексов произойдет огромное ускорение.

 n <- 1000000L
x <- 1:n
ind <- seq(0L,n,2L)
ind2 <- which(x>n 1) # integer(0)

library(microbenchmark)
microbenchmark(
  f2(x, ind),
  new_f(x, ind),
  f2(x, ind2),
  new_f(x, ind2)
)
all.equal(f2(x, ind), new_f(x, ind)) # TRUE - same result at about same speed
all.equal(f2(x, ind2), new_f(x, ind2)) # TRUE - same result at much faster speed

Unit: nanoseconds
           expr     min        lq        mean  median       uq      max neval
     f2(x, ind) 6223596 7377396.5 11039152.47 9317005 10271521 50434514   100
  new_f(x, ind) 6190239 7398993.0 11129271.17 9239386 10202882 59717093   100
    f2(x, ind2) 6823589 7992571.5 11267034.52 9217149 10568524 63417978   100
 new_f(x, ind2)     428    1283.5     5414.74    6843     7271    14969   100
 

Комментарии:

1. Это полезно знать. Обычно я очень забочусь о производительности. В данном конкретном случае это не то, к чему я буду обращаться много раз. Так что 11 мс для меня хорошо. Как и в моем комментарии к ответу Джона, мне нужно такое поведение для 2 или 3 параметров, поэтому использование типичных операторов if-then-else приведет к созданию сложного кода.

Ответ №2:

То, что у вас есть, неплохо, но если вы хотите избежать передачи значения по умолчанию аргумента по умолчанию, вы можете реструктурировать его следующим образом:

 f2 <- function(x, ind.rm) {
    `if`(length(ind.rm) > 0, f(x,-ind.rm), f(x))
}
 

что немного короче того, что у вас есть.

При редактировании

Судя по комментариям, кажется, вы хотите иметь возможность ничего не передавать функции (а не просто не передавать вообще), чтобы она использовала значение по умолчанию. Вы можете сделать это, написав функцию, которая настроена на получение nothing , также известную как NULL . Вы можете переписать свой f as:

 f <- function(x, ind = NULL) {
    if(is.null(ind)){ind <- seq(length(x))}
    x[ind]
}
 

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

Теперь f2 можно переписать как

 f2 <- function(x, ind.rm) {
    f(x, ind = `if`(length(ind.rm) > 0, -ind.rm, NULL))
}
 

Это немного более читабельно, чем то, что у вас есть, но за счет увеличения длины исходной функции.

Комментарии:

1. не могли бы вы объяснить использование if с обратными ссылками здесь? Это то же ifelse самое, что и? ?ifelse отмечает, что: Дополнительно обратите внимание, что if(test) yes else no это намного эффективнее и часто намного предпочтительнее ifelse(test, yes, no) , чем всякий раз, когда тест представляет собой простой результат true / false, т. Е. Когда length(test) == 1 .

2. Это синтаксис для функции if R (аналогично ? : C). Обратные пометки должны отличать его от использования ключевого слова в потоке управления if ). Я на самом деле не использую эту версию, но не видел причин изменять использование OP здесь. Вы правы, что это похоже ifelse .

3. «Обычный» R if сам по себе является функцией. Обратные знаки, очевидно, обозначают нестандартный способ вызова этой функции. Я подозреваю, что под капотом if(test) yes else no в некотором смысле находится синтаксический сахар для версии с обратным тиком.

4. @C8H10N4O2 Поскольку я использовал if , чтобы получить значение и передать это значение в качестве параметра, я предпочел использовать обратные метки, чтобы оно было записано как функция. В ответе Джона использование if-then-else подходит. ifelse на самом деле это не так, как если бы-тогда-еще, и я обычно стараюсь его не использовать. Просто проверьте результат ifelse(0 > 1, NULL, 1:5) , чтобы понять, что я имею в виду.

5. @Coleman Ваш ответ хорош для проблемы, которую я представил. Я хотел, чтобы минимальный пример был как можно более простым, я должен был быть более точным. На самом деле, я делаю подмножество объектов, подобных матрице, так что у меня будет 4 случая (потому что у меня есть индексы для строк и столбцов), и 8 случаев, если я хочу такого же поведения для третьего параметра. Я хотел бы что-то сказать, parameter1 = if(cond1) then value1 else default_value_of_param и то же самое для параметра2 или любых других параметров..

Ответ №3:

Чтобы реализовать «parameter1 = if(cond1) then value1 else default_value_of_param1», я formals получал параметры по умолчанию как call :

 f <- function(x, ind.row = seq_len(nrow(x)), ind.col = seq_len(ncol(x))) {
  x[ind.row, ind.col]
}

f2 <- function(x, ind.row.rm = integer(0), ind.col.rm = integer(0)) {
  f.args <- formals(f)
  f(x, 
    ind.row = `if`(length(ind.row.rm) > 0, -ind.row.rm, eval(f.args$ind.row)),
    ind.col = `if`(length(ind.col.rm) > 0, -ind.col.rm, eval(f.args$ind.col)))
}
 

Затем:

 > x <- matrix(1:6, 2)

> f2(x, 1:2)
     [,1] [,2] [,3]

> f2(x, , 1:2)
[1] 5 6

> f2(x, 1, 2)
[1] 2 6

> f2(x, , 1)
     [,1] [,2]
[1,]    3    5
[2,]    4    6

> f2(x, 1, )
[1] 2 4 6

> f2(x)
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6