【问题标题】:Split a vector into chunks将向量拆分为块
【发布时间】:2011-03-20 02:25:06
【问题描述】:

我必须在 R 中将向量拆分为 n 个大小相等的块。我找不到任何基本函数来做到这一点。谷歌也没有让我去任何地方。到目前为止,这是我想出的;

x <- 1:10
n <- 3
chunk <- function(x,n) split(x, factor(sort(rank(x)%%n)))
chunk(x,n)
$`0`
[1] 1 2 3

$`1`
[1] 4 5 6 7

$`2`
[1]  8  9 10

【问题讨论】:

  • 是的,很不清楚你得到的是“n 个大小相等的块”的解决方案。但也许这也能让你到达那里:x
  • 问题中的解决方案和前面评论中的解决方案都不正确,因为如果向量有重复条目,它们可能不起作用。试试这个: > foo chunk(foo, 2) (给出错误的结果) > chunk(foo, 3) (也是错误的)
  • (继续前面的评论)为什么? rank(x) 不需要是整数 > rank(c(1,1,2,3)) [1] 1.5 1.5 3.0 4.0 所以这就是问题中的方法失败的原因。这个有效(感谢下面的哈兰)> chunk2
  • > split(foo, cut(foo, 3, labels = FALSE)) (也是错误的)
  • 正如@mathheadinclouds 所暗示的,示例数据是一个非常特殊的情况。更通用的示例将是更有用和更好的测试。例如。 x &lt;- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y &lt;- letters[x]; z &lt;- factor(y) 给出了缺失数据、重复值、尚未排序且属于不同类(整数、字符、因子)的示例。

标签: r vector


【解决方案1】:

这里还有一个,让您可以控制是否要对结果进行排序:

split_to_chunks <- function(x, n, keep.order=TRUE){
  if(keep.order){
    return(split(x, sort(rep(1:n, length.out = length(x)))))
  }else{
    return(split(x, rep(1:n, length.out = length(x))))
  }
}

split_to_chunks(x = 1:11, n = 3)
$`1`
[1] 1 2 3 4

$`2`
[1] 5 6 7 8

$`3`
[1]  9 10 11

split_to_chunks(x = 1:11, n = 3, keep.order=FALSE)

$`1`
[1]  1  4  7 10

$`2`
[1]  2  5  8 11

$`3`
[1] 3 6 9

