如何在R中重新采样而不重复排列?


12

在R中,如果我先set.seed(),然后使用样本函数将列表随机化,是否可以保证不会生成相同的排列?

即...

set.seed(25)
limit <- 3
myindex <- seq(0,limit)
for (x in seq(1,factorial(limit))) {
    permutations <- sample(myindex)
    print(permutations)
}

这产生

[1] 1 2 0 3
[1] 0 2 1 3
[1] 0 3 2 1
[1] 3 1 2 0
[1] 2 3 0 1
[1] 0 1 3 2

打印的所有排列都是唯一的排列吗?还是基于实现的方式,有机会我可以重复一遍?

我希望能够做到这一点而无需重复,保证。我该怎么办?

(我还希望避免使用像permn()这样的函数,该函数具有用于生成所有排列的非常机械的方法-它看起来并不是随机的。)

另外,旁注-如果我没记错的话,这个问题似乎是O((n!)!)。


默认情况下,“样本”的参数“替换”设置为FALSE。
ocram 2012年

感谢ocram,但这在特定示例中有效。这样可以确保在抽签中不会重复出现0、1、2和3(因此,我不能绘制0、1、2、2),但是我不知道这是否可以保证第二个样本,我无法再次绘制相同的0123序列。这就是我想在实现方面的问题,设置种子是否会对重复产生任何影响。
Mittenchops 2012年

是的,这是我通过阅读答案终于明白的;-)
ocram 2012年

1
如果limit超过12,则当R尝试为分配空间时,您可能会用完RAM seq(1,factorial(limit))。(12!需要大约2 GB,所以13!将需要大约25 GB,14!大约需要350 GB,等等。)
whuber

2
有一种快速,紧凑且优雅的解决方案,可以生成所有 1:n排列的随机序列,前提是您可以舒适地存储n!0:(n!)范围内的整数。它将排列的反转表表示与数字的阶乘基本表示相结合。
ub

Answers:


9

这个问题有许多有效的解释。注释(尤其是需要15个或更多元素的排列的注释(15!= 1307674368000越来越大))建议,建议所需要的是相对较小的随机样本,而不替换所有n!个样本。= n *(n-1)(n-2) ... * 2 * 1 1:n的排列。如果是这样,则存在(某种程度上)有效的解决方案。

以下函数rperm接受两个参数n(要采样m的排列的大小)和(要绘制的大小为n的排列的数目)。如果m接近或超过n !,则该函数将花费很长时间并返回许多NA值:该函数适用于n相对较大(例如8或更大)且m比n!小得多的情况。它的工作方式是缓存到目前为止找到的排列的字符串表示形式,然后(随机)生成新的排列,直到找到一个新的排列。它利用R的关联列表索引功能来快速搜索先前找到的排列列表。

rperm <- function(m, size=2) { # Obtain m unique permutations of 1:size

    # Function to obtain a new permutation.
    newperm <- function() {
        count <- 0                # Protects against infinite loops
        repeat {
            # Generate a permutation and check against previous ones.
            p <- sample(1:size)
            hash.p <- paste(p, collapse="")
            if (is.null(cache[[hash.p]])) break

            # Prepare to try again.
            count <- count+1
            if (count > 1000) {   # 1000 is arbitrary; adjust to taste
                p <- NA           # NA indicates a new permutation wasn't found
                hash.p <- ""
                break
            }
        }
        cache[[hash.p]] <<- TRUE  # Update the list of permutations found
        p                         # Return this (new) permutation
    }

    # Obtain m unique permutations.
    cache <- list()
    replicate(m, newperm())  
} # Returns a `size` by `m` matrix; each column is a permutation of 1:size.

本质上replicate是将排列返回为向量;例如,以下是原始问题转置后的示例:

> set.seed(17)
> rperm(6, size=4)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    2    4    4    3    4
[2,]    3    4    1    3    1    2
[3,]    4    1    3    2    2    3
[4,]    2    3    2    1    4    1

