Как повысить производительность при случайном выборе кластеров и добавлении наблюдений?

В кластеризованном наборе данных я хочу случайным образом выбрать несколько кластеров, а затем добавить несколько смоделированных наблюдений к выбранным кластерам. Затем я хочу создать набор данных, который объединяет смоделированные и исходные наблюдения из выбранных кластеров со всеми исходными наблюдениями из невыделенных кластеров. Я также хотел бы повторить этот процесс много раз и таким образом создать много (возможно, 1000) новых наборов данных. Мне удалось сделать это с помощью цикла for, но я хотел бы знать, есть ли более эффективный и краткий способ сделать это. Вот пример набора данных:

## simulate some data
y <- rnorm(20)
x <- rnorm(20)
z <- rep(1:5, 4)
w <- rep(1:4, each=5)
dd <- data.frame(id=z, cluster=w, x=x, y=y)
#    id cluster           x           y
# 1   1       1  0.30003855  0.65325768
# 2   2       1 -1.00563626 -0.12270866
# 3   3       1  0.01925927 -0.41367651
# 4   4       1 -1.07742065 -2.64314895
# 5   5       1  0.71270333 -0.09294102
# 6   1       2  1.08477509  0.43028470
# 7   2       2 -2.22498770  0.53539884
# 8   3       2  1.23569346 -0.55527835
# 9   4       2 -1.24104450  1.77950291
# 10  5       2  0.45476927  0.28642442
# 11  1       3  0.65990264  0.12631586
# 12  2       3 -0.19988983  1.27226678
# 13  3       3 -0.64511396 -0.71846622
# 14  4       3  0.16532102 -0.45033862
# 15  5       3  0.43881870  2.39745248
# 16  1       4  0.88330282  0.01112919
# 17  2       4 -2.05233698  1.63356842
# 18  3       4 -1.63637927 -1.43850664
# 19  4       4  1.43040234 -0.19051680
# 20  5       4  1.04662885  0.37842390

cl <- split(dd, dd$cluster)  ## split the data based on clusters 
k <- length(dd$id)
l <- length(cl)
`%notin%` <- Negate(`%in%`)  ## define "not in" to exclude unselected clusters so
                             ## as to retain their original observations

Затем создается clsamp функция в следующем коде, которая включает два цикла for. Первый цикл for предназначен для исключения невыделенных кластеров, а второй цикл for предназначен для моделирования новых наблюдений и добавления их к выбранным кластерам. Обратите внимание, что я произвольно выбираю 2 кластера (10% от общего количества наблюдений) без замены

clsamp <- function(cl, k) {
  a <- sample(cl, size=0.1*k, replace=FALSE)  
  jud <- (names(cl) %notin% names(a))  
  need <- names(cl)[jud] 
  T3 <- NULL
  for (k in need) {
    T3 <- rbind(T3, cl[[k]])  
  }
  subt <- NULL
  s <- a
  for (j in 1:2) {
    y <- rnorm(2)
    x <- rnorm(2)
    d <- cbind(id=nrow(a[[j]]) + c(1:length(x)), 
               cluster=unique(a[[j]]$cluster), x, y)
    s[[j]] <- rbind(a[[j]], d)
    subt <- rbind(subt, s[[j]])
  }
  T <- rbind(T3, subt)
  return(T)
}

Наконец, это создает список из 5 наборов данных, каждый из которых объединяет смоделированные и исходные наблюдения из выбранных кластеров со всеми исходными наблюдениями из невыделенных кластеров.

Q <- vector(mode="list", length=5)
for (i in 1:length(Q)) {
  Q[[i]] <- clsamp(cl, 20)
}

Кто-нибудь знает более короткий способ сделать это? Может воспользоваться функцией replicate? Спасибо.


person cliu    schedule 17.12.2020    source источник
comment
Не могли бы вы показать, как вы используете clsamp? Я просто получаю один набор данных 24x4.   -  person jay.sf    schedule 17.12.2020
comment
Последний фрагмент кода внизу моего сообщения - это повторение `clsmap` 5 раз.   -  person cliu    schedule 17.12.2020
comment
Да, я обнаружил это в цикле for, спасибо! Пожалуйста, посмотрите мое решение ниже и скажите, что вы думаете.   -  person jay.sf    schedule 17.12.2020


