Come ricampionare in R senza ripetere le permutazioni?


12

In R, se set.seed () e quindi utilizzo la funzione di esempio per randomizzare un elenco, posso garantire che non genererò la stessa permutazione?

vale a dire ...

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

Questo produce

[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

tutte le permutazioni stampate saranno permutazioni uniche? O c'è qualche possibilità, in base al modo in cui è implementato, che potrei ottenere alcune ripetizioni?

Voglio essere in grado di farlo senza ripetizioni, garantito. Come potrei farlo?

(Voglio anche evitare di dover usare una funzione come permn (), che ha un metodo molto meccanicistico per generare tutte le permutazioni --- non sembra casuale.)

Inoltre, sidenote --- sembra che questo problema sia O ((n!)!), Se non sbaglio.


Per impostazione predefinita, l'argomento 'sostituisci' di 'campione' è impostato su FALSO.
Ocram,

Grazie Ocram, ma funziona all'interno di un particolare campione. In questo modo si assicura che 0,1,2 e 3 non si ripetano in un pareggio (quindi, non posso disegnare 0,1,2,2), ma non so se ciò garantisca che il secondo campione, Non riesco a disegnare di nuovo la stessa sequenza di 0123. Questo è quello che mi chiedo dal punto di vista dell'implementazione, se l'impostazione del seme ha qualche effetto su quella ripetizione.
Mittenchops

Sì, questo è quello che ho finalmente capito leggendo le risposte ;-)
ocram

1
Se limitsupera 12, probabilmente si esaurirà la RAM quando R tenta di allocare spazio per seq(1,factorial(limit)). (12! Richiede circa 2 GB, quindi 13! Avrà bisogno di circa 25 GB, 14! Circa 350 GB, ecc.)
whuber

2
C'è un veloce, compatto ed elegante soluzione per la generazione di sequenze casuali di tutte le permutazioni di 1: n, a condizione che si può comodamente memorizzare n! numeri interi compresi nell'intervallo 0: (n!). Combina la rappresentazione della tabella di inversione di una permutazione con la rappresentazione base fattoriale dei numeri.
whuber

Risposte:


9

La domanda ha molte interpretazioni valide. I commenti - in particolare quello che indica le permutazioni di 15 o più elementi (15! = 1307674368000 sta diventando grande) - suggeriscono che ciò che si desidera è un campione casuale relativamente piccolo , senza sostituzione, di tutti n! = n * (n-1) (n-2) ... * 2 * 1 permutazioni di 1: n. Se questo è vero, esistono soluzioni (in qualche modo) efficienti.

La seguente funzione rpermaccetta due argomenti n(la dimensione delle permutazioni da campionare) e m(il numero di permutazioni della dimensione n da disegnare). Se m si avvicina o supera n !, la funzione richiederà molto tempo e restituirà molti valori NA: è destinata all'uso quando n è relativamente grande (diciamo 8 o più) e m è molto più piccolo di n !. Funziona memorizzando nella cache una rappresentazione in forma di stringa delle permutazioni trovate finora e quindi generando nuove permutazioni (in modo casuale) fino a quando non ne viene trovata una nuova. Sfrutta la capacità associativa di indicizzazione di elenchi di R per cercare rapidamente l'elenco di permutazioni trovate in precedenza.

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.

La natura di replicateè di restituire le permutazioni come vettori di colonna ; ad esempio , quanto segue riproduce un esempio nella domanda originale, trasposta :

> 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

I tempi sono eccellenti per valori da piccoli a moderati di m, fino a circa 10.000, ma si degradano per problemi più grandi. Ad esempio, un campione di m = 10.000 permutazioni di n = 1000 elementi (una matrice di 10 milioni di valori) è stato ottenuto in 10 secondi; un campione di m = 20.000 permutazioni di n = 20 elementi ha richiesto 11 secondi, anche se l'output (una matrice di 400.000 voci) era molto più piccolo; e il campione di calcolo di m = 100.000 permutazioni di n = 20 elementi è stato interrotto dopo 260 secondi (non ho avuto la pazienza di attendere il completamento). Questo problema di ridimensionamento sembra essere correlato alle inefficienze di ridimensionamento nell'indirizzamento associativo di R. Si può aggirare il problema generando campioni in gruppi, per esempio, circa 1000, quindi combinando quei campioni in un campione di grandi dimensioni e rimuovendo i duplicati.

modificare

