Как быстро сэмплировать из групп в R
У меня есть большой набор данных, x
, который содержит реплицированные значения, некоторые из которых дублируются по своим переменным:
set.seed(40)
x <- data.frame(matrix(round(runif(1000)), ncol = 10))
x_unique <- x[!duplicated(x),]
Мне нужно сэмплировать все экземпляры каждой уникальной строки в x a заданное количество раз, поэтому я создаю новую переменную, которая просто объединяет переменные для каждой строки:
# Another way of seeing x is as a single value - will be useful later
x_code <- do.call(paste0, x)
u_code <- x_code[!duplicated(x)]
Нам нужен повторный выборочный образец из x, повторяющий каждую уникальную строку s раз. Эта информация представлена в векторе s:
s <- rpois(n = nrow(x_unique), lambda = 0.9)
Вопрос в том, как выбрать людей из x, чтобы достичь квоты, установленной s, для каждой уникальной строки? Вот длинный и некрасивый путь, который дает правильный результат:
for(i in 1:length(s)){
xs <- which(x_code %in% u_code[i])
sel <- c(sel, xs[sample(length(xs), size = s[i], replace = T)])
}
x_sampled <- x[sel, ]
Это медленно работает и громоздко писать.
Есть ли способ получить тот же результат (x_sampled
в вышесказанном) быстрее и лаконичнее? Конечно, должен быть способ!
2 ответа
Ключ к эффективному выполнению этой задачи - выяснить, как работать с индексами и как максимально векторизовать. Для вашей проблемы все станет намного проще, если вы найдете индексы для каждой повторяющейся строки:
set.seed(40)
x <- data.frame(matrix(round(runif(1000)), ncol = 10))
index <- 1:nrow(x)
grouped_index <- split(index, x, drop = TRUE)
names(grouped_index) <- NULL
Тогда вы можете использовать Map()
объединить индексы для выборки и количество выборок для каждой группы. Я пишу оберткуsample()
защитить от раздражающего поведения, когда x
имеет длину 1.
sample2 <- function(x, n, ...) {
if (length(x) == 1) return(rep(x, n))
sample(x, n, ...)
}
samples <- rpois(n = length(grouped_index), lambda = 0.9)
sel <- unlist(Map(sample2, grouped_index, samples, replace = TRUE))
sel
#> [1] 66 99 99 2 6 31 90 25 42 57 14 14 8 8 12 77 60
#> [18] 17 17 92 76 76 76 70 95 36 36 36 100 91 41 41 28 69
#> [35] 69 54 54 54 54 81 64 96 35 39 29 11 74 93 82 82 24
#> [52] 46 48 48 48 51 51 73 20 37 71 71 58 16 68 94 94 94
#> [69] 80 80 80 13 13 87 87 67 67 86 49 49 88 88 52 75 47
#> [86] 89 7 79 63 78 72 72 19
Если вы хотите сохранить в первоначальном порядке, используйте sort()
:
sort(sel)
#> [1] 2 6 7 8 8 11 12 13 13 14 14 16 17 17 19 20 24
#> [18] 25 28 29 31 35 36 36 36 37 39 41 41 42 46 47 48 48
#> [35] 48 49 49 51 51 52 54 54 54 54 57 58 60 63 64 66 67
#> [52] 67 68 69 69 70 71 71 72 72 73 74 75 76 76 76 77 78
#> [69] 79 80 80 80 81 82 82 86 87 87 88 88 89 90 91 92 93
#> [86] 94 94 94 95 96 99 99 100
Я думаю узкое место в этом коде будет split()
: base R не имеет эффективного способа хеширования кадров данных, поэтому полагается на вставку столбцов вместе.
Ты можешь использовать rep()
создать индексный вектор с последующим поднабором ваших данных с использованием этого индексного вектора.
Попробуй это:
idx <- rep(1:length(s), times=s)
Первые несколько значений idx. Обратите внимание, как второй ряд повторяется дважды, в то время как строка 4 отсутствует:
idx
[1] 1 2 2 3 6 7 8 10 11 13 14 14 ......
Затем выполните подмножество. Обратите внимание, как новые дубликаты имеют имена строк, которые указывают на репликацию.
x_unique[idx, ]
X1 X2 X3 X4 X5 X6 X7 X8 X9 X10
1 1 1 0 0 0 1 0 0 1 0
2 1 0 1 0 0 1 0 0 0 0
2.1 1 0 1 0 0 1 0 0 0 0
3 1 1 0 0 1 0 0 0 1 0
6 0 0 0 0 1 1 0 0 0 0
7 0 1 1 0 1 1 0 1 1 1
8 1 1 0 1 0 0 1 1 0 0
10 0 0 1 0 1 1 1 1 0 0
....