una studio simple muestreo muestras muestra libros generar factores estratificado con calculo aleatorio aleatorias r performance algorithm

simple - muestreo con r studio



Muestreo ponderado más rápido sin reemplazo (3)

Decidí profundizar en algunos de los comentarios y encontré que el artículo de Efraimidis y Spirakis era fascinante (gracias a @Hemmo por encontrar la referencia). La idea general en el documento es esta: cree una clave generando un número aleatorio uniforme y elevándolo a la potencia de uno sobre el peso de cada artículo. Luego, simplemente toma los valores clave más altos como su muestra. ¡Esto funciona brillantemente!

weighted_Random_Sample <- function( .data, .weights, .n ){ key <- runif(length(.data)) ^ (1 / .weights) return(.data[order(key, decreasing=TRUE)][1:.n]) }

Si configura ''.n'' para que sea la longitud de ''.data'' (que siempre debería ser la longitud de ''.weights''), esto es en realidad una permutación ponderada del reservorio, pero el método funciona bien tanto para el muestreo como para la permutación.

Actualización : probablemente debería mencionar que la función anterior espera que los pesos sean mayores que cero. De lo contrario, la key <- runif(length(.data)) ^ (1 / .weights) no se ordenará correctamente.

Sólo por diversión, también usé el escenario de prueba en el OP para comparar ambas funciones.

set.seed(1) times_WRS <- ldply( 1:7, function(i) { n <- 1024 * (2 ** i) p <- runif(2 * n) n_Set <- 1:(2 * n) data.frame( n=n, user=system.time(weighted_Random_Sample(n_Set, p, n), gcFirst=T)[''user.self'']) }, .progress=''text'' ) sample.int.test <- function(n, p) { sample.int(2 * n, n, replace=F, prob=p); NULL } times_sample.int <- ldply( 1:7, function(i) { n <- 1024 * (2 ** i) p <- runif(2 * n) data.frame( n=n, user=system.time(sample.int.test(n, p), gcFirst=T)[''user.self'']) }, .progress=''text'' ) times_WRS$group <- "WRS" times_sample.int$group <- "sample.int" library(ggplot2) ggplot(rbind(times_WRS, times_sample.int) , aes(x=n, y=user/n, col=group)) + geom_point() + scale_x_log10() + ylab(''Time per unit (s)'')

Y aquí están los tiempos:

times_WRS # n user # 1 2048 0.00 # 2 4096 0.01 # 3 8192 0.00 # 4 16384 0.01 # 5 32768 0.03 # 6 65536 0.06 # 7 131072 0.16 times_sample.int # n user # 1 2048 0.02 # 2 4096 0.05 # 3 8192 0.14 # 4 16384 0.58 # 5 32768 2.33 # 6 65536 9.23 # 7 131072 37.79

Esta pregunta llevó a un nuevo paquete R: wrswoR

El muestreo predeterminado de R sin reemplazo usando sample.int parece requerir un tiempo de ejecución cuadrático, por ejemplo, cuando se usan pesos extraídos de una distribución uniforme. Esto es lento para muestras de gran tamaño. ¿Alguien sabe una implementación más rápida que se pueda utilizar desde R ? Dos opciones son "muestreo de rechazo con reemplazo" (consulte esta pregunta en stats.sx) y el algoritmo de Wong y Easton (1980) (con una implementación de Python en una respuesta de StackOverflow ).

Gracias a Ben Bolker por sugerir la función C que se llama internamente cuando se llama a sample.int con replace=F y pesos no uniformes: ProbSampleNoReplace . De hecho, el código muestra dos anidados for bucles (línea 420 ff de random.c ).

Aquí está el código para analizar empíricamente el tiempo de ejecución:

library(plyr) sample.int.test <- function(n, p) { sample.int(2 * n, n, replace=F, prob=p); NULL } times <- ldply( 1:7, function(i) { n <- 1024 * (2 ** i) p <- runif(2 * n) data.frame( n=n, user=system.time(sample.int.test(n, p), gcFirst=T)[''user.self'']) }, .progress=''text'' ) times library(ggplot2) ggplot(times, aes(x=n, y=user/n)) + geom_point() + scale_x_log10() + ylab(''Time per unit (s)'') # Output: n user 1 2048 0.008 2 4096 0.028 3 8192 0.100 4 16384 0.408 5 32768 1.645 6 65536 6.604 7 131072 26.558