【讨论】:

    【解决方案2】:

    简化版:

    n = 3
    split(x, sort(x%%n))
    

    注意:这仅适用于数字向量。

    【讨论】:

    • 我喜欢这个,因为它为您提供了尽可能相同大小的块(有利于分割大型任务,例如容纳有限的 RAM 或跨多个线程运行任务)。
    • 这很有用,但请记住,这仅适用于数字向量。
    • @KeithHughitt 这可以通过因子来解决,并将水平返回为数字。或者至少我是这样实现的。
    • @drmariod 也可以通过split(x, sort(1:length(x) %% n))进行扩展
    • @JessicaBurnett 我认为split() 是这段代码中最慢的部分(因为它调用as.factor)。因此,也许可以考虑使用 data.frame 并执行data$group &lt;- sort(1:length(data) %% n) 之类的操作,然后在其余代码中使用 group 列。
    【解决方案3】:

    我想出了这个解决方案:

    require(magrittr)
    create.chunks <- function(x, elements.per.chunk){
        # plain R version
        # split(x, rep(seq_along(x), each = elements.per.chunk)[seq_along(x)])
        # magrittr version - because that's what people use now
        x %>% seq_along %>% rep(., each = elements.per.chunk) %>% extract(seq_along(x)) %>% split(x, .) 
    }
    create.chunks(letters[1:10], 3)
    $`1`
    [1] "a" "b" "c"
    
    $`2`
    [1] "d" "e" "f"
    
    $`3`
    [1] "g" "h" "i"
    
    $`4`
    [1] "j"
    

    关键是使用seq(each = chunk.size) 参数使其工作。使用seq_along 与我之前的解决方案中的rank(x) 类似,但实际上能够通过重复条目产生正确的结果。

    【讨论】:

    • 对于那些担心 rep(seq_along(x), each = elements.per.chunk) 可能对内存造成太大压力的人:是的。你可以试试我之前建议的修改版本:chunk
    • 对我来说,它会产生以下错误:no applicable method for 'extract_' applied to an object of class "c('integer', 'numeric')
    【解决方案4】:

    这将与你所拥有的不同,但我认为仍然是一个很好的列表结构:

    chunk.2 <- function(x, n, force.number.of.groups = TRUE, len = length(x), groups = trunc(len/n), overflow = len%%n) { 
      if(force.number.of.groups) {
        f1 <- as.character(sort(rep(1:n, groups)))
        f <- as.character(c(f1, rep(n, overflow)))
      } else {
        f1 <- as.character(sort(rep(1:groups, n)))
        f <- as.character(c(f1, rep("overflow", overflow)))
      }
      
      g <- split(x, f)
      
      if(force.number.of.groups) {
        g.names <- names(g)
        g.names.ordered <- as.character(sort(as.numeric(g.names)))
      } else {
        g.names <- names(g[-length(g)])
        g.names.ordered <- as.character(sort(as.numeric(g.names)))
        g.names.ordered <- c(g.names.ordered, "overflow")
      }
      
      return(g[g.names.ordered])
    }
    

    这将为您提供以下内容,具体取决于您希望它的格式:

    > x <- 1:10; n <- 3
    > chunk.2(x, n, force.number.of.groups = FALSE)
    $`1`
    [1] 1 2 3
    
    $`2`
    [1] 4 5 6
    
    $`3`
    [1] 7 8 9
    
    $overflow
    [1] 10
    
    > chunk.2(x, n, force.number.of.groups = TRUE)
    $`1`
    [1] 1 2 3
    
    $`2`
    [1] 4 5 6
    
    $`3`
    [1]  7  8  9 10
    

    使用这些设置运行几个计时:

    set.seed(42)
    x <- rnorm(1:1e7)
    n <- 3
    

    那么我们有以下结果:

    > system.time(chunk(x, n)) # your function 
       user  system elapsed 
     29.500   0.620  30.125 
    
    > system.time(chunk.2(x, n, force.number.of.groups = TRUE))
       user  system elapsed 
      5.360   0.300   5.663 
    

    注意:将 as.factor() 更改为 as.character() 使我的函数速度提高了一倍。

    【讨论】:

      【解决方案5】:

      这会分成大小为 ⌊n/k⌋+1 或 ⌊n/k⌋ 的块,并且不使用 O(n log n) 排序。

      get_chunk_id<-function(n, k){
          r <- n %% k
          s <- n %/% k
          i<-seq_len(n)
          1 + ifelse (i <= r * (s+1), (i-1) %/% (s+1), r + ((i - r * (s+1)-1) %/% s))
      }
      
      split(1:10, get_chunk_id(10,3))
      

      【讨论】:

        【解决方案6】:

        使用基础 R 的 rep_len:

        x <- 1:10
        n <- 3
        
        split(x, rep_len(1:n, length(x)))
        # $`1`
        # [1]  1  4  7 10
        # 
        # $`2`
        # [1] 2 5 8
        # 
        # $`3`
        # [1] 3 6 9
        

        如前所述,如果您想要排序索引,只需:

        split(x, sort(rep_len(1:n, length(x))))
        # $`1`
        # [1] 1 2 3 4
        # 
        # $`2`
        # [1] 5 6 7
        # 
        # $`3`
        # [1]  8  9 10
        

        【讨论】:

          【解决方案7】:

          另一种可能性是包parallel中的splitIndices函数:

          library(parallel)
          splitIndices(20, 3)
          

          给予:

          [[1]]
          [1] 1 2 3 4 5 6 7
          
          [[2]]
          [1]  8  9 10 11 12 13
          
          [[3]]
          [1] 14 15 16 17 18 19 20
          

          【讨论】:

            【解决方案8】:

            对不起,如果这个答案来得太晚,但也许它对其他人有用。其实这个问题有一个非常有用的解决方案,在 ?split 的末尾有解释。

            > testVector <- c(1:10) #I want to divide it into 5 parts
            > VectorList <- split(testVector, 1:5)
            > VectorList
            $`1`
            [1] 1 6
            
            $`2`
            [1] 2 7
            
            $`3`
            [1] 3 8
            
            $`4`
            [1] 4 9
            
            $`5`
            [1]  5 10
            

            【讨论】:

            • 如果每组中的值数量不相等,这将中断!
            【解决方案9】:

            通过简单地使用索引来拆分向量的简单函数 - 无需过于复杂

            vsplit <- function(v, n) {
                l = length(v)
                r = l/n
                return(lapply(1:n, function(i) {
                    s = max(1, round(r*(i-1))+1)
                    e = min(l, round(r*i))
                    return(v[s:e])
                }))
            }
            

            【讨论】:

              【解决方案10】:

              我需要一个函数,它接受 data.table 的参数(用引号括起来)和另一个参数,即原始 data.table 子集中行数的上限。此函数生成上限允许的任何数量的 data.tables:

              library(data.table)    
              split_dt <- function(x,y) 
                  {
                  for(i in seq(from=1,to=nrow(get(x)),by=y)) 
                      {df_ <<- get(x)[i:(i + y)];
                          assign(paste0("df_",i),df_,inherits=TRUE)}
                  rm(df_,inherits=TRUE)
                  }
              

              此函数为我提供了一系列名为 df_[number] 的 data.tables,名称中包含原始 data.table 的起始行。最后一个 data.table 可以很短并充满 NA,因此您必须将其子集化回剩余的任何数据。这种类型的功能很有用,因为某些 GIS 软件对您可以导入的地址引脚数量有限制,例如。因此可能不建议将 data.tables 切成更小的块,但可能无法避免。

              【讨论】:

                【解决方案11】:

                试试 ggplot2 函数,cut_number:

                library(ggplot2)
                x <- 1:10
                n <- 3
                cut_number(x, n) # labels = FALSE if you just want an integer result
                #>  [1] [1,4]  [1,4]  [1,4]  [1,4]  (4,7]  (4,7]  (4,7]  (7,10] (7,10] (7,10]
                #> Levels: [1,4] (4,7] (7,10]
                
                # if you want it split into a list:
                split(x, cut_number(x, n))
                #> $`[1,4]`
                #> [1] 1 2 3 4
                #> 
                #> $`(4,7]`
                #> [1] 5 6 7
                #> 
                #> $`(7,10]`
                #> [1]  8  9 10
                

                【讨论】:

                • 这不适用于拆分this comment 中定义的xyz。特别是,它会对结果进行排序,这可能会也可能不会,这取决于应用程序。
                • 而是this comment
                【解决方案12】:

                如果你不喜欢split() 并且你不喜欢matrix()(带有悬空的NA),那就是:

                chunk <- function(x, n) (mapply(function(a, b) (x[a:b]), seq.int(from=1, to=length(x), by=n), pmin(seq.int(from=1, to=length(x), by=n)+(n-1), length(x)), SIMPLIFY=FALSE))
                

                split()一样,它返回一个列表,但它不会浪费时间或空间与标签,因此它可能会更高效。

                【讨论】:

                • 这太快了!
                • 这也执行大小为 n 的块而不是 n 块。
                【解决方案13】:

                如果你不喜欢 split() 并且你不介意 NA 填补你的短尾巴:

                chunk <- function(x, n) { if((length(x)%%n)==0) {return(matrix(x, nrow=n))} else {return(matrix(append(x, rep(NA, n-(length(x)%%n))), nrow=n))} }
                

                返回矩阵 ([,1:ncol]) 的列是您正在寻找的机器人。

                【讨论】:

                  【解决方案14】:

                  感谢@Sebastian function

                  chunk <- function(x,y){
                           split(x, factor(sort(rank(row.names(x))%%y)))
                           }
                  

                  【讨论】:

                    【解决方案15】:

                    将 d 分成大小为 20 的块:

                    split(d, ceiling(seq_along(d)/20))
                    

                    更多细节:我想你只需要seq_along()split()ceiling()

                    > d <- rpois(73,5)
                    > d
                     [1]  3  1 11  4  1  2  3  2  4 10 10  2  7  4  6  6  2  1  1  2  3  8  3 10  7  4
                    [27]  3  4  4  1  1  7  2  4  6  0  5  7  4  6  8  4  7 12  4  6  8  4  2  7  6  5
                    [53]  4  5  4  5  5  8  7  7  7  6  2  4  3  3  8 11  6  6  1  8  4
                    > max <- 20
                    > x <- seq_along(d)
                    > d1 <- split(d, ceiling(x/max))
                    > d1
                    $`1`
                     [1]  3  1 11  4  1  2  3  2  4 10 10  2  7  4  6  6  2  1  1  2
                    
                    $`2`
                     [1]  3  8  3 10  7  4  3  4  4  1  1  7  2  4  6  0  5  7  4  6
                    
                    $`3`
                     [1]  8  4  7 12  4  6  8  4  2  7  6  5  4  5  4  5  5  8  7  7
                    
                    $`4`
                     [1]  7  6  2  4  3  3  8 11  6  6  1  8  4
                    

                    【讨论】:

                    • 该问题要求n 大小相等的块。这会为您提供未知数量的大小为n 的块。我遇到了同样的问题并使用了@mathheadinclouds 的解决方案。
                    • 从 d1 的输出可以看出,这个答案并没有将 d 分成大小相等的组(4 显然更短)。因此它没有回答这个问题。
                    • @rrs : 分割(d,天花板(seq_along(d)/(length(d)/n)))
                    • 我知道这已经很老了,但它可能对那些在这里绊倒的人有所帮助。尽管 OP 的问题是分成大小相等的块,但如果向量恰好不是除数的倍数,则最后一个缝隙的大小将与块不同。要拆分为n-chunks,我使用了max &lt;- length(d)%/%n。我将它与 31 个字符串的向量一起使用,并获得了一个包含 10 个句子和 1 个句子的 3 个向量的列表。
                    • @Harlan 有没有办法洗牌?您的解决方案对我来说效果很好,但我想确保拆分是随机分配的,而不仅仅是连续的
                    【解决方案16】:

                    这是另一个变体。

                    注意:在此示例中,您在第二个参数中指定了 CHUNK SIZE

                    1. 所有块都是统一的,除了最后一个;
                    2. 最后一个最坏的情况是更小,永远不会大于块大小。

                    chunk <- function(x,n)
                    {
                        f <- sort(rep(1:(trunc(length(x)/n)+1),n))[1:length(x)]
                        return(split(x,f))
                    }
                    
                    #Test
                    n<-c(1,2,3,4,5,6,7,8,9,10,11)
                    
                    c<-chunk(n,5)
                    
                    q<-lapply(c, function(r) cat(r,sep=",",collapse="|") )
                    #output
                    1,2,3,4,5,|6,7,8,9,10,|11,|
                    

                    【讨论】:

                      【解决方案17】:

                      我需要相同的功能并阅读了以前的解决方案,但是我还需要将不平衡的块放在最后,即如果我有 10 个元素将它们分成每个 3 个的向量,那么我的结果应该有向量分别有 3,3,4 个元素。所以我使用了以下内容(我没有为可读性优化代码,否则不需要有很多变量):

                      chunk <- function(x,n){
                        numOfVectors <- floor(length(x)/n)
                        elementsPerVector <- c(rep(n,numOfVectors-1),n+length(x) %% n)
                        elemDistPerVector <- rep(1:numOfVectors,elementsPerVector)
                        split(x,factor(elemDistPerVector))
                      }
                      set.seed(1)
                      x <- rnorm(10)
                      n <- 3
                      chunk(x,n)
                      $`1`
                      [1] -0.6264538  0.1836433 -0.8356286
                      
                      $`2`
                      [1]  1.5952808  0.3295078 -0.8204684
                      
                      $`3`
                      [1]  0.4874291  0.7383247  0.5757814 -0.3053884
                      

                      【讨论】:

                        【解决方案18】:
                        chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 
                        

                        【讨论】:

                        • 这是迄今为止我尝试过的最快的方法!设置labels = FALSE 速度提高两倍,使用cut() 比在我的数据上使用ceiling(seq_along(x) / n 快​​4 倍。
                        • 更正:这是split() 方法中最快的。 @verbarmour 下面的答案是总体上最快的。它的速度非常快,因为它不需要使用因子,也不需要排序。这个答案值得更多的支持。
                        【解决方案19】:

                        split(x,matrix(1:n,n,length(x))[1:length(x)])

                        也许这样更清楚,但思路相同:
                        split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

                        如果你想要它排序,在它周围扔一个排序

                        【讨论】:

                          【解决方案20】:

                          还有一些变种...

                          > x <- 1:10
                          > n <- 3
                          

                          请注意,您不需要在这里使用factor 函数,但您仍然希望sort o/w 您的第一个向量将是1 2 3 10

                          > chunk <- function(x, n) split(x, sort(rank(x) %% n))
                          > chunk(x,n)
                          $`0`
                          [1] 1 2 3
                          $`1`
                          [1] 4 5 6 7
                          $`2`
                          [1]  8  9 10
                          

                          或者您可以指定字符索引,而不是上面左勾号中的数字:

                          > my.chunk <- function(x, n) split(x, sort(rep(letters[1:n], each=n, len=length(x))))
                          > my.chunk(x, n)
                          $a
                          [1] 1 2 3 4
                          $b
                          [1] 5 6 7
                          $c
                          [1]  8  9 10
                          

                          或者您可以使用存储在向量中的明文名称。请注意,使用sort 获取x 中的连续值将标签按字母顺序排列:

                          > my.other.chunk <- function(x, n) split(x, sort(rep(c("tom", "dick", "harry"), each=n, len=length(x))))
                          > my.other.chunk(x, n)
                          $dick
                          [1] 1 2 3
                          $harry
                          [1] 4 5 6
                          $tom
                          [1]  7  8  9 10
                          

                          【讨论】:

                            【解决方案21】:

                            您可以按照 mdsummer 的建议将拆分/剪切与分位数组合以创建偶数组:

                            split(x,cut(x,quantile(x,(0:n)/n), include.lowest=TRUE, labels=FALSE))
                            

                            这对您的示例给出了相同的结果,但对于偏斜变量则不然。

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 2018-08-09
                              • 2020-11-08
                              相关资源
                              最近更新 更多