Альтернатива Python xrange для R ИЛИ как петляться над большим набором данных lazilly?

Следующий пример основан на обсуждении использования expand.grid с большими данными. Как вы видите, это заканчивается ошибкой. Я предполагаю, что это связано с возможными комбинациями, которые соответствуют указанным страницам 68,7 миллиарда:

 > v1  v2  v3  v4  v5  v6  v7  v8  v9  v10  v11  v12  expand.grid(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) Error in rep.int(rep.int(seq_len(nx), rep.int(rep.fac, nx)), orep) : invalid 'times' value In addition: Warning message: In rep.int(rep.int(seq_len(nx), rep.int(rep.fac, nx)), orep) : NAs introduced by coercion to integer range 

Даже с восемью векторами он убивает мой процессор и / или оперативную память ( > expand.grid(v1, v2, v3, v4, v5, v6, v7, v8) ). Здесь я нашел некоторые улучшения, которые предполагают использование outer или rep.int . Эти решения работают с двумя векторами, поэтому я не могу применить его для 12 векторов, но, я думаю, принцип тот же: он создает большую матрицу, которая находится в памяти. Мне интересно, есть ли что-то вроде python xrange, который оценивается лениво? Здесь я нашел функцию delayedAssign но я думаю, это не поможет, потому что также упоминается следующее:

К сожалению, R оценивает ленивые переменные, когда на них указывает структура данных, даже если их значение не требуется в то время. Это означает, что бесконечные структуры данных, одно общее применение лени в Хаскелле, невозможно в Р.

Использует ли вложенные петли только решение этой проблемы?

PS: У меня нет конкретной проблемы, но предположим, что вам нужно сделать некоторые вычисления, используя функцию, которая по какой-то причине принимает 12 целых аргументов. Также предположим, что вам нужно сделать все комбинации этих 12 целых чисел и сохранить результаты в файл. Использование 12 вложенных циклов и сохранение результатов в файл будет работать (несмотря на то, что он будет медленным, но он не будет убивать вашу оперативную память). Здесь показано, как вы можете использовать expand.grid и apply функцию для замены двух вложенных циклов. Проблема в том, что создание такой матрицы с 12 векторами длиной 8 с использованием expand.grid имеет некоторые недостатки:

  1. генерация такой матрицы медленная
  2. такая большая matrix потребляет много памяти (68,7 миллиарда строк и 8 столбцов)
  3. дальнейшая итерация по этой матрице с использованием apply также медленна

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

Один (возможно, более «правильный») способ приблизиться к этому – это написать свой собственный iterator для iterators который предложил @BenBolker (pdf здесь для написания расширений). Отсутствие чего-то более формального, вот iterator бедного человека, похожий на expand.grid но с ручным продвижением. (Примечание: этого достаточно, учитывая, что вычисление на каждой итерации «более дорого», чем сама эта функция. Это действительно может быть улучшено, но «это работает».)

Эта функция возвращает именованный список (с предоставленными факторами) каждый раз, когда возвращается возвращаемая функция. Он ленив в том, что он не расширяет весь список возможностей; они не ленивы с самим аргументом, их следует «немедленно уничтожить».

 lazyExpandGrid <- function(...) { dots <- list(...) sizes <- sapply(dots, length, USE.NAMES = FALSE) indices <- c(0, rep(1, length(dots)-1)) function() { indices[1] <<- indices[1] + 1 DONE <- FALSE while (any(rolls <- (indices > sizes))) { if (tail(rolls, n=1)) return(FALSE) indices[rolls] <<- 1 indices[ 1+which(rolls) ] <<- indices[ 1+which(rolls) ] + 1 } mapply(`[`, dots, indices, SIMPLIFY = FALSE) } } 

Использование образца:

 nxt <- lazyExpandGrid(a=1:3, b=15:16, c=21:22) nxt() # abc # 1 1 15 21 nxt() # abc # 1 2 15 21 nxt() # abc # 1 3 15 21 nxt() # abc # 1 1 16 21 ##  nxt() # abc # 1 3 16 22 nxt() # [1] FALSE 

NB: для краткости отображения я использовал as.data.frame(mapply(...)) для примера; он работает в любом случае, но если именованный список отлично подходит для вас, то преобразование в data.frame не требуется.

РЕДАКТИРОВАТЬ

