【问题标题】:Python's xrange alternative for R OR how to loop over large dataset lazilly?R 的 Python 的 xrange 替代方案或如何懒惰地循环大型数据集?
【发布时间】:2016-03-21 23:09:12
【问题描述】:

以下示例基于discussion 关于使用expand.grid 处理大数据。如您所见,它以错误告终。我猜这是由于可能的组合,根据mentioned page 687 亿:

> v1 <-  c(1:8)
> v2 <-  c(1:8)
> v3 <-  c(1:8)
> v4 <-  c(1:8)
> v5 <-  c(1:8)
> v6 <-  c(1:8)
> v7 <-  c(1:8)
> v8 <-  c(1:8)
> v9 <-  c(1:8)
> v10 <- c(1:8)
> v11 <- c(1:8)
> v12 <- c(1:8)
> 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

即使有八个向量,它也会杀死我的 CPU 和/或 RAM (&gt; expand.grid(v1, v2, v3, v4, v5, v6, v7, v8))。 Here 我发现了一些改进建议使用outerrep.int。这些解决方案适用于两个向量,因此我无法将其应用于 12 个向量,但我想原理是相同的:它创建了驻留在内存中的大型矩阵。我想知道是否有类似 python 的 xrange 可以懒惰地评估? Here 我找到了delayedAssign 函数,但我想这无济于事,因为还提到了以下内容:

不幸的是,当惰性变量由 一个数据结构,即使当时不需要它们的值。这 意味着无限的数据结构,一种常见的应用 Haskell 中的懒惰,在 R 中是不可能的。

是否只使用嵌套循环来解决这个问题?

PS:我没有具体问题,但假设您需要使用接受 12 个整数参数的函数进行一些计算,出于某种原因。还假设您需要对这 12 个整数进行所有组合并将结果保存到文件中。使用 12 个嵌套循环并将结果连续保存到文件将起作用(尽管它会很慢但不会杀死你的 RAM)。 Here 展示了如何使用 expand.gridapply 函数来替换两个嵌套循环。问题是使用expand.grid 创建这样的矩阵有12 个长度为8 的向量有一些缺点:

  1. 生成这样的矩阵很慢
  2. 如此大的矩阵消耗大量内存(687 亿行 8 列)
  3. 使用apply 对该矩阵进行进一步迭代也很慢

所以在我看来,功能性方法比程序性解决方案要慢得多。我只是想知道是否有可能懒惰地创建理论上不适合内存并对其进行迭代的大型数据结构。就是这样。

【问题讨论】:

  • 什么问题?我看到一堆链接,对“它”的引用,以及来自 Python 的名为 xrange 的东西。你能用一两句话说明你想要完成什么吗?
  • 你想要完成什么,你需要创造(甚至是幽灵)如此巨大的物体
  • 有一个用于 R 的 iterators 包可能会有所帮助...
  • 你能解释一下为什么我被否决了吗?请澄清什么不清楚或我应该提供什么进一步的信息。谢谢

标签: r lazy-evaluation infinite


【解决方案1】:

解决此问题的一种(可以说是更“正确”)的方法是为@BenBolker 建议的iterators 编写自己的迭代器(关于编写扩展的pdf 是here)。缺少更正式的东西,这里是一个穷人的迭代器,类似于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()
#   a  b  c
# 1 1 15 21
nxt()
#   a  b  c
# 1 2 15 21
nxt()
#   a  b  c
# 1 3 15 21
nxt()
#   a  b  c
# 1 1 16 21

## <yawn>

nxt()
#   a  b  c
# 1 3 16 22
nxt()
# [1] FALSE

注意:为简洁起见,我以as.data.frame(mapply(...)) 为例;无论哪种方式都可以,但如果命名列表适合您,则无需转换为 data.frame。

编辑

基于alexis_laz's answer,这是一个大大改进的版本,它 (a) 速度更快,并且 (b) 允许任意搜索。

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())
#   a b c d e f
# 1 1 1 1 1 1 1
nxt(sample(1e2^6, size=7))
#      a  b  c  d  e  f
# 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 对 cumprodMap 和索引计算的改进!

【讨论】:

  • 谢谢你这是非常有用的帖子。是否也可以将nxt() 用作X 函数系列的X 参数?我想这是不可能的,唯一的方法是循环使用它,但我可能错了。谢谢
  • 如果您好奇(并且喜欢冒险),我添加了更多功能并将其作为gist 提供。欢迎评论。
  • 顺便说一句:不,我认为不可能在 *apply 函数中使用它。我相信他们需要提前知道列表/向量的(有限)长度,并为返回值预先分配内存。 (不过,我不是 R 的核心开发人员,所以我可能对这个评论有点偏离。)
【解决方案2】:

另一种方法,不知何故,看起来有效..:

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

与此类似的方法是构造一个“类”来存储... 参数以在需要时使用expand.grid 并定义[ 方法来计算适当的组合索引。使用%%%/% 似乎是有效的,不过,我猜使用这些运算符进行迭代会比它需要的慢。

【讨论】:

  • cumprod 的绝妙技巧,并且拥有可搜索的版本非常有用。 (我从你的代码中得到一个cannot allocate vector 错误,而不是NAs introduced。你知道为什么会出现这个错误吗?)不知道为什么我这些年来一直没有使用lengths,哇哇比sapply(dots, length).
  • 我看到的一个问题是它会翻车。试试exp_gr(1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, 1:1e2, index = 1e12+1)。一个简单的边界检查应该涵盖这种情况。
  • @r2evans :谢谢,你说得对。此外,除非有大量的大长度... 参数,否则cumprod 似乎是可行的(否则会有一些Infs)。至于你得到的错误,似乎我早些时候被rep阻止,试图强制“整数”一个很大的值,结果NA导致“无效时间”,而你后来被阻止了(不是无效的范围整数,而是)要创建的对象的大尺寸;我猜你会通过在expand.grid 中添加更多/更长的元素来获得“引入的 NA”?
  • (续).. 例如请参阅rep.int(1, .Machine$integer.max)rep.int(1, .Machine$integer.max + 1)
猜你喜欢
  • 2021-10-19
  • 1970-01-01
  • 1970-01-01
  • 2011-10-14
  • 2015-07-30
  • 1970-01-01
  • 1970-01-01
  • 2014-09-01
  • 2013-01-09
相关资源
最近更新 更多