时序对于m的小到中等值是极好的,最高可达10,000,但是对于较大的问题会降低时序。例如,在10秒内获得了n = 1000个元素的m = 10,000个排列的样本(一千万个值的矩阵);即使输出(400,000个条目的矩阵)小得多,一个m = 20,000个n = 20个元素的排列的样本也需要11秒;260秒后,中止了m = 100,000个n = 20个元素的排列的计算样本(我没有耐心等待完成)。此缩放问题似乎与R的关联寻址的缩放效率低下有关。一个人可以解决这个问题,方法是生成大约1000个左右的样本组,然后将这些样本合并成一个大样本并删除重复项。

编辑

通过将缓存分成两个缓存的层次结构,我们可以实现接近线性渐近的性能,因此R不必搜索大列表。从概念上(尽管未实现),创建一个由排列的前元素索引的数组。该数组中的条目是共享那些共享前元素的所有排列的列表。要检查是否已看到排列,请使用其前元素在缓存中找到其条目,然后在该条目中搜索该排列。我们可以选择来平衡所有列表的预期大小。实际的实现不使用ķ ķ ķ ķkkkkk-fold数组,很难以足够的通用性进行编程,而是使用另一个列表。

这是一系列排列大小和请求的不同排列数的经过时间(以秒为单位):

 Number Size=10 Size=15 Size=1000 size=10000 size=100000
     10    0.00    0.00      0.02       0.08        1.03
    100    0.01    0.01      0.07       0.64        8.36
   1000    0.08    0.09      0.68       6.38
  10000    0.83    0.87      7.04      65.74
 100000   11.77   10.51     69.33
1000000  195.5   125.5

(显然,从size = 10到size = 15的异常加速是因为对于size = 15,缓存的第一级较大,从而减少了第二级列表中条目的平均数量,从而加快了R的关联搜索。在某些情况下例如,由于增加了RAM的开销,可以通过增加上层缓存的大小来加快执行速度,例如,仅增加k.head1(将上层大小乘以10)就可以rperm(100000, size=10)从11.77秒加快到8.72秒。快10倍,却没有获得可观的增益,时钟为8.51秒。)

除了10个元素的1,000,000个唯一排列的情况(所有10!的很大一部分=约363万个此类排列)之外,几乎没有检测到碰撞。在这种例外情况下,发生了169,301次碰撞,但没有完全失败(实际上获得了一百万个唯一排列)。

注意,对于较大的排列大小(大于20左右),即使在高达1,000,000,000的样本中获得两个相同排列的机会也很小。因此,该解决方案主要适用于以下情况:(a)将会生成(b)在和之间的大量唯一置换,因此,即使如此,(c)基本上少于所有元素需要排列。n = 15 n n=5n=15n!

工作代码如下。

rperm <- function(m, size=2) { # Obtain m unique permutations of 1:size
    max.failures <- 10

    # Function to index into the upper-level cache.
    prefix <- function(p, k) {    # p is a permutation, k is the prefix size
        sum((p[1:k] - 1) * (size ^ ((1:k)-1))) + 1
    } # Returns a value from 1 through size^k

    # Function to obtain a new permutation.
    newperm <- function() {
        # References cache, k.head, and failures in parent context.
        # Modifies cache and failures.        

        count <- 0                # Protects against infinite loops
        repeat {
            # Generate a permutation and check against previous ones.
            p <- sample(1:size)
            k <- prefix(p, k.head)
            ip <- cache[[k]]
            hash.p <- paste(tail(p,-k.head), collapse="")
            if (is.null(ip[[hash.p]])) break

            # Prepare to try again.
            n.failures <<- n.failures + 1
            count <- count+1
            if (count > max.failures) {  
                p <- NA           # NA indicates a new permutation wasn't found
                hash.p <- ""
                break
            }
        }
        if (count <= max.failures) {
            ip[[hash.p]] <- TRUE      # Update the list of permutations found
            cache[[k]] <<- ip
        }
        p                         # Return this (new) permutation
    }

    # Initialize the cache.
    k.head <- min(size-1, max(1, floor(log(m / log(m)) / log(size))))
    cache <- as.list(1:(size^k.head))
    for (i in 1:(size^k.head)) cache[[i]] <- list()

    # Count failures (for benchmarking and error checking).
    n.failures <- 0

    # Obtain (up to) m unique permutations.
    s <- replicate(m, newperm())
    s[is.na(s)] <- NULL
    list(failures=n.failures, sample=matrix(unlist(s), ncol=size))
} # Returns an m by size matrix; each row is a permutation of 1:size.

