У меня есть вектор x
и функция f
. Мне нужно сгенерировать матрицу, у которой первый столбец — v
, второй столбец — f(v)
и так далее до последнего столбца f^k(v)
. На функциональном языке я мог бы использовать операцию развертывания. Я посмотрел шпаргалку purrr
, но аналога не нашел.
Если вам нужен конкретный пример, возьмите v = c(1:100)
и f = function(x){return (2*x)}
— но, пожалуйста, не злоупотребляйте тем фактом, что в этом случае f^k имеет красивую закрытую форму.
@qwr У меня нет хорошей модели того, что происходит под капотом в R, и я беспокоюсь об эффективности. Например. Я не хочу в конечном итоге выделять k матриц и копировать O(k^2).
Это обобщение матриц Вандермонда?
что не так с циклом for? Я обнаружил, что функциональный код в R может работать намного медленнее, если он не оптимизирован для конкретной задачи. особенно муррр.
@qwr Я читал с момента публикации вопроса и пришел к такому же выводу. Я думаю, дело в том, что я новичок в R и никогда не видел цикла for в коде R! (Кроме того, не Вандермонд. Что-то вроде моделирования цепи Маркова.)
Большинство людей используют R для управления фреймами данных (аналогично реляционной модели данных). Общие матричные вещи — сильная сторона numpy.
Вы ищете что-то вроде:
v = c(1:100)
f = function(x){return (2*x)}
df <- data.frame(v)
for( i in 1:5) {
df[,(i+1)] <- f(df[,i])
}
Возможно. Я не понимаю там модель памяти. Что происходит внутри, когда вы продолжаете добавлять новые столбцы в v
?
Я действительно не знаю. Может не вызывать беспокойства, если только v не очень длинное или k не велико. Если вас беспокоит создание и удаление нескольких матриц, просто заранее выделите окончательный размер.
Я бы написал это как предварительное выделение памяти способом C/FOTRAN, потому что нет смысла динамически увеличивать объект, окончательный размер которого вам известен.
Другой подход, выращивание формулы функции вместо объекта памяти (см. R-Inferno стр. 12):
|>
, затем анализирует строку и оценивает ее по x
:
raise_f <- \(f, k, x) {
parse(text = paste('x |> ',
sprintf('(%s)()', paste(deparse(f), collapse = ' ')) |>
rep(k) |> paste(collapse = ' |> ')
)
) |> eval()
}
cbind
полученные векторы в матрицу:
f <- \(x) 2 * x
k <- 3
init <- 1:5
do.call(cbind, Map(1:3, f = \(k) raise_f(f, k, 1:5)))
выход:
## [,1] [,2] [,3]
## [1,] 2 4 8
## [2,] 4 8 16
## [3,] 6 12 24
## [4,] 8 16 32
## [5,] 10 20 40
Сравнение производительности с циклом матрицы:
library(microbenchmark)
microbenchmark(
loop_a_matrix = for( i in 1:5) {df[,(i+1)] <- f(df[,i])},
grow_a_formula = do.call(cbind, Map(1:5, f = \(k) raise_f(f, k, 1:100)))
)
## Unit: microseconds
## expr min lq mean median uq max neval cld
## loop_a_matrix 5481.6 6350.55 7254.535 7012.95 7999.2 12083.5 100 a
## grow_a_formula 919.1 1044.00 1335.874 1219.80 1419.6 3817.9 100 b
Это приносит очки за сообразительность, но «Если ответ parse()
, вам обычно следует переосмыслить вопрос» (Томас Ламли 2005, fortunes::fortune("If the answer is parse")
)
Однажды мне придется перечитать (и полностью понять) Advanced R :-)
У нас есть функциональное программирование, поэтому нам не нужно вот так проделывать строковые функции!
@qwr: есть какие-нибудь подсказки по построению рекурсивного выражения типа f(f(x))
из f (не делая f рекурсивной функцией)?
Вы можете использовать Reduce
, как показано ниже.
v <- 1:10
k <- 5
f <- \(x) 2 * x
do.call(cbind, Reduce(\(x, y) f(x), rep(list(v), 5), accumulate = TRUE))
что дает результат
[,1] [,2] [,3] [,4] [,5]
[1,] 1 2 4 8 16
[2,] 2 4 8 16 32
[3,] 3 6 12 24 48
[4,] 4 8 16 32 64
[5,] 5 10 20 40 80
[6,] 6 12 24 48 96
[7,] 7 14 28 56 112
[8,] 8 16 32 64 128
[9,] 9 18 36 72 144
[10,] 10 20 40 80 160
@BenBolker ага, ты прав! Я забыл cbind
их
Это также самый быстрый (на данный момент) FWIW
@BenBolker вау, это превзошло мои ожидания, рад видеть такую скорость 😀
Предварительное выделение матрицы и ее заполнение происходит примерно в 10 раз быстрее, хотя более медленной версии по-прежнему требуется всего около 0,1 секунды для построения матрицы 1000x100... использование Reduce()
увеличивает скорость в два раза. Попытка использовать метод построения строк приводит к ошибке «слишком глубоко вложенная оценка».
f <- function(x) {return (2*x)}
f1 <- function(n1 = 1000, n2 = 1000) {
df <- data.frame(seq.int(n1))
for (i in 1:(n2-1)) {
df[,(i+1)] <- f(df[,i])
}
df <- as.matrix(df)
dimnames(df) <- NULL
df
}
f2 <- function(n1= 1000, n2 = 1000) {
df <- matrix(nrow=n1, ncol = n2)
df[,1] <- seq.int(n1)
for (i in 1:(n2-1)) {
df[,(i+1)] <- f(df[,i])
}
df
}
f3 <- function(n1 = 1000, n2 = 1000) {
v <- seq.int(n1)
do.call(cbind, Reduce(\(x, y) f(x), rep(list(v), n2),
accumulate = TRUE))
}
bench:mark(f1(), f2(), f3() )
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl>
1 f1() 126.75ms 126.81ms 7.89 NA 3.94 2 1
2 f2() 8.14ms 8.93ms 107. NA 19.5 33 6
3 f3() 3.67ms 3.98ms 221. NA 21.2 73 7
(Я думаю, что медленный метод был бы намного медленнее, если бы вы увеличивали фрейм данных по строкам, а не по столбцам...)
возможно, есть короткое рекурсивное решение, если вы определите свою собственную рекурсию