EDITAR : Gracias a Arun por señalar que el muestreo no ponderado no parece tener esta penalización de rendimiento.


Permítame incluir mi propia implementación de un enfoque más rápido basado en el muestreo de rechazo con reemplazo . La idea es esta:

  • Genere una muestra con reemplazo que sea "algo" más grande que el tamaño solicitado

  • Deseche los valores duplicados

  • Si no se han dibujado suficientes valores, llame al mismo procedimiento de forma recursiva con los parámetros n , size y prob ajustados

  • Reasignar los índices devueltos a los índices originales

¿Qué tamaño de muestra necesitamos dibujar? Suponiendo una distribución uniforme, el resultado es el número esperado de ensayos para ver x valores únicos de N valores totales . Es una diferencia de dos números armónicos (H_n y H_ {n - tamaño}). Los primeros números de armónicos se tabulan, de lo contrario se usa una aproximación utilizando el logaritmo natural. (Esto es solo una cifra aproximada, no es necesario que sea demasiado preciso aquí). Ahora, para una distribución no uniforme, el número esperado de elementos a dibujar solo puede ser mayor, por lo que no extraeremos demasiadas muestras. Además, el número de muestras extraídas está limitado por el doble del tamaño de la población. Supongo que es más rápido tener algunas llamadas recursivas que muestrear hasta O (n ln n) elementos.

El código está disponible en el paquete de R wrswoR en la rutina sample_int_rej.R en sample_int_rej.R . Instalar con:

library(devtools) install_github(''wrswoR'', ''muelleki'')

Parece funcionar "lo suficientemente rápido", sin embargo, aún no se han realizado pruebas formales de tiempo de ejecución. Además, el paquete se prueba solo en Ubuntu. Aprecio sus comentarios.


Actualizar:

Una implementación Rcpp del algoritmo Efraimidis y Spirakis (gracias a @Hemmo, @Dinrem, @krlmlr y @rtlgrmpf ):

library(inline) library(Rcpp) src <- '' int num = as<int>(size), x = as<int>(n); Rcpp::NumericVector vx = Rcpp::clone<Rcpp::NumericVector>(x); Rcpp::NumericVector pr = Rcpp::clone<Rcpp::NumericVector>(prob); Rcpp::NumericVector rnd = rexp(x) / pr; for(int i= 0; i<vx.size(); ++i) vx[i] = i; std::partial_sort(vx.begin(), vx.begin() + num, vx.end(), Comp(rnd)); vx = vx[seq(0, num - 1)] + 1; return vx; '' incl <- '' struct Comp{ Comp(const Rcpp::NumericVector& v ) : _v(v) {} bool operator ()(int a, int b) { return _v[a] < _v[b]; } const Rcpp::NumericVector& _v; }; '' funFast <- cxxfunction(signature(n = "Numeric", size = "integer", prob = "numeric"), src, plugin = "Rcpp", include = incl) # See the bottom of the answer for comparison p <- c(995/1000, rep(1/1000, 5)) n <- 100000 system.time(print(table(replicate(funFast(6, 3, p), n = n)) / n)) 1 2 3 4 5 6 1.00000 0.39996 0.39969 0.39973 0.40180 0.39882 user system elapsed 3.93 0.00 3.96 # In case of: # Rcpp::IntegerVector vx = Rcpp::clone<Rcpp::IntegerVector>(x); # i.e. instead of NumericVector 1 2 3 4 5 6 1.00000 0.40150 0.39888 0.39925 0.40057 0.39980 user system elapsed 1.93 0.00 2.03

Versión antigua:

Probemos algunos enfoques posibles:

Muestreo de rechazo simple con reemplazo. Esta es una función mucho más simple que sample.int.rej ofrece @krlmlr, es decir, el tamaño de la muestra siempre es igual a n . Como veremos, sigue siendo muy rápido, asumiendo una distribución uniforme para los pesos, pero extremadamente lento en otra situación.

fastSampleReject <- function(all, n, w){ out <- numeric(0) while(length(out) < n) out <- unique(c(out, sample(all, n, replace = TRUE, prob = w))) out[1:n] }

El algoritmo de Wong y Easton (1980) . Aquí hay una implementación de esta versión de Python. Es estable y puede que me falte algo, pero es mucho más lento en comparación con otras funciones.