这很接近,但是我注意到我遇到了一些错误,例如1、2和4,但是我想我明白了你的意思,应该可以使用它。谢谢!> rperm(6,3) $failures [1] 9 $sample [,1] [,2] [,3] [1,] 3 1 3 [2,] 2 2 1 [3,] 1 3 2 [4,] 1 2 2 [5,] 3 3 1 [6,] 2 1 3
Mittenchops 2012年

3

unique正确的方式使用应该可以达到目的:

set.seed(2)
limit <- 3
myindex <- seq(0,limit)

endDim<-factorial(limit)
permutations<-sample(myindex)

while(is.null(dim(unique(permutations))) || dim(unique(permutations))[1]!=endDim) {
    permutations <- rbind(permutations,sample(myindex))
}
# Resulting permutations:
unique(permutations)

# Compare to
set.seed(2)
permutations<-sample(myindex)
for(i in 1:endDim)
{
permutations<-rbind(permutations,sample(myindex))
}
permutations
# which contains the same permutation twice

抱歉,无法正确解释代码。我现在有点着急,但是很高兴回答您以后遇到的任何问题。另外,我不知道关于上述代码的速度...
MånsT

1
我通过这种方式对您给我的功能进行了功能化: (排列)))|| dim(唯一(排列))[1]!= endDim){排列<-rbind(排列,样本(myindex))} return(唯一(排列))}'有效,可以做limit = 6,limit = 7会使我的计算机过热。= PI认为仍然有办法对此进行二次采样...
Mittenchops 2012年

@Mittenchops,为什么要说我们需要在R中使用unique进行重采样而不重复排列?谢谢。
弗兰克,

2

我“米要边踩你的第一个问题一点,并建议,如果你正在处理的比较短的载体,你可以简单地使用所有的排列产生permn并随机排序使用sample

x <- combinat:::permn(1:3)
> x[sample(factorial(3),factorial(3),replace = FALSE)]
[[1]]
[1] 1 2 3

[[2]]
[1] 3 2 1

[[3]]
[1] 3 1 2

[[4]]
[1] 2 1 3

[[5]]
[1] 2 3 1

[[6]]
[1] 1 3 2

我很喜欢这个,我相信这是正确的想法。但是我的问题是我使用的序列最多增加到10个。Permn()在factorial(7)和factorial(8)之间的速度明显变慢,因此我认为9和10会变得过大。
Mittenchops 2012年

@Mittenchops是的,但是您真的仍然可能只需要计算一次,对吗?将它们保存到文件中,然后在需要时加载它们,并从预定义列表中“采样”。因此,您可以permn(10)仅对一次进行慢速计算。
joran 2012年

是的,但是如果我将所有排列存储在某个地方,那么即使这个排列也分解为factorial(15)---太多的存储空间了。这就是为什么我想知道设置种子是否可以让我集体采样排列的方法,如果没有,那么是否有算法可以做到这一点。
Mittenchops 2012年

@Mittenchops设置种子不会影响性能,它只是保证每次调用PRNG时都以相同的开始。
RomanLuštrik2012年

1
@Mitten请参阅帮助set.seed:它描述了如何保存RNG的状态并在以后还原它。
ub
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.