Случайная выборка из упорядоченных данных

В моделировании нам нужны упорядоченные данные, которые представляют собой случайную выборку (с заменой или без нее) размера m из полного набора данных размера n. К сожалению, упорядочение выборочных данных оказывается узким местом в наших симуляциях. Проблема в том, что выборка повторяется R раз, что приводит к сложности выполнения O(R m log(m)). Мы стремимся снизить сложность выполнения, вызывая sort() только один раз, перед всей выборкой:

n <- 500; m <- 100; R <- 1000
all.data <- runif (n)
all.data <- sort(all.data)  # the full data set is already sorted
for (r in 1:R) {
  indices <- sample(1:n, m)
  sample.data <- sort(all.data[indices])  # this call to sort should be avoided
}

Поэтому мы задаемся вопросом, можно ли отсортировать полный набор данных только один раз, а затем напрямую получить упорядоченные выборки путем выборки из упорядоченных данных (без упорядочивания индексов, возвращаемых sample()).

Есть ли у кого-нибудь предложения, как это сделать в R (нам не обязательно нужен код R, но общего подхода тоже будет достаточно)?

Если бы вы могли предложить воспроизводимый пример, это очень помогло бы в выявлении и устранении узкого места.

PGSA 16.04.2024 10:55

Если полный набор данных упорядочен и вы выполняете выборку с использованием неупорядоченных индексов, не делает ли это выборку неупорядоченной? Как пишет @PGSA, прояснить ситуацию помог бы минимальный воспроизводимый пример.

Limey 16.04.2024 10:59

@PGSA Я добавил пример кода, описывающий наш текущий подход и узкие места.

cdalitz 16.04.2024 11:12

Если вы действительно не можете позволить себе сортировать индексы, вы могли бы, я думаю, предварительно вычислить все возможные неупорядоченные выборки (т.е. n-choose-k), что было бы очень трудоемко для большого набора, а затем случайным образом выбирать один набор на каждой итерации...

PGSA 16.04.2024 11:22

Не могли бы вы предоставить хотя бы дополнительную информацию — о каких размерах m, n и R мы здесь говорим?

PGSA 16.04.2024 11:27

n варьируется от 30 до 1000, для каждого n пробуются разные значения m, а R равно 1000. Каждая комбинация повторяется N = 10^6 раз, поскольку мы оцениваем вероятность покрытия.

cdalitz 16.04.2024 11:31

Тогда предварительный расчет возможных выборок не является хорошим планом - 1000 выберите 10 (например) предлагает 2,63E + 23 возможных набора выборок.

PGSA 16.04.2024 11:34

@SamR Я отредактировал код, чтобы его можно было выполнять, а также использовал реалистичные значения для n, m и R.

cdalitz 16.04.2024 11:35

Давайте продолжим обсуждение в чате.

PGSA 16.04.2024 11:52

Для выборки без замены может быть достаточно all.data[sample(rep(c(TRUE, FALSE), c(m, n-m)))].

lotus 16.04.2024 12:04
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
10
239
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Один из возможных методов — систематическая выборка отсортированных последовательностей. Например, если из 500 требуется 100 отсортированных индексов, разделите всю выборку на 100 слоев, содержащих пять индексов, и случайным образом выберите 1 из этих пяти. Полученная выборка автоматически сортируется.

pseudo.sort <- function() {
  size <- n/m
  indices <- sapply(1:m, function(i) sample(1:size + (size * (i-1)), 1))
  replicate(R, \() all.data[indices])
}

R <- 10000

pseudo.sort()
  [1]   4   8  13  17  21  29  35  38  44  46  55  56  61  69  72  77  82  89  93  98
 [21] 102 109 115 120 123 127 132 136 143 149 154 156 162 167 171 180 185 186 194 198
 [41] 202 208 214 216 221 226 233 240 243 246 255 257 264 266 271 276 284 290 294 300
 [61] 301 307 314 316 325 326 333 340 341 347 353 357 365 367 374 376 384 388 395 397
 [81] 404 406 414 418 422 428 432 440 445 447 454 456 464 468 471 477 485 489 492 496

Сравните с алгоритмом, который сортирует индексы выборки:

sorted.sample <- \() {
  for (r in 1:R) {
    indices <- sort(sample(1:n, m)) # the sort has been shifted
    sample.data <- all.data[indices]  
  }
}

lotus.sort <- \() replicate(R, \() all.data[sample(rep(c(TRUE, FALSE), c(m, n-m)))])

> microbenchmark::microbenchmark(sorted.sample(), pseudo.sort(), lotus.sort())
Unit: microseconds
            expr     min      lq      mean   median       uq      max neval
 sorted.sample() 44166.7 45439.5 50490.146 45936.05 47094.55 363412.8   100
   pseudo.sort()  1166.3  1201.5  1232.323  1225.90  1258.80   1400.6   100
    lotus.sort()   535.3   555.00   612.407   569.55   586.65  4126.2   100

Это происходит очень быстро (и предварительно отсортировано), но теряется элемент «настоящей» случайной выборки. Это интересный подход. Надеюсь, это обеспечит баланс, который нужен ОП.

PGSA 16.04.2024 12:07

Случайная выборка может быть простой или систематической. Оба метода дают «настоящую» случайную выборку.

Edward 16.04.2024 12:09

Да, каждый член популяции имеет равные шансы быть выбранным в выборке, но не означает ли этот подход, что некоторые комбинации никогда не могут быть выбраны? например, ни в одной выборке не будет одновременно 1 и 2, поскольку они оба находятся в страте [1:5]? Я неправильно понял? Для меня это новая территория, поэтому я очень хочу учиться.