Основываясь на ответе alexis_laz , вот значительно улучшенная версия, которая (а) намного быстрее и (б) позволяет произвольно искать.

 lazyExpandGrid <- function(...) { dots <- list(...) argnames <- names(dots) if (is.null(argnames)) argnames <- paste0('Var', seq_along(dots)) sizes <- lengths(dots) indices <- cumprod(c(1L, sizes)) maxcount <- indices[ length(indices) ] i <- 0 function(index) { i <<- if (missing(index)) (i + 1L) else index if (length(i) > 1L) return(do.call(rbind.data.frame, lapply(i, sys.function(0)))) if (i > maxcount || i < 1L) return(FALSE) setNames(Map(`[[`, dots, (i - 1L) %% indices[-1L] %/% indices[-length(indices)] + 1L ), argnames) } } 

Он работает без аргументов (автоинкремент внутреннего счетчика), один аргумент (поиск и установка внутреннего счетчика) или векторный аргумент (искать для каждого и устанавливать счетчик последним, возвращает data.frame).

Этот последний случай использования позволяет отбирать подмножество проектного пространства:

 set.seed(42) nxt <- lazyExpandGrid2(a=1:1e2, b=1:1e2, c=1:1e2, d=1:1e2, e=1:1e2, f=1:1e2) as.data.frame(nxt()) # abcdef # 1 1 1 1 1 1 1 nxt(sample(1e2^6, size=7)) # abcdef # 2 69 61 7 7 49 92 # 21 72 28 55 40 62 29 # 3 88 32 53 46 18 65 # 4 88 33 31 89 66 74 # 5 57 75 31 93 70 66 # 6 100 86 79 42 78 46 # 7 55 41 25 73 47 94 

Спасибо alexis_laz за улучшения расчетов cumprod, Map и index!

Другой подход, который каким-то образом выглядит действительным ..:

 exp_gr = function(..., index) { args = list(...) ns = lengths(args) offs = cumprod(c(1L, ns)) n = offs[length(offs)] stopifnot(index <= n) i = (index[[1L]] - 1L) %% offs[-1L] %/% offs[-length(offs)] return(do.call(data.frame, setNames(Map("[[", args, i + 1L), paste("Var", seq_along(args), sep = "")))) } 

В приведенной выше функции ... являются аргументами expand.grid а index - все большее число комбинаций. Например:

 expand.grid(1:3, 10:12, 21:24, letters[2:5])[c(5, 22, 24, 35, 51, 120, 144), ] # Var1 Var2 Var3 Var4 #5 2 11 21 b #22 1 11 23 b #24 3 11 23 b #35 2 12 24 b #51 3 11 22 c #120 3 10 22 e #144 3 12 24 e do.call(rbind, lapply(c(5, 22, 24, 35, 51, 120, 144), function(i) exp_gr(1:3, 10:12, 21:24, letters[2:5], index = i))) # Var1 Var2 Var3 Var4 #1 2 11 21 b #2 1 11 23 b #3 3 11 23 b #4 2 12 24 b #5 3 11 22 c #6 3 10 22 e #7 3 12 24 e 

И на больших структурах:

 expand.grid(1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2) #Error in rep.int(rep.int(seq_len(nx), rep.int(rep.fac, nx)), orep) : # invalid 'times' value #In addition: Warning message: #In rep.int(rep.int(seq_len(nx), rep.int(rep.fac, nx)), orep) : # NAs introduced by coercion to integer range exp_gr(1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, index = 1) # Var1 Var2 Var3 Var4 Var5 Var6 #1 1 1 1 1 1 1 exp_gr(1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, index = 1e3 + 487) # Var1 Var2 Var3 Var4 Var5 Var6 #1 87 15 1 1 1 1 exp_gr(1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, index = 1e2 ^ 6) # Var1 Var2 Var3 Var4 Var5 Var6 #1 100 100 100 100 100 100 exp_gr(1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, index = 1e11 + 154) # Var1 Var2 Var3 Var4 Var5 Var6 #1 54 2 1 1 1 11 

Аналогичным подходом к этому было бы создание «classа», в котором хранятся аргументы ... для использования expand.grid on и определения [ метода для вычисления соответствующего индекса комбинации, когда это необходимо. Использование %% и %/% кажется действительно, однако, я думаю, что итерация с этими операторами будет медленнее, чем должно быть.

  • Передавать аргументы функции dplyr
  • Давайте будем гением компьютера.