Siamo in grado di ottenere prestazioni asintotiche quasi lineari suddividendo la cache in una gerarchia di due cache, in modo che R non debba mai cercare in un ampio elenco. Concettualmente (sebbene non come implementato), creare un array indicizzato dai primi elementi di una permutazione. Le voci in questo array sono elenchi di tutte le permutazioni che condividono quei primi elementi. Per verificare se è stata vista una permutazione, usa i suoi primi elementi per trovare la sua voce nella cache e quindi cercare quella permutazione all'interno di quella voce. Possiamo scegliere per bilanciare le dimensioni previste di tutti gli elenchi. L'implementazione effettiva non utilizza unkkkkk-fold array, che sarebbe difficile da programmare in una generalità sufficiente, ma utilizza invece un altro elenco.

Ecco alcuni tempi trascorsi in secondi per un intervallo di dimensioni di permutazione e numeri di permutazioni distinte richieste:

 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

(L'accelerazione apparentemente anomala da size = 10 a size = 15 è perché il primo livello della cache è più grande per size = 15, riducendo il numero medio di voci negli elenchi di secondo livello, accelerando così la ricerca associativa di R. costo in RAM, l'esecuzione potrebbe essere resa più veloce aumentando la dimensione della cache di livello superiore. Aumentando solo k.headdi 1 (che moltiplica la dimensione di livello superiore per 10) rperm(100000, size=10), ad esempio, si è accelerato da 11,77 secondi a 8,72 secondi. cache 10 volte più grande ma non ottenuto alcun guadagno apprezzabile, con un clock di 8,51 secondi.)

Tranne il caso di 1.000.000 di permutazioni uniche di 10 elementi (una parte sostanziale di tutti i 10! = Circa 3,63 milioni di tali permutazioni), praticamente non sono mai state rilevate collisioni. In questo caso eccezionale, ci furono 169.301 collisioni, ma nessun completo fallimento (furono infatti ottenute un milione di permutazioni uniche).

Si noti che con grandi dimensioni di permutazione (maggiori di circa 20), la possibilità di ottenere due permutazioni identiche anche in un campione grande come 1.000.000.000 è evanescente. Pertanto, questa soluzione è applicabile principalmente in situazioni in cui (a) devono essere generati un gran numero di permutazioni uniche di (b) tra e ma anche così, (c) sostanzialmente meno di tutte lesono necessarie permutazioni.n=5n=15n!

Segue il codice di lavoro.

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.

Questo è vicino, ma noto che ho degli errori, come 1, 2 e 4, ma penso di vedere cosa intendi e dovrei essere in grado di lavorarci. Grazie! > 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

3

Usare uniquenel modo giusto dovrebbe fare il trucco:

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

Ci scusiamo per non aver spiegato correttamente il codice. Sono un po 'di fretta ora, ma sono felice di rispondere a qualsiasi domanda tu abbia in seguito. Inoltre, non ho idea della velocità del codice sopra ...
MånsT

1
Ho funzionalizzato ciò che mi hai dato in questo modo: `myperm <- function (limit) {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))} return (unique (permutations))} 'Funziona, ma mentre I posso fare limit = 6, limit = 7 fa surriscaldare il mio computer. = PI pensa che ci debba essere ancora un modo per sottocampionare questo ...
Mittenchops

@Mittenchops, Perché dici che dobbiamo usare unico per il ricampionamento in R senza ripetere le permutazioni? Grazie.
Frank,

2

Sto andando a fare un piccolo passo avanti con la tua prima domanda, e suggerisco che se hai a che fare con vettori relativamente corti, potresti semplicemente generare tutte le permutazioni usando permne ordinarle casualmente quelle che usano 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

Mi piace MOLTO, e sono sicuro che sia il pensiero giusto. Ma il mio problema mi ha fatto usare una sequenza che va fino a 10. Permn () è stato significativamente più lento tra fattoriale (7) e fattoriale (8), quindi penso che 9 e 10 saranno proibitivamente enormi.
Mittenchops

@Mittenchops Vero, ma è ancora possibile che tu debba davvero calcolarli solo una volta, giusto? Salvarli in un file, quindi caricarli quando sono necessari e "campionarli" dall'elenco predefinito. Quindi potresti fare il calcolo lento permn(10)o qualunque cosa solo una volta.
joran

Giusto, ma se sto conservando tutte le permutazioni da qualche parte, anche questo si rompe per fattoriale (15) --- semplicemente troppo spazio per conservare. Ecco perché mi chiedo se l'impostazione del seme mi permetterà di campionare collettivamente le permutazioni --- e in caso contrario, se esiste un algoritmo per farlo.
Mittenchops

@Mittenchops L'impostazione di un seed non influisce sulle prestazioni, ma garantisce lo stesso avvio ogni volta che si effettua una chiamata a PRNG.
Roman Luštrik,

1
@Mitten Vedi l'aiuto per set.seed: descrive come salvare lo stato del RNG e ripristinarlo in seguito.
whuber
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.