Генерация случайных пар целых чисел без замены в R

Я хочу нарисовать случайные целые пары без замены (иначе я не хочу дублировать пары). Эта концепция звучит просто, но я не могу придумать быстрое и простое решение.

Представьте, например, что я хочу генерировать случайные пары целых чисел, используя последовательность integer 1:4, чтобы заполнить элементы пары. Также предположим, что я хочу сгенерировать 5 случайных пар без замены. Затем я хочу иметь возможность генерировать что-то вроде этого...

     [,1] [,2]
[1,]    1    2
[2,]    2    1
[3,]    3    3
[4,]    1    4
[5,]    4    3

В приведенном выше примере нет повторяющихся пар (например, строк). Однако в каждом столбце вышеуказанной матрицы есть повторяющиеся целые числа. Следовательно, использование sample() для генерации случайного числа для каждого столбца отдельно не будет работать.

Другое потенциально потенциальное решение, которое не будет работать для моего контекста, состоит в создании множества пар, которые включают дубликаты, а затем удаляют эти дубликаты ретроактивно. Я не могу этого сделать, потому что мне нужно будет генерировать определенное количество пар.

Я ищу эффективное решение этой проблемы. Это похоже на такой простой вопрос, оно должно иметь простое решение (т.е. Не нужно встраивать петли)

Вот мой уродливый подход:

#This matrix maps a unique id i.e. (1:16) to a pair (i.e. the row & col of the matrix)
r.mat<-matrix(1:(4*4),4,4) 
#Drawing a random id
r.id<-sample(r.mat,5,replace=FALSE)
#Mapping the random id to a random pair
r.pair<-t(sapply(r.id, function (x) which(r.mat==x,arr.ind=TRUE)))

Это будет отлично работать для моего примера игрушек, но когда я хочу нарисовать большое количество пар из последовательности 1:10000000, это не так уж хорошо.

Ответ 1

Ключ здесь не в том, чтобы генерировать все перестановки, поскольку это очень дорогая память и время. Поскольку вас беспокоит только два числа, мы можем сделать это очень легко, пока (number_of_possible_values) ^ 2 меньше, чем наибольшее представляемое целое число с плавающей запятой двойной точности:

size <- 1e5
samples <- 100
vals <- sample.int(size ^ 2, samples)
cbind(vals %/% size + 1, vals %% size)

В принципе, мы используем целые числа для представления каждой возможной комбинации значений. В нашем примере мы отбираем все числа до 1e5 ^ 2, так как мы имеем 1e5 ^ 2 возможные комбинации чисел 1e5. Каждый из этих 1e10 целых чисел представляет собой одну из комбинаций. Затем мы разложим это целое число на два значения компонента, взяв по модулю, как первое число, и целочисленное деление как второе.

Ориентиры:

Unit: microseconds
                   expr        min         lq       mean
  funBrodie(10000, 100)     16.457     17.188     22.052
 funRichard(10000, 100) 542513.717 640647.919 638045.215

Кроме того, ограничение должно быть ~ 3x1e7 и остается относительно быстрым:

Unit: microseconds
                  expr    min      lq     mean median      uq    max neval
 funBrodie(1e+07, 100) 18.285 20.6625 22.88209 21.211 22.4905 77.893   100

Функции для бенчмаркинга:

funRichard <- function(size, samples) {
  nums <- 1:size
  dt = CJ(nums, nums)
  dt[sample(1:dim(dt)[1], size = samples), ]
}
funBrodie <- function(size, samples) {
  vals <- sample.int(size ^ 2, samples)
  cbind(vals %/% size + 1, vals %% size)
}

И подтвердите, что мы делаем похожие вещи (обратите внимание, что это не то, что они должны быть точно такими же, но, оказывается, они есть):

set.seed(1)
resB <- funBrodie(1e4, 100)
set.seed(1)
resR <- unname(as.matrix(funRichard(1e4, 100)))
all.equal(resB, resR)
# TRUE

Ответ 2

Сначала я нашел, как сгенерировать пары на fooobar.com/questions/510215/.... Однако это не масштабировалось, поэтому я просмотрел ?combn и нашел функцию expand.grid.

Далее я использую пакет data.table, потому что он хорошо справляется с большими данными (см. документацию по причине).

## the data.table library does well with large data sets
library(data.table)

## Small dummy dataset
pairOne = 1:10
pairTwo = 1:2
nSamples = 3

system.time({
dt = data.table(expand.grid(pairOne, pairTwo))
dt2 = dt[sample(1:dim(dt)[1], size = nSamples), ]
})
#   user  system elapsed 
#  0.002   0.001   0.001 

## Large dummy dataset
pairOne = 1:10000
pairTwo = 1:10000
length(pairOne) * length(pairTwo)
nSamples = 1e5
system.time({
dt = data.table(expand.grid(pairOne, pairTwo))
dt2 = dt[sample(1:dim(dt)[1], size = nSamples), ]
})
#   user  system elapsed 
#  2.576   1.276   3.862 

Ответ 3

Вдохновленный Дэвидом Робинсоном первоначальный удар:

set.seed(1)
np <- 1000 # number of elements desired
M1 <- t(combn(1:np, 2))
sam <- sample(1:nrow(M1), np, replace = FALSE)
M2 <- M1[sam,]
anyDuplicated(M2) # returns FALSE

Это будет использовать все возможные записи M1, но в случайном порядке. Это то, что вы хотели?

Ответ 4

Здесь моя попытка. Он выглядит не очень элегантно, но он все же немного быстрее, чем @Richard Erickson (2.0s против 2.6s, для тех же размеров). Идея заключается в том, чтобы избежать создания перестановок, потому что это может занять много времени и использовать много памяти. Вместо этого я создаю две случайные выборки идентификаторов в заданном диапазоне и проверяю, дублируется ли какая-либо строка (что очень маловероятно для большого диапазона и средних выборок). Если они дублируются, то создается новый образец для столбца 2, и все повторяется.

range <- 1e8
n <- 1e5
ids1 <- sample(range, n)
ids2 <- sample(range, n)
mat1 <- cbind(ids1, ids2)
found = FALSE
while(!found) {
  if (any(duplicated(rbind(mat1, mat1[,2:1])))) {
    ids2 <- sample(range, n)
    mat1 <- cbind(ids1, ids2)
  } else {
    found=TRUE
  }
}

Ответ 5

Как насчет:

no.pairs.needed <- 4 # or however many you want
npairs<-0
pairs <- NULL
top.sample.range <- 10000  # or whatever

while (npairs < no.pairs.needed){
  newpair <- matrix(data=sample(1:top.sample.range,2), nrow=1, ncol=2)
 if(!anyDuplicated(rbind(pairs, newpair))){
    pairs <- rbind(pairs, newpair)
    npairs <- npairs+1
  }
}

Затем объект pairs вернет нужную вам матрицу. Кажется, масштабируется нормально.