Ответ 1
См. мой ответ на этот вопрос Уникальные (не повторяющиеся) случайные числа в O (1)?. Та же логика должна выполнить то, что вы хотите сделать.
Я пытаюсь проверить вероятность случайной случайной кластеризации данных. Мощным способом это является симуляция Монте-Карло, в которой ассоциации между данными и группами случайным образом переназначаются много раз (например, 10 000), а метрика кластеризации используется для сравнения фактических данных с имитациями для определения ap значение.
У меня есть большая часть этой работы, с указателями, сопоставляющими группировку с элементами данных, поэтому я планирую случайное переназначение указателей на данные. ВОПРОС: что такое быстрый способ выборки без замены, чтобы каждый указатель был случайным образом переназначен в реплицированных наборах данных?
Например (эти данные являются просто упрощенным примером):
Данные (n = 12 значений) - Группа A: 0,1, 0,2, 0,4/Группа B: 0,5, 0,6, 0,8/Группа C: 0,4, 0,5/Группа D: 0,2, 0,2, 0,3, 0,5
Для каждого реплицированного набора данных я бы имел одинаковые размеры кластеров (A = 3, B = 3, C = 2, D = 4) и значения данных, но переназначил значения для кластеров.
Чтобы сделать это, я мог генерировать случайные числа в диапазоне 1-12, назначить первый элемент группы A, затем генерировать случайные числа в диапазоне 1-11 и назначать второй элемент в группе A и т.д. Переустановка указателя выполняется быстро, и я буду предварительно распределять все структуры данных, но выборка без замены кажется проблемой, которая могла быть решена много раз раньше.
Предпочитаемая логика или псевдокод.
См. мой ответ на этот вопрос Уникальные (не повторяющиеся) случайные числа в O (1)?. Та же логика должна выполнить то, что вы хотите сделать.
Вот некоторый код для выборки без замены, основанный на алгоритме 3.4.2S книги Кнута Семинумерные алгоритмы.
void SampleWithoutReplacement
(
int populationSize, // size of set sampling from
int sampleSize, // size of each sample
vector<int> & samples // output, zero-offset indicies to selected items
)
{
// Use Knuth variable names
int& n = sampleSize;
int& N = populationSize;
int t = 0; // total input records dealt with
int m = 0; // number of items selected so far
double u;
while (m < n)
{
u = GetUniform(); // call a uniform(0,1) random number generator
if ( (N - t)*u >= n - m )
{
t++;
}
else
{
samples[m] = t;
t++; m++;
}
}
}
Существует более эффективный, но более сложный метод Джеффри Скотта Виттера в "Эффективный алгоритм последовательной случайной выборки", ACM Transactions on Mathematical Software, 13 (1), March 1987, 58-67.
Рабочий код С++, основанный на ответе Джона Д. Кука.
#include <random>
#include <vector>
double GetUniform()
{
static std::default_random_engine re;
static std::uniform_real_distribution<double> Dist(0,1);
return Dist(re);
}
// John D. Cook, /questions/23208/algorithm-for-sampling-without-replacement/172490#172490
void SampleWithoutReplacement
(
int populationSize, // size of set sampling from
int sampleSize, // size of each sample
std::vector<int> & samples // output, zero-offset indicies to selected items
)
{
// Use Knuth variable names
int& n = sampleSize;
int& N = populationSize;
int t = 0; // total input records dealt with
int m = 0; // number of items selected so far
double u;
while (m < n)
{
u = GetUniform(); // call a uniform(0,1) random number generator
if ( (N - t)*u >= n - m )
{
t++;
}
else
{
samples[m] = t;
t++; m++;
}
}
}
#include <iostream>
int main(int,char**)
{
const size_t sz = 10;
std::vector< int > samples(sz);
SampleWithoutReplacement(10*sz,sz,samples);
for (size_t i = 0; i < sz; i++ ) {
std::cout << samples[i] << "\t";
}
return 0;
}
Вдохновленный @John D. Cook answer, я написал реализацию в Nim. Сначала у меня возникли трудности с пониманием того, как это работает, поэтому я также прокомментировал также пример. Может быть, это помогает понять эту идею. Кроме того, я немного изменил имена переменных.
iterator uniqueRandomValuesBelow*(N, M: int) =
## Returns a total of M unique random values i with 0 <= i < N
## These indices can be used to construct e.g. a random sample without replacement
assert(M <= N)
var t = 0 # total input records dealt with
var m = 0 # number of items selected so far
while (m < M):
let u = random(1.0) # call a uniform(0,1) random number generator
# meaning of the following terms:
# (N - t) is the total number of remaining draws left (initially just N)
# (M - m) is the number how many of these remaining draw must be positive (initially just M)
# => Probability for next draw = (M-m) / (N-t)
# i.e.: (required positive draws left) / (total draw left)
#
# This is implemented by the inequality expression below:
# - the larger (M-m), the larger the probability of a positive draw
# - for (N-t) == (M-m), the term on the left is always smaller => we will draw 100%
# - for (N-t) >> (M-m), we must get a very small u
#
# example: (N-t) = 7, (M-m) = 5
# => we draw the next with prob 5/7
# lets assume the draw fails
# => t += 1 => (N-t) = 6
# => we draw the next with prob 5/6
# lets assume the draw succeeds
# => t += 1, m += 1 => (N-t) = 5, (M-m) = 4
# => we draw the next with prob 4/5
# lets assume the draw fails
# => t += 1 => (N-t) = 4
# => we draw the next with prob 4/4, i.e.,
# we will draw with certainty from now on
# (in the next steps we get prob 3/3, 2/2, ...)
if (N - t)*u >= (M - m).toFloat: # this is essentially a draw with P = (M-m) / (N-t)
# no draw -- happens mainly for (N-t) >> (M-m) and/or high u
t += 1
else:
# draw t -- happens when (M-m) gets large and/or low u
yield t # this is where we output an index, can be used to sample
t += 1
m += 1
# example use
for i in uniqueRandomValuesBelow(100, 5):
echo i
Когда размер популяции намного больше размера выборки, указанные алгоритмы становятся неэффективными, поскольку они имеют сложность O (n), n - размер популяции.
Когда я был студентом, я написал несколько алгоритмов для равномерной выборки без замены, которые имеют среднюю сложность O (s log s), где s - размер выборки. Вот код для алгоритма двоичного дерева, со средней сложностью O (s log s), в R:
# The Tree growing algorithm for uniform sampling without replacement
# by Pavel Ruzankin
quicksample = function (n,size)
# n - the number of items to choose from
# size - the sample size
{
s=as.integer(size)
if (s>n) {
stop("Sample size is greater than the number of items to choose from")
}
# upv=integer(s) #level up edge is pointing to
leftv=integer(s) #left edge is poiting to; must be filled with zeros
rightv=integer(s) #right edge is pointig to; must be filled with zeros
samp=integer(s) #the sample
ordn=integer(s) #relative ordinal number
ordn[1L]=1L #initial value for the root vertex
samp[1L]=sample(n,1L)
if (s > 1L) for (j in 2L:s) {
curn=sample(n-j+1L,1L) #current number sampled
curordn=0L #currend ordinal number
v=1L #current vertice
from=1L #how have come here: 0 - by left edge, 1 - by right edge
repeat {
curordn=curordn+ordn[v]
if (curn+curordn>samp[v]) { #going down by the right edge
if (from == 0L) {
ordn[v]=ordn[v]-1L
}
if (rightv[v]!=0L) {
v=rightv[v]
from=1L
} else { #creating a new vertex
samp[j]=curn+curordn
ordn[j]=1L
# upv[j]=v
rightv[v]=j
break
}
} else { #going down by the left edge
if (from==1L) {
ordn[v]=ordn[v]+1L
}
if (leftv[v]!=0L) {
v=leftv[v]
from=0L
} else { #creating a new vertex
samp[j]=curn+curordn-1L
ordn[j]=-1L
# upv[j]=v
leftv[v]=j
break
}
}
}
}
return(samp)
}
Сложность этого алгоритма обсуждается в: Rouzankin, P.S.; Войтишек А. В. О стоимости алгоритмов случайного отбора. Методы Монте-Карло. 5 (1999), no. 1, 39-54. http://dx.doi.org/10.1515/mcma.1999.5.1.39
Если вы найдете алгоритм полезным, сделайте ссылку.
См. также: P. Gupta, G. P. Bhattacharjee. (1984) Эффективный алгоритм случайной выборки без замены. Международный журнал компьютерной математики 16: 4, с. 201-209. DOI: 10.1080/00207168408803438
Teuhola, J. and Nevalainen, O. 1982. Два эффективных алгоритма для случайной выборки без замены. /IJCM/, 11 (2): 127-140. DOI: 10.1080/00207168208803304
В последней статье авторы используют хэш-таблицы и утверждают, что их алгоритмы имеют сложность O (s). Существует еще один быстрый алгоритм хеш-таблицы, который скоро будет реализован в pqR (довольно быстро R): https://stat.ethz.ch/pipermail/r-devel/2017-October/075012.html
Далее описан алгоритм выборки без замены здесь.
Он похож на тот, который описывается Джоном Д. Куком в его ответе, а также от Кнута, но имеет другую гипотезу: размер популяции неизвестен, но образец может поместиться в памяти. Это называется "алгоритмом Кнута S".
Указание статьи в виде розеток:
- Выберите первые n элементов в качестве образца по мере их появления;
- Для i-го элемента, где i > n, имеют случайную вероятность сохранения n/i. Если это не удается, образец остается неизменным. Если нет, произвольно (1/n) заменить один из ранее выбранных n элементы образца.
- Повторите # 2 для любых последующих элементов.