¿Cómo volver a muestrear en R sin repetir permutaciones?

12

En R, si configuro.seed () y luego uso la función de muestra para aleatorizar una lista, ¿puedo garantizar que no generaré la misma permutación?

es decir...

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

Esto 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

¿Todas las permutaciones impresas serán permutaciones únicas? ¿O hay alguna posibilidad, en función de la forma en que se implementa, de que pueda obtener algunas repeticiones?

Quiero poder hacer esto sin repeticiones, garantizado. ¿Como podría hacerlo?

(También quiero evitar tener que usar una función como permn (), que tiene un método muy mecanicista para generar todas las permutaciones --- no parece aleatorio).

Además, nota al margen --- parece que este problema es O ((n!)!), Si no me equivoco.

Mittenchops
fuente
Por defecto, el argumento 'reemplazar' de 'muestra' está establecido en FALSO.
ocram
Gracias ocram, pero eso está funcionando dentro de una muestra particular. Entonces eso asegura que 0,1,2 y 3 no se repitan dentro de un sorteo (por lo tanto, no puedo sacar 0,1,2,2), pero no sé si eso garantiza que la segunda muestra, No puedo volver a dibujar la misma secuencia de 0123. Eso es lo que me pregunto en términos de implementación, si establecer la semilla tiene algún efecto en esa repetición.
Mittenchops
Sí, esto es lo que finalmente entendí al leer las respuestas ;-)
ocram
1
Si limitexcede de 12, es probable que se quede sin RAM cuando R intente asignar espacio para seq(1,factorial(limit)). (12! Requiere aproximadamente 2 GB, por lo que 13! Necesitará aproximadamente 25 GB, 14!
Aproximadamente
2
Existe una solución rápida, compacta y elegante para generar secuencias aleatorias de todas las permutaciones de 1: n, ¡siempre que pueda almacenar cómodamente n! enteros en el rango 0: (n!). Combina la representación de la tabla de inversión de una permutación con la representación de números de base factorial .
whuber

Respuestas:

9

La pregunta tiene muchas interpretaciones válidas. Los comentarios, especialmente el que indica que se necesitan permutaciones de 15 o más elementos (15! = 1307674368000 se está haciendo grande), sugieren que lo que se desea es una muestra aleatoria relativamente pequeña , sin reemplazo, de todo n! = n * (n-1) (n-2) ... * 2 * 1 permutaciones de 1: n. Si esto es cierto, existen soluciones (algo) eficientes.

La siguiente función, rpermacepta dos argumentos n(el tamaño de las permutaciones para muestrear) y m(el número de permutaciones de tamaño n para dibujar). Si m se aproxima o excede n !, la función tomará mucho tiempo y devolverá muchos valores de NA: está destinada a usarse cuando n es relativamente grande (digamos, 8 o más) ym es mucho más pequeño que n !. Funciona almacenando en caché una representación de cadena de las permutaciones encontradas hasta ahora y luego generando nuevas permutaciones (al azar) hasta que se encuentre una nueva. Explota la capacidad asociativa de indexación de listas de R para buscar rápidamente la lista de permutaciones encontradas previamente.

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 naturaleza de replicatees devolver las permutaciones como vectores de columna ; por ejemplo , lo siguiente reproduce un ejemplo en la pregunta original, transpuesta :

> 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

Los tiempos son excelentes para valores pequeños a moderados de m, hasta aproximadamente 10,000, pero se degradan para problemas más grandes. Por ejemplo, se obtuvo una muestra de m = 10,000 permutaciones de n = 1000 elementos (una matriz de 10 millones de valores) en 10 segundos; una muestra de m = 20,000 permutaciones de n = 20 elementos requirió 11 segundos, a pesar de que la salida (una matriz de 400,000 entradas) fue mucho menor; y la muestra de cálculo de m = 100,000 permutaciones de n = 20 elementos fue abortada después de 260 segundos (no tuve la paciencia para esperar a que se completara). Este problema de escala parece estar relacionado con las ineficiencias de escala en el direccionamiento asociativo de R. Uno puede evitarlo generando muestras en grupos de, digamos, aproximadamente 1000, luego combinando esas muestras en una muestra grande y eliminando duplicados.

Editar

Podemos lograr un rendimiento asintótico casi lineal al dividir el caché en una jerarquía de dos cachés, de modo que R nunca tenga que buscar en una lista grande. Conceptualmente (aunque no según lo implementado), cree una matriz indexada por los primeros elementos de una permutación. Las entradas en esta matriz son listas de todas las permutaciones que comparten esos primeros elementos. Para verificar si se ha visto una permutación, use sus primeros elementos para encontrar su entrada en el caché y luego busque esa permutación dentro de esa entrada. Podemos elegir para equilibrar los tamaños esperados de todas las listas. La implementación real no usa unak k k kkkkkk-fold array, que sería difícil de programar con suficiente generalidad, pero en su lugar usa otra lista.

Estos son algunos tiempos transcurridos en segundos para un rango de tamaños de permutación y números de permutaciones distintas solicitadas:

 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

(La aceleración aparentemente anómala de tamaño = 10 a tamaño = 15 se debe a que el primer nivel de la memoria caché es más grande para tamaño = 15, lo que reduce el número promedio de entradas en las listas de segundo nivel, lo que acelera la búsqueda asociativa de R. En algunos costo en RAM, la ejecución podría hacerse más rápido aumentando el tamaño de caché de nivel superior. Simplemente aumentando k.headen 1 (que multiplica el tamaño de nivel superior por 10) aceleró rperm(100000, size=10)de 11.77 segundos a 8.72 segundos, por ejemplo. caché 10 veces más grande pero no logró una ganancia apreciable, registrando a 8.51 segundos).

A excepción del caso de 1,000,000 de permutaciones únicas de 10 elementos (una porción sustancial de los 10! = Alrededor de 3.63 millones de permutaciones), prácticamente no se detectaron colisiones. En este caso excepcional, hubo 169.301 colisiones, pero no hubo fallas completas (de hecho, se obtuvo un millón de permutaciones únicas).

Tenga en cuenta que con tamaños de permutación grandes (más de 20 o menos), la posibilidad de obtener dos permutaciones idénticas incluso en una muestra tan grande como 1,000,000,000 es muy pequeña. Por lo tanto, esta solución es aplicable principalmente en situaciones donde (a) un gran número de permutaciones únicas de (B) entre y o así elementos son a generarse pero aún así, (c) sustancialmente menos de todos losSe necesitan permutaciones.n = 15 n !n=5n=15n!

El código de trabajo sigue.

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.
whuber
fuente
Esto está cerca, pero noto que recibo algunos errores, como 1, 2 y 4, pero creo que veo lo que quieres decir y debería poder trabajar con eso. ¡Gracias! > 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

Usar uniquede la manera correcta debería hacer el truco:

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
fuente
Perdón por no explicar el código correctamente. Ahora tengo un poco de prisa, pero estoy feliz de responder cualquier pregunta que tenga más adelante. Además, no tengo idea de la velocidad del código anterior ...
MånsT
1
Funcioné lo que me diste de esta manera: `myperm <- function (limit) {myindex <- seq (0, limit) endDim <-factorial (limit) permutations <-sample (myindex) while (is.null (dim (unique (permutaciones))) || dim (unique (permutations)) [1]! = endDim) {permutations <- rbind (permutations, sample (myindex))} return (unique (permutations))} 'Funciona, pero mientras yo puede hacer limit = 6, limit = 7 hace que mi computadora se sobrecaliente. = PI creo que todavía debe haber una manera de submuestrear esto ...
Mittenchops
@ Mittenchops, ¿por qué dices que necesitamos usar únicos para volver a muestrear en R sin repetir permutaciones? Gracias.
Frank
2

I "m va a paso de lado a su primera pregunta un poco, y sugieren que si su están tratando con vectores relativamente cortos, simplemente podría generar todas las permutaciones usando permny les ordenará al azar los utilizando 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
joran
fuente
Me gusta MUCHO, y estoy seguro de que es el pensamiento correcto. Pero mi problema me obliga a usar una secuencia de hasta 10. Permn () fue significativamente más lento entre factorial (7) y factorial (8), por lo que creo que 9 y 10 serán prohibitivamente enormes.
Mittenchops
@ Mittenchops Es cierto, pero aún es posible que realmente solo necesites calcularlos una vez, ¿verdad? Guárdelos en un archivo y luego cárguelos cuando los necesite y "muestree" de la lista predefinida. Entonces podrías hacer el cálculo lento permn(10)o lo que sea solo una vez.
joran
Correcto, pero si estoy almacenando todas las permutaciones en algún lugar, incluso esto se descompone en factorial (15) --- simplemente demasiado espacio para almacenar. Es por eso que me pregunto si establecer la semilla me permitirá muestrear permutaciones colectivamente, y si no, si hay un algoritmo para hacerlo.
Mittenchops
@Mittenchops Establecer una semilla no influirá en el rendimiento, solo garantiza el mismo inicio cada vez que realiza una llamada a PRNG.
Roman Luštrik
1
@Mitten Consulte la ayuda para set.seed: describe cómo guardar el estado del RNG y restaurarlo más tarde.
whuber