The package RcppAlgos
(I am the author) has some functions specifically for this type of problem. Observe:
library(RcppAlgos)
permuteSample(v = 0:10, m = 5, seed = 123, n = 3)
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 10 2 5 6 3
#> [2,] 0 6 10 3 5
#> [3,] 5 10 3 2 9
## Or use global RNG to get the same results
set.seed(123)
permuteSample(v = 0:10, m = 5, n = 3)
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 10 2 5 6 3
#> [2,] 0 6 10 3 5
#> [3,] 5 10 3 2 9
They are guaranteed to sample without replacement:
permuteCount(3)
#> [1] 6
permuteSample(3, n = 7)
#> Error: n exceeds the maximum number of possible results
If we request the maximal number of samples, it will be equivalent to generating all results and shuffling:
ps <- permuteSample(3, n = 6, seed = 100)
## Shuffled
ps
#> [,1] [,2] [,3]
#> [1,] 1 3 2
#> [2,] 2 1 3
#> [3,] 1 2 3
#> [4,] 3 2 1
#> [5,] 3 1 2
#> [6,] 2 3 1
## Same as getting them all
identical(
permuteGeneral(3),
ps[do.call(order, as.data.frame(ps)), ]
)
#> [1] TRUE
Flexibility
These functions are quite general as well. For example, if we want to sample a vector where repetition within the vector is allowed set repetition = TRUE
. This is not to be confused with sampling with replacement (see the section: Sampling With Replacement below):
permuteSample(v = 0:10, m = 5, repetition = TRUE, seed = 42, n = 3)
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 4 2 1 5 10
#> [2,] 3 7 9 8 7
#> [3,] 6 8 8 8 5
## Go beyond the vector length
permuteSample(v = 3, m = 10, repetition = TRUE, seed = 42, n = 3)
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,] 3 3 1 3 2 3 3 3 1 2
#> [2,] 2 3 1 2 2 3 3 3 3 3
#> [3,] 1 2 2 1 1 1 3 3 2 3
What about specific multiplicity for each element… easy! Use the freqs
parameter:
## With v = 3 and freqs = 2:4, this means:
##
## 1 can occur a maximum of 2 times
## 2 can occur a maximum of 3 times
## 3 can occur a maximum of 4 times
##
permuteSample(v = 3, m = 6, freqs = 2:4, seed = 1234, n = 3)
#> [,1] [,2] [,3] [,4] [,5] [,6]
#> [1,] 3 1 3 3 2 3
#> [2,] 3 2 3 2 2 1
#> [3,] 3 3 3 2 3 2
It works on all atomic types as well:
## When m is NULL, the length defaults to the length of v
set.seed(28)
permuteSample(
v = rnorm(3) + rnorm(3) * 1i,
repetition = TRUE,
n = 5
)
#> [,1] [,2] [,3]
#> [1,] -1.331167+0.5313963i -1.33116707+0.5313963i -1.90215722-1.8199917i
#> [2,] -1.902157-1.8199917i -1.33116707+0.5313963i -0.06429479+0.1626697i
#> [3,] -1.331167+0.5313963i -0.06429479+0.1626697i -1.33116707+0.5313963i
#> [4,] -1.902157-1.8199917i -1.90215722-1.8199917i -1.33116707+0.5313963i
#> [5,] -1.902157-1.8199917i -1.90215722-1.8199917i -1.90215722-1.8199917i
Sampling With Replacement
The default behavior of the sampling functions in RcppAlgos
is to sample without replacement. If you need sampling with replacement, we can make use of the sampleVec
parameter. This parameter takes a vector and interprets them as indices representing the lexicographical permutations to return. For example:
permuteSample(3, sampleVec = c(1, 3, 6))
#> [,1] [,2] [,3]
#> [1,] 1 2 3
#> [2,] 2 1 3
#> [3,] 3 2 1
## Same as
permuteGeneral(3)[c(1, 3, 6), ]
#> [,1] [,2] [,3]
#> [1,] 1 2 3
#> [2,] 2 1 3
#> [3,] 3 2 1
This means we can generate our own random sample of indices and pass it to permuteSample
:
set.seed(123)
idx <- sample(permuteCount(3), 5, replace = TRUE)
idx
#> [1] 3 6 3 2 2
permuteSample(3, sampleVec = idx)
#> [,1] [,2] [,3]
#> [1,] 2 1 3
#> [2,] 3 2 1
#> [3,] 2 1 3
#> [4,] 1 3 2
#> [5,] 1 3 2
## See rownames corresponding to the lexicographical result
permuteSample(3, sampleVec = idx, namedSample = TRUE)
#> [,1] [,2] [,3]
#> 3 2 1 3
#> 6 3 2 1
#> 3 2 1 3
#> 2 1 3 2
#> 2 1 3 2
Efficiency
The functions are written in C++
with efficiency in mind. Take for example the largest case @whuber tests:
system.time(size_10 <- permuteSample(10, n = 1e6, seed = 42))
#> user system elapsed
#> 0.269 0.006 0.275
## Guaranteed no repeated permutations!
nrow(unique(size_10))
#> [1] 1000000
system.time(size_15 <- permuteSample(15, n = 1e6, seed = 42))
#> user system elapsed
#> 0.395 0.008 0.403
## Guaranteed no repeated permutations!
nrow(unique(size_15))
#> [1] 1000000
Why not try even larger cases?!?!?! Using nThreads
, we can achieve even greater efficiency:
## Single threaded
system.time(permuteSample(15, n = 1e7, seed = 321))
#> user system elapsed
#> 4.490 0.108 4.599
## Using 4 threads
system.time(permuteSample(15, n = 1e7, seed = 321, nThreads = 4))
#> user system elapsed
#> 4.794 0.083 2.106
Sampling Other Combinatorial Objects
There are also sampling functions for other combinatorial objects:
- permutations:
permuteSample
- combinations:
comboSample
- integer partitions:
partitionsSample
- integer compositions:
compositionsSample
- partitions of a vector into groups
comboGroupsSample
limit
exceeds 12, you will likely run out of RAM when R attempts to allocate space forseq(1,factorial(limit))
. (12! requires about 2 GB, so 13! will need about 25 GB, 14! about 350 GB, etc.) $\endgroup$