fastSample1980 <- function(all, n, w){ tws <- w for(i in (length(tws) - 1):0) tws[1 + i] <- sum(tws[1 + i], tws[1 + 2 * i + 1], tws[1 + 2 * i + 2], na.rm = TRUE) out <- numeric(n) for(i in 1:n){ gas <- tws[1] * runif(1) k <- 0 while(gas > w[1 + k]){ gas <- gas - w[1 + k] k <- 2 * k + 1 if(gas > tws[1 + k]){ gas <- gas - tws[1 + k] k <- k + 1 } } wgh <- w[1 + k] out[i] <- all[1 + k] w[1 + k] <- 0 while(1 + k >= 1){ tws[1 + k] <- tws[1 + k] - wgh k <- floor((k - 1) / 2) } } out }

Implementación Rcpp del algoritmo por Wong y Easton. Posiblemente se puede optimizar aún más ya que esta es mi primera función Rcpp utilizable, pero de todos modos funciona bien.

library(inline) library(Rcpp) src <- '' Rcpp::NumericVector weights = Rcpp::clone<Rcpp::NumericVector>(w); Rcpp::NumericVector tws = Rcpp::clone<Rcpp::NumericVector>(w); Rcpp::NumericVector x = Rcpp::NumericVector(all); int k, num = as<int>(n); Rcpp::NumericVector out(num); double gas, wgh; if((weights.size() - 1) % 2 == 0){ tws[((weights.size()-1)/2)] += tws[weights.size()-1] + tws[weights.size()-2]; } else { tws[floor((weights.size() - 1)/2)] += tws[weights.size() - 1]; } for (int i = (floor((weights.size() - 1)/2) - 1); i >= 0; i--){ tws[i] += (tws[2 * i + 1]) + (tws[2 * i + 2]); } for(int i = 0; i < num; i++){ gas = as<double>(runif(1)) * tws[0]; k = 0; while(gas > weights[k]){ gas -= weights[k]; k = 2 * k + 1; if(gas > tws[k]){ gas -= tws[k]; k += 1; } } wgh = weights[k]; out[i] = x[k]; weights[k] = 0; while(k > 0){ tws[k] -= wgh; k = floor((k - 1) / 2); } tws[0] -= wgh; } return out; '' fun <- cxxfunction(signature(all = "numeric", n = "integer", w = "numeric"), src, plugin = "Rcpp")

Ahora algunos resultados:

