順列を繰り返さずに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!)!)のようです。


デフォルトでは、「sample」の引数「replace」はFALSEに設定されています。
ocram 2012年

ocramに感謝しますが、それは特定のサンプル内で機能しています。これにより、0、1、2、3が描画内で繰り返されないことが保証されます(したがって、0、1、2、2は描画できません)が、2番目のサンプルが保証されているかどうかはわかりません。同じ0123のシーケンスを再び描くことはできません。それは、シードを設定することがその繰り返しに影響を与えるかどうか、実装面で私が疑問に思っていることです。
Mittenchops 2012年

はい、これは私が回答を読んで最終的に理解したものです;-)
ocram

1
場合はlimit12を超えるRの試みはのためのスペースを割り当てるとき、あなたはおそらくRAMを使い果たしますseq(1,factorial(limit))。(12!には約2 GBが必要なので、13!には約25 GB、14!には約350 GBなどが必要です)
whuber

2
nを快適に格納できる場合、1:nのすべての順列のランダムシーケンスを生成するための高速、コンパクト、かつエレガントなソリューションがあります。0:(n!)の範囲の整数。これは、順列の反転テーブル表現と数値の階乗ベース表現を組み合わせています。
whuber

回答:


9

質問には多くの有効な解釈があります。コメント(特に15以上の要素の順列が必要であるというコメントが必要です(15!= 1307674368000が大きくなっています))-必要なのは、すべてのnの置換なしの比較的小さなランダムサンプルであることを示唆しています!= n *(n-1)(n-2) ... * 2 * 1 1:nの順列。これが本当なら、(ある程度)効率的なソリューションが存在します。

次の関数はrperm、2つの引数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まで)では優れていますが、より大きな問題では低下します。たとえば、n = 1000要素のm = 10,000順列(1,000万値の行列)のサンプルが10秒で取得されました。出力(400,000エントリの行列)ははるかに小さくても、n = 20要素のm = 20,000置換のサンプルには11秒必要でした。また、m = 100,000の順列の計算、n = 20の要素の計算は、260秒後に中止されました(完了を待つ忍耐力がありませんでした)。このスケーリングの問題は、Rの連想アドレッシングにおけるスケーリングの非効率性に関連しているようです。たとえば、1000程度のグループでサンプルを生成し、それらのサンプルを結合して大きなサンプルを作成し、重複を削除することで、この問題を回避できます。

編集する

Rが大きなリストを検索する必要がないように、キャッシュを2つのキャッシュの階層に分割することで、ほぼ線形の漸近的なパフォーマンスを実現できます。概念的には(実装されているわけではありませんが)、順列の最初の要素によってインデックスが付けられた配列を作成します。この配列のエントリは、最初の要素を共有するすべての順列のリストです。順列が表示されたかどうかを確認するには、最初の要素を使用してキャッシュ内のエントリを見つけ、そのエントリ内でその順列を検索します。すべてのリストの予想されるサイズのバランスを取るためにを選択できます。実際の実装では使用しませんk k k kkkkkk-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の方が大きく、第2レベルのリストのエントリの平均数を減らし、それによってRの連想検索を高速化するためです。 RAMのコストを節約するには、上位レベルのキャッシュサイズを増やすことで実行を高速化できます。たとえば、k.head1を増やすだけで(上位レベルのサイズに10を掛ける)rperm(100000, size=10)、11.77秒から8.72秒にスピードアップします。キャッシュは10倍大きくなりますが、8.51秒で計時するほどの向上はありません。

10個の要素の1,000,000個の一意の置換(10個すべてのかなりの部分!=約363万個のそのような置換)の場合を除いて、実際には衝突は検出されませんでした。この例外的なケースでは、169,301回の衝突がありましたが、完全な失敗はありませんでした(実際に100万の一意の順列が取得されました)。

順列サイズが大きい(20を超えるなど)場合、1,000,000,000ものサンプルでも2つの同じ順列を取得する可能性は非常に小さいことに注意してください。したがって、このソリューションは主に(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
私はあなたが私にこのように与えたものを機能化しました: `myperm <-function(limit){myindex <-seq(0、limit)endDim <-factorial(limit)permutations <-sample(myindex)while(is.null(dim(unique (順列)))|| dim(unique(permutations))[1]!= endDim){順列<-rbind(permutations、sample(myindex))} return(unique(permutations))} '動作しますが、I limit = 6、limit = 7を実行すると、コンピュータが過熱します。= PIは、これをサブサンプリングする方法がまだあるはずだと考えています...
Mittenchops

@Mittenchops、なぜ順列を繰り返さずにRでリサンプリングするためにuniqueを使用する必要があると言うのですか?ありがとうございました。
フランク

2

I」側のステップにあなたの最初の質問ビットを行くメートル、そしてあなたは、比較的短いベクトルを扱っている場合、あなたは、単に使用して、すべての順列を生成することができることを示唆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štrik12年

1
@Mittenのヘルプを参照してください。RNG set.seedの状態を保存して後で復元する方法について説明しています。
whuber
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.