在R中,如何将一个向量随机分成k个块?

3
我在这里看到了许多“将向量X分成Y个块”的变化形式的问题。例如:这里这里,都是其中两个例子。所以,当我意识到我需要将一个向量分成Y个大小随机的块时,我很惊讶地发现随机性要求可能是“新的”——我找不到在这方面的方法。
因此,这里是我的想法:
k.chunks = function(seq.size, n.chunks) {
  break.pts = sample(1:seq.size, n.chunks, replace=F) %>% sort() #Get a set of break points chosen from along the length of the vector without replacement so no duplicate selections.
  groups = rep(NA, seq.size) #Set up the empty output vector.
  groups[1:break.pts[1]] = 1 #Set the first set of group affiliations because it has a unique start point of 1.

for (i in 2:(n.chunks)) { #For all other chunks...
    groups[break.pts[i-1]:break.pts[i]] = i #Set the respective group affiliations
    }
    groups[break.pts[n.chunks]:seq.size] = n.chunks #Set the last group affiliation because it has a unique endpoint of seq.size.
    return(groups)
    }

我的问题是:这种方法是否不够优雅或者效率不高?在我计划的代码中,它会被调用数千次,因此效率对我很重要。最好避免使用for循环或手动设置第一个和最后一个组。我的另一个问题是:是否存在可以破坏这个方法的逻辑输入?我知道n.chunks不能大于seq.size,所以我的意思是除此之外的问题。


1
在这里,您需要处理的典型大小是什么?似乎对于不太大的n.chunks,您的代码应该足够快。 - F. Privé
1
此外,你的代码有两个奇怪的地方。你正在重新分配每个组的最后一个元素到下一个组(break.pts[i-1]:break.pts[i]),而最后一组与前一组具有相同的分配。 - F. Privé
对于随机的Y,如果没有最小块大小,你会在sort(sample(1:length(vector), sample(1:length(n.chunks( ie, vector again), replace = FALSE), replace= FALSE))中,除非你可以扔进一个seq - Chris
@Chris 我认为 sort 会影响你的执行时间。我能想到的最简单的代码是 sort(sample(1:n.chunks, seq.size, replace = TRUE))。但实际上这会变得相当慢(相对而言)。 - user10917479
1
@Adam,我理解你的观点并且欣赏你下面的代码。我的排序包装只是遵循上面的原始贴文,但如果不是两者都随机,那哪个是随机的更多是我的问题,因为对于范围的开始/结束进行随机化似乎是一个相当棘手的问题。 - Chris
@F. Prive。我会说n.chunks将小于100,seq.size将在100到10000之间。此外,请继续编辑我的问题,如果我的代码索引错误--如果是这样,我不会感到惊讶! - Bajcz
2个回答

2
对于较小的数字,这应该是相当快的。但以下是更加简洁的方法。
k.chunks2 = function(seq.size, n.chunks) {
  break.pts <- sort(sample(1:seq.size, n.chunks - 1, replace = FALSE))
  break.len <- diff(c(0, break.pts, seq.size))
  
  groups <- rep(1:n.chunks, times = break.len)
  return(groups)
}

如果你有大量的分组,我认为sort将会消耗更多的执行时间。因此,你可以像这样做(可能可以进行调整以获得更快的速度)来根据比例进行分割。我不确定自己对此的感觉,因为随着n.chunks变得非常大,比例将变得非常小。但是这样做会更快。
k.chunks3 = function(seq.size, n.chunks) {
  props <- runif(n.chunks)
  grp.props <- props / sum(props)
  
  chunk.size <- floor(grp.props[-n.chunks] * seq.size)
  break.len <- c(chunk.size, seq.size - sum(chunk.size))
  
  groups <- rep(1:n.chunks, times = break.len)
  return(groups)
}

进行基准测试,我认为这些任何一种都足够快(单位是微秒)。

n <- 1000
y <- 10

microbenchmark::microbenchmark(k.chunks(n, y),
                               k.chunks2(n, y),
                               k.chunks3(n, y))

Unit: microseconds
            expr  min    lq   mean median    uq   max neval
  k.chunks(n, y) 49.9 52.05 59.613  53.45 58.35 251.7   100
 k.chunks2(n, y) 46.1 47.75 51.617  49.25 52.55 107.1   100
 k.chunks3(n, y)  8.1  9.35 11.412  10.80 11.75  44.2   100

但随着数字变得越来越大,你会注意到速度明显加快(请注意单位现在是毫秒)。

n <- 1000000
y <- 100000

microbenchmark::microbenchmark(k.chunks(n, y),
                               k.chunks2(n, y),
                               k.chunks3(n, y))

Unit: milliseconds
            expr     min       lq     mean   median       uq      max neval
  k.chunks(n, y) 46.9910 51.38385 57.83917 54.54310 56.59285 113.5038   100
 k.chunks2(n, y) 17.2184 19.45505 22.72060 20.74595 22.73510  69.5639   100
 k.chunks3(n, y)  7.7354  8.62715 10.32754  9.07045 10.44675  58.2093   100

总的来说,我可能会使用我的k.chunks2()函数。


好答案!我不知道diff(),或者至少它在这个上下文中的潜在用途。我有三个问题。首先,您两种方法得到的输出结构不同——后者似乎总是产生比前者更正常分布的组长度(前者是我喜欢的,因为在我的情况下混沌是好的)。第二,有没有一种方法可以使用rle()来实现这一点?我思考了一段时间。第三,您是否有理由避免索引?索引通常很慢吗? - Bajcz
1
是的,这两个会有不同的结构。老实说,我没有玩够它来真正观察行为。所以选择你喜欢的那一个吧!你可能可以使用 rle(),但我不确定你会得到什么。本质上,它将替换 rep(),应该是一个快速的原语。第三,我不确定在这种情况下你所指的索引是什么,但在这种情况下,我认为问题是要找出断点长度。一旦你有了这个,用 rep() 扩展就很自然了。所以这是我想到的最干净的方法。 - user10917479
我的代码经常使用 [,但你的不用——这就是我所说的索引。我记得读过它相对于其他函数来说速度比较慢,是吗? - Bajcz
1
啊,我明白了。老实说,使用 for 循环并做那个操作并不是很糟糕的事情。但在这种情况下,我看到了一种清晰的向量化输出方式,所以我就这样做了。它通常可以更快地运行,而且对我来说只是使代码更易读。 - user10917479

0

随机数可能效率低下,但似乎应该是这样的预期。随机数表明所有输入元素也应该是随机的。因此,在考虑从向量Y中选择所需的随机选择时,应将努力应用于Y的索引和连续的Y,这些Y将是或看起来是随机的。通过足够数量的Y集,可以确定索引与完全随机之间的距离,但也许这并不重要,或者仅仅重复几千次就不足以证明它。

尽管如此,我的感觉是sample的两个输入都需要以某种方式“随机”,因为其中一个的确定会降低另一个的随机性。

my_vector <- c(1:100000) 
sample_1 <- sample(my_vector, 50, replace = FALSE)
sample_2 <- sample(my_vector, 80, replace = FALSE)
full_range <- c(1, sort(unique(sample1,sample2)), 100000)
starts <- full_range[c(TRUE,FALSE)]#[generally](https://stackoverflow.com/questions/33257610/how-to-return-the-elements-in-the-odd-position)
ends <- full_range[c(FALSE, TRUE)]
!unique(diff(full_range))

如果没有设置种子,我认为非可重复性是你在Y(s)上获得随机选择的最接近方式。这个答案只是建议一种索引Y的方法。之后的索引使用可能会遵循@Adam的方法。当然,我对所有这些都可能完全错误。比我更清晰的随机思考者可能会发表意见...


非常抱歉,但我必须承认我没有跟上你的思路。你是在说我的代码实现在某种意义上并不真正“随机”吗? - Bajcz
很抱歉,这是一种直觉,类似上述内容可能会解决问题,并有助于您对混乱的感知。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接