times1 <- ldply( 1:6, function(i) { n <- 1024 * (2 ** i) p <- runif(2 * n) # Uniform distribution p <- p/sum(p) data.frame( n=n, user=c(system.time(sample.int.test(n, p), gcFirst=T)[''user.self''], system.time(weighted_Random_Sample(1:(2*n), p, n), gcFirst=T)[''user.self''], system.time(fun(1:(2*n), n, p), gcFirst=T)[''user.self''], system.time(sample.int.rej(2*n, n, p), gcFirst=T)[''user.self''], system.time(fastSampleReject(1:(2*n), n, p), gcFirst=T)[''user.self''], system.time(fastSample1980(1:(2*n), n, p), gcFirst=T)[''user.self'']), id=c("Base", "Reservoir", "Rcpp", "Rejection", "Rejection simple", "1980")) }, .progress=''text'' ) times2 <- ldply( 1:6, function(i) { n <- 1024 * (2 ** i) p <- runif(2 * n - 1) p <- p/sum(p) p <- c(0.999, 0.001 * p) # Special case data.frame( n=n, user=c(system.time(sample.int.test(n, p), gcFirst=T)[''user.self''], system.time(weighted_Random_Sample(1:(2*n), p, n), gcFirst=T)[''user.self''], system.time(fun(1:(2*n), n, p), gcFirst=T)[''user.self''], system.time(sample.int.rej(2*n, n, p), gcFirst=T)[''user.self''], system.time(fastSampleReject(1:(2*n), n, p), gcFirst=T)[''user.self''], system.time(fastSample1980(1:(2*n), n, p), gcFirst=T)[''user.self'']), id=c("Base", "Reservoir", "Rcpp", "Rejection", "Rejection simple", "1980")) }, .progress=''text'' )

arrange(times1, id) n user id 1 2048 0.53 1980 2 4096 0.94 1980 3 8192 2.00 1980 4 16384 4.32 1980 5 32768 9.10 1980 6 65536 21.32 1980 7 2048 0.02 Base 8 4096 0.05 Base 9 8192 0.18 Base 10 16384 0.75 Base 11 32768 2.99 Base 12 65536 12.23 Base 13 2048 0.00 Rcpp 14 4096 0.01 Rcpp 15 8192 0.03 Rcpp 16 16384 0.07 Rcpp 17 32768 0.14 Rcpp 18 65536 0.31 Rcpp 19 2048 0.00 Rejection 20 4096 0.00 Rejection 21 8192 0.00 Rejection 22 16384 0.02 Rejection 23 32768 0.02 Rejection 24 65536 0.03 Rejection 25 2048 0.00 Rejection simple 26 4096 0.01 Rejection simple 27 8192 0.00 Rejection simple 28 16384 0.01 Rejection simple 29 32768 0.00 Rejection simple 30 65536 0.05 Rejection simple 31 2048 0.00 Reservoir 32 4096 0.00 Reservoir 33 8192 0.00 Reservoir 34 16384 0.02 Reservoir 35 32768 0.03 Reservoir 36 65536 0.05 Reservoir arrange(times2, id) n user id 1 2048 0.43 1980 2 4096 0.93 1980 3 8192 2.00 1980 4 16384 4.36 1980 5 32768 9.08 1980 6 65536 19.34 1980 7 2048 0.01 Base 8 4096 0.04 Base 9 8192 0.18 Base 10 16384 0.75 Base 11 32768 3.11 Base 12 65536 12.04 Base 13 2048 0.01 Rcpp 14 4096 0.02 Rcpp 15 8192 0.03 Rcpp 16 16384 0.08 Rcpp 17 32768 0.15 Rcpp 18 65536 0.33 Rcpp 19 2048 0.00 Rejection 20 4096 0.00 Rejection 21 8192 0.02 Rejection 22 16384 0.02 Rejection 23 32768 0.05 Rejection 24 65536 0.08 Rejection 25 2048 1.43 Rejection simple 26 4096 2.87 Rejection simple 27 8192 6.17 Rejection simple 28 16384 13.68 Rejection simple 29 32768 29.74 Rejection simple 30 65536 73.32 Rejection simple 31 2048 0.00 Reservoir 32 4096 0.00 Reservoir 33 8192 0.02 Reservoir 34 16384 0.02 Reservoir 35 32768 0.02 Reservoir 36 65536 0.04 Reservoir

Obviamente, podemos rechazar la función 1980 porque es más lenta que la Base en ambos casos. Rejection simple mete en problemas también cuando hay una probabilidad única de 0.999 en el segundo caso.

Así que queda el Rejection , Rcpp , Reservoir . El último paso es verificar si los valores en sí son correctos. Para estar seguros acerca de ellos, utilizaremos la sample como punto de referencia (también para eliminar la confusión sobre las probabilidades que no tienen que coincidir con p debido al muestreo sin reemplazo).

p <- c(995/1000, rep(1/1000, 5)) n <- 100000 system.time(print(table(replicate(sample(1:6, 3, repl = FALSE, prob = p), n = n))/n)) 1 2 3 4 5 6 1.00000 0.39992 0.39886 0.40088 0.39711 0.40323 # Benchmark user system elapsed 1.90 0.00 2.03 system.time(print(table(replicate(sample.int.rej(2*3, 3, p), n = n))/n)) 1 2 3 4 5 6 1.00000 0.40007 0.40099 0.39962 0.40153 0.39779 user system elapsed 76.02 0.03 77.49 # Slow system.time(print(table(replicate(weighted_Random_Sample(1:6, p, 3), n = n))/n)) 1 2 3 4 5 6 1.00000 0.49535 0.41484 0.36432 0.36338 0.36211 # Incorrect user system elapsed 3.64 0.01 3.67 system.time(print(table(replicate(fun(1:6, 3, p), n = n))/n)) 1 2 3 4 5 6 1.00000 0.39876 0.40031 0.40219 0.40039 0.39835 user system elapsed 4.41 0.02 4.47

Note algunas cosas aquí. Por alguna razón, weighted_Random_Sample devuelve valores incorrectos (no lo he buscado en absoluto, pero funciona correctamente suponiendo una distribución uniforme). sample.int.rej es muy lento en el muestreo repetido.

En conclusión, parece que Rcpp es la opción óptima en caso de un muestreo repetido, mientras que sample.int.rej es un poco más rápido y también más fácil de usar.