PGSA 16.04.2024 12:14

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

Edward 16.04.2024 12:16

Метод @lotus — это именно то, что я искал, и он решает проблему выборки без замены. Реализация собственной процедуры выборки, как вы это сделали, несколько рискованна из-за множества ошибок при генерации случайных чисел с помощью арифметики по модулю. Таким образом, я бы не стал доверять результирующим вероятностям охвата или доверительным интервалам, полученным в результате «систематической» выборки: известно, например, что выборка складным ножом, являющаяся очень систематическим подходом, не дает репрезентативного распределения выборки.

cdalitz 16.04.2024 14:54

Спасибо за ответы. Поскольку самый существенный ответ скрыт в комментарии @lotus, позвольте мне резюмировать его как ответ.

Для выборки без замены индексы могут выбираться как последовательность из n логических значений (ИСТИНА, если элемент является частью выборки) вместо последовательности из m числовых индексов. Этого можно достичь в R с помощью

selected <- sample(rep(c(TRUE, FALSE), c(m, n-m)))
sample.data <- all.data[selected]

Для выборки с заменой простой алгоритм будет следующим:

  1. Инициализируйте массив счетчиков c размера n нулями.
  2. Выберите m раз число i из {1,2,...,n} и увеличьте c[i] для каждого числа.
  3. В конце концов выберите каждый элемент так часто, как он учитывается в c.

Простая, хотя и довольно медленная из-за цикла for, реализация в R будет следующей:

c <- numeric(n)
is <- sample(1:n, m, replace=TRUE)
for (i in is) c[i] <- c[i] + 1
sample.data <- rep(all.data, c)

Как было предложено в комментарии @jblood94, цикл for можно обойти с помощью tabulate():

c <- tabulate(sample(n, m, replace=TRUE), n)
sample.data <- rep(all.data, c)

То же самое работает и с replace=FALSE.

Используйте tabulate, чтобы избежать цикла for: rep(x, tabulate(sample(n, m, 1), n)).

jblood94 18.04.2024 13:49

@ jblood94 Да, это действительно приводит к некоторому ускорению. Интересно, что это быстрее, чем выборка без замены из логического вектора.

cdalitz 18.04.2024 18:10
Ответ принят как подходящий

Для выборки с заменой (ссылки и проверку моделирования см. ниже):

frexp <- function(m, R, x) {
  y <- rexp(R)
  for (r in 1:R) {
    cs <- cumsum(rexp(m))
    sample.data <- x[ceiling(cs*length(x)/(cs[m] + y[r]))]
  }
}

Для отбора проб без замены:

fbool2 <- function(m, R, x) {
  n <- length(x)
  b <- logical(n)
  for (r in 1:R) {
    b[i <- sample(n, m)] <- TRUE
    sample.data <- x[b]
    b[i] <- FALSE
  }
}

Другие функции для бенчмаркинга, в том числе тот, который не требует сортировки:

fNoSort <- function(m, R, x) for (r in 1:R) sample.data <- sample(x, m)
fsort <- function(m, R, x) for (r in 1:R) sample.data <- sort(sample(x, m))
fbool1 <- function(m, R, x) { # from @lotus
  for (r in 1:R) sample.data <- x[sample(rep.int(!0:1, c(m, length(x) - m)))]
}

Бенчмаркинг:

microbenchmark::microbenchmark(
  fNoSort = fNoSort(m, R, all.data),
  fsort = fsort(m, R, all.data),
  fbool1 = fbool1(m, R, all.data),
  fbool2 = fbool2(m, R, all.data),
  frexp = frexp(m, R, all.data)
)
#> Unit: milliseconds
#>     expr     min       lq      mean   median       uq     max neval
#>  fNoSort  8.3386  8.52895  9.746159  8.80505  9.37925 19.4995   100
#>    fsort 35.6090 38.05500 40.689749 39.67485 41.73925 79.2346   100
#>   fbool1 29.2213 29.64240 31.838542 30.65475 32.70145 40.3151   100
#>   fbool2 10.2221 10.43100 11.616368 10.79390 12.22930 19.7826   100
#>    frexp  5.0672  5.16110  6.028450  5.30465  5.81935 16.2895   100

frexp быстрее, чем sample без сортировки.

Быстрое моделирование для проверки того, что индексы, используемые в frexp, представляют собой (отсортированную) однородную случайную выборку с заменой:

(seed <- sample(.Machine$integer.max, 1))
#> [1] 904968452
set.seed(seed)
n <- 20 # sample the numbers 1 through 20
N <- 1e6 # number of samples
tabulate(sample(n, N, 1), n)
#>  [1] 50176 50058 50270 50279 50208 49722 50005 49668 49856 50196 49890 49798
#> [13] 50163 49489 49918 49698 50052 50240 50333 49981
tabulate(ceiling((cs <- cumsum(rexp(N)))*n/(cs[N] + rexp(1))), n)
#>  [1] 49918 49883 49957 50063 49815 50226 49792 49970 49879 49788 50119 49965
#> [13] 50417 49832 50059 50281 49930 49904 50416 49786

Использованная литература:

https://math.stackexchange.com/questions/74218/relations-between-order-statistics-of-uniform-rvs-and-exponential-rvs

https://stackoverflow.com/a/63430876/9463489

https://djalil.chafai.net/blog/2014/06/03/back-to-basics-order-statistics-of-exponential-distribution/

Результаты производительности впечатляют. Не могли бы вы объяснить, как работает frexp() и почему генерируются случайные числа экспоненциального распределения?

cdalitz 16.04.2024 17:27

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

jblood94 16.04.2024 21:58

Другие вопросы по теме