Ответы (1)


Это генерирует матрицу sizeX2 случайных значений и cbinds выборочных имен кластеров и последовательных идентификаторов к ним. Он напрямую начинается с dd и также работает, когда вы конвертируете dd в матрицу mm, что может быть немного быстрее. Однако вывод - это фрейм данных. Вместо вашего k я использую f для прямого вычисления количества строк, которые должны быть добавлены к двум выбранным кластерам. Если размер становится нулевым, возвращается исходный фрейм данных.

clsamp2 <- function(m, f=.1) {
  size <- round(nrow(m)*f)
  if (size == 0) as.data.frame(m)
  else {
    ids <- unique(m[,1])
    cls <- unique(m[,2])
    rd <- matrix(rnorm(size * 4), ncol=2, dimnames=list(NULL, c("x", "y")))
    out <- rbind.data.frame(m, cbind(id=rep(max(ids) + 1:size, each=2), 
                                     cluster=sample(cls, 2), rd))
    `rownames<-`(out[order(out$cluster, out$id), ], NULL)
  }
}

Результат

set.seed(42)  ## same seed also used for creating `dd`
clsamp2(dd, .1)

## or
mm <- as.matrix(dd)
clsamp2(mm, .1)

#    id cluster           x           y
# 1   1       1 -0.30663859  1.37095845
# 2   2       1 -1.78130843 -0.56469817
# 3   3       1 -0.17191736  0.36312841
# 4   4       1  1.21467470  0.63286260
# 5   5       1  1.89519346  0.40426832
# 6   1       2 -0.43046913 -0.10612452
# 7   2       2 -0.25726938  1.51152200
# 8   3       2 -1.76316309 -0.09465904
# 9   4       2  0.46009735  2.01842371
# 10  5       2 -0.63999488 -0.06271410
# 11  6       2  1.37095845  0.40426832
# 12  7       2  0.36312841  1.51152200
# 13  1       3  0.45545012  1.30486965
# 14  2       3  0.70483734  2.28664539
# 15  3       3  1.03510352 -1.38886070
# 16  4       3 -0.60892638 -0.27878877
# 17  5       3  0.50495512 -0.13332134
# 18  1       4 -1.71700868  0.63595040
# 19  2       4 -0.78445901 -0.28425292
# 20  3       4 -0.85090759 -2.65645542
# 21  4       4 -2.41420765 -2.44046693
# 22  5       4  0.03612261  1.32011335
# 23  6       4 -0.56469817 -0.10612452
# 24  7       4  0.63286260 -0.09465904

Чтобы создать список из пяти образцов, вы можете использовать replicate.

replicate(5, clsamp2(dd, .1), simplify=FALSE)

Время работы незначительное.

system.time(replicate(1000, clsamp2(dd, .1), simplify=FALSE))
# user  system elapsed 
# 0.44    0.03    0.44 
person jay.sf    schedule 17.12.2020
comment
Работает как шарм! Спасибо. Зачем нужно рассчитывать размер: size <- round(nrow(m)*f) и иметь условие: if (size == 0) as.data.frame(m)? - person cliu; 17.12.2020
comment
Добро пожаловать, @cliu! На самом деле размер рассчитывается аналогично исходному коду size=0.1*k - nrow фрейма данных совпадает с length столбца, верно? Я только что изменил аргументы, так что теперь вы можете определять процент прямо в функции. if (size == 0) checks if you don't want to sample any rows for some reason, and returns the input data (as.data.frame` только потому, что вы также можете ввести matrix), потому что остальная часть могла бы потерпеть неудачу, если size == 0. - person jay.sf; 17.12.2020
comment
Отлично! Это даже обеспечивает большую гибкость для указания вероятности выборки в самой функции, и поэтому она лучше, чем моя исходная функция. - person cliu; 17.12.2020
comment
@cliu Простите за это! ;) - person jay.sf; 17.12.2020