Идиома R для коммутатора/футляра
У меня есть код R, который выглядит в основном так:
compute.quantiles <- function(mu, type) {
## 'mu' and 'type' are vectors of the same length
var <- ifelse(type=='a', 6.3523 * mu^2,
ifelse(type=='b', 234.23 * mu,
ifelse(type=='c', {s <- 9.8 * ((mu-0.3)/3)^(6/7)+0.19; mu + mu^2/s},
ifelse(type=='d', 56.345 * mu^1.5,
ifelse(type=='e', 0.238986 * mu^2,
ifelse(type=='f', mu + 1.1868823 * mu^2,
NA ))))))
# ...then do something with var...
}
Некоторые примеры ввода и вывода:
print(compute.quantiles(2:4, c('c','d','e')))
[1] 2.643840 292.777208 3.823776
Это работает правильно, но это отвратительно с глубоким вложением, поэтому мне интересно, есть ли другая идиома, которая работает лучше. У кого-нибудь есть предложение? Если switch()
принял вектор в качестве первого аргумента, это будет работать красиво, но оно просто принимает скаляр.
Ответы
Ответ 1
Думаю, я придумал что-то, что мне больше нравится:
## Vector-switch
vswitch <- function(EXPR, ...) {
vars <- cbind(...)
vars[cbind(seq_along(EXPR), match(EXPR, names(list(...))))]
}
compute.quantiles <- function(mu, type) {
stopifnot(length(mu) == length(type))
vswitch( type,
a = 6.3523 * mu^2,
b = 234.23 * mu,
c = mu + mu^2/(9.8 * ((mu-0.3)/3)^(6/7)+0.19),
d = 56.345 * mu^1.5,
e = 0.238986 * mu^2,
f = mu + 1.1868823 * mu^2)
}
С кодом индексирования матрицы всего за 2 строки я думаю, что это нормально для моего слишком умного кода. =)
Ответ 2
Вот альтернативный подход:
library(data.table)
# Case selection table:
dtswitch <- data.table(type=letters[1:6],
result=c("6.3523 * mu^2",
"234.23 * mu",
"{s <- 9.8 * ((mu-0.3)/3)^(6/7)+0.19; mu + mu^2/s}",
"56.345 * mu^1.5",
"0.238986 * mu^2",
"mu + 1.1868823 * mu^2"),
key="type")
# Data to which you want the cases applied:
compute <- data.table(type=letters[3:5],mu=2:4,key="type")
# Join the data table with the case selection table, and evaluate the results:
dtswitch[compute,list(mu,result=eval(parse(text=result)))]
#> type mu result
#>1: c 2 2.643840
#>2: d 3 292.777208
#>3: e 4 3.823776
Вместо того, чтобы создавать таблицу dtswitch в R-коде, вы можете сохранить ее во внешней электронной таблице или базе данных, а затем загрузить в нее. Возможно, вам будет удобно, если у вас много разных случаев или они часто меняются, и вы хотите контролируйте их из центра.
Ответ 3
Реализация Кена Уильямса vswitch
не подходит для некоторых типов входов. Я думаю, что это более гибко:
vswitch <- function(expr, ...) {
lookup <- list(...)
vec <- as.character(expr)
vec[is.na(vec)] <- "NA"
unname(do.call(c, lookup[vec]))
}
Чтобы использовать его с числовыми значениями поиска, вам нужно указать или вернуть их:
num_vec <- c(1, 3, 2, 2, 1)
vswitch(num_vec, `1` = 10, `2` = 25, `3` = 50)
## [1] 10 50 25 25 10
С поиском символов:
char_vec <- letters[num_vec]
vswitch(char_vec, a = "Albert", b = "Bertrand", c = "Charles")
## [1] "Albert" "Charles" "Bertrand" "Bertrand" "Albert"
Ответ 4
Возможно, что-то подобное работает:
compute.quantiles <- function(mu, type) {
stopifnot(length(mu) == length(type))
vars <- cbind(
a = 6.3523 * mu^2,
b = 234.23 * mu,
c = mu + mu^2/(9.8 * ((mu-0.3)/3)^(6/7)+0.19),
d = 56.345 * mu^1.5,
e = 0.238986 * mu^2,
f = mu + 1.1868823 * mu^2)
vars[cbind(seq_along(mu), match(type, colnames(vars)))]
}
Не уверен, что это будет выглядеть слишком "продвинутым" для будущего читателя (включая меня).
Ответ 5
Я не мог удержаться от добавления другого ответа с совершенно другим подходом. Вот оно.
## Sort of a cross between tapply() and ave()
tswitch <- function(x, INDEX, ...) {
l <- substitute(list(...))
s <- split(x, INDEX)
pf <- parent.frame()
split(x, INDEX) <- lapply(names(s), function(n)
eval(l[[n]], list(x=s[[n]]), pf)
)
x
}
compute.quantiles <- function(mu, type) {
stopifnot(length(mu) == length(type))
tswitch(mu, type,
a = 6.3523 * x^2,
b = 234.23 * x,
c = x + x^2/(9.8 * ((x-0.3)/3)^(6/7)+0.19),
d = 56.345 * x^1.5,
e = 0.238986 * x^2,
f = x + 1.1868823 * x^2)
}
И пример ввода и вывода:
> compute.quantiles(2:4, c('c','d','e'))
[1] 2.643840 292.777208 3.823776
Преимущество этой реализации заключается в том, что она вычисляет только те значения length(mu)
, которые необходимо вычислить. Напротив, метод vswitch
выше вычисляет значения length(mu) * M
, где M
- количество "случаев" в коммутаторе. Поэтому, если вычисления являются дорогостоящими или большие данные, эта версия может быть выигрышной.