【问题标题】:In R, split a vector randomly into k chunks?在 R 中,将一个向量随机分成 k 个块?
【发布时间】:2021-10-30 09:55:15
【问题描述】:

我在这里看到了关于“将向量 X 拆分为 R 中的 Y 块”问题的许多变体。例如:here 和 here 仅两个。所以,当我意识到我需要将一个向量分成 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)
    }

我的问题是:这在某种程度上是不优雅或低效的吗?它会在我计划做的代码中被调用 1000 次,所以效率对我来说很重要。避免for 循环或必须“手动”设置第一组和最后一组会特别好。我的另一个问题:是否有逻辑输入可以打破这一点?我知道n.chunks 不能> seq.size,所以我的意思是除此之外。

【问题讨论】:

  • 您需要在这里处理的典型尺寸是多少?对于一个不太大的n.chunks 来说,您的代码应该足够快。
  • 另外,您的代码中有两处奇怪的地方。您正在将每个组的最后一个元素重新分配给下一个组 (break.pts[i-1]:break.pts[i]),最后一个组与之前的分配相同。
  • 对于随机 Y,你不会在 sort(sample(1:length(vector), sample(1:length(n.chunks( ie, vector again), replace = FALSE), replace= FALSE)) 中,除非有最小块大小,此时你可以在 seq 中折腾?
  • @Chris 我认为sort 会缩短你的执行时间。我能想到的最简单的代码是sort(sample(1:n.chunks, seq.size, replace = TRUE))。但这实际上会变得相当缓慢(相对)。
  • @Adam,我同意你的观点并欣赏你下面的代码。我的排序包装只是遵循上面的 OP,但如果不是两者都是我的问题,这是随机的,因为随机化范围的开始/结束似乎是一个非常棘手的问题。

标签: r performance vector vectorization


【解决方案1】:

随机可能效率低下,但似乎应该如此。随机建议所有输入元素也应该是随机的。因此,考虑从向量 Y 中进行所需的随机选择;似乎应该将努力应用于 Y 的索引和连续的 Y(s),这将是或似乎是随机的。有了足够的 Y(s) 集,就可以看出索引与完全随机的距离有多远,但这可能并不重要,或者仅仅数千次重复不足以证明这一点。

不过,我的感觉是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 的方法。而且,当然,我对所有这一切都可能是完全错误的。比我更清晰的随机思想者可能会权衡......

【讨论】:

  • 我很抱歉,但我不得不承认在这里没有关注你。你是说我在代码中实现的东西在某种意义上并不是真正的“随机”?
  • 我很遗憾地说这是一种直觉,上面的内容可能会解决并可能有助于增加你的混乱感。
【解决方案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() 来做到这一点?我对此感到困惑。第三,您是否出于某种原因避免编制索引?索引通常很慢吗?
  • 是的,两者的结构不同。老实说,我玩的不够多,无法真正观察这些行为。所以抓住你喜欢的那个吧!您可能可以使用rle(),但我不确定您会得到什么。从本质上讲,它将替代rep(),它应该是一个快速的原语。第三,我不确定您在这种情况下所指的索引是什么,但在这种情况下,我认为问题是与中断长度有关。一旦你有了它,用rep() 扩展似乎很自然。所以这是我想到的最干净的方式。
  • 我的使用[ 很多,但你的没有——这就是我所说的索引。我想我记得读到与其他功能相比,这是一个相对较慢的功能?
  • 啊,明白了。老实说,for 循环并没有那么糟糕。但在这种情况下,我看到了一种将输出向量化的清晰方法,所以我做到了。它通常可以更快,但也只是让我的代码更具可读性。
猜你喜欢
  • 1970-01-01
  • 2017-10-17
  • 1970-01-01
  • 1970-01-01
  • 2012-08-28
  • 2023-03-23
  • 1970-01-01
  • 2013-03-09
  • 1970-01-01
相关资源
最近更新 更多