将向量拆分为块

Split a vector into chunks

提问人:Sebastian 提问时间:7/23/2010 最后编辑:M--Sebastian 更新时间:10/26/2022 访问量:231780

问:

我必须在 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
r 向量

评论

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

答:

10赞 SiggyF 7/23/2010 #1

您可以按照 mdsummer 的建议,将分割/切割与分位数组合在一起以创建偶数组:

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

这将为您的示例提供相同的结果,但不会为偏斜变量提供相同的结果。

13赞 Richard Herron 7/23/2010 #2

还有几个变体......

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

请注意,您不需要在此处使用该函数,但您仍然希望 o/w 您的第一个向量将是:factorsort1 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

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

> 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
19赞 15 revs, 2 users 100%Tony Breyal #3

这将以不同的方式将其拆分到您拥有的,但我认为仍然是一个相当不错的列表结构:

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()

7赞 frankc 7/24/2010 #4

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

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

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

397赞 Harlan 7/24/2010 #5

将 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

评论

48赞 rrs 4/22/2014
该问题要求大小相等的块。这会给你一个未知数量的大小块。我遇到了同样的问题,并使用了@mathheadinclouds的解决方案。nn
5赞 Calimo 1/24/2015
从 d1 的输出中可以看出,这个答案不会将 d 分成大小相等的组(4 显然更短)。因此,它没有回答这个问题。
9赞 gkcn 6/5/2015
@rrs : split(d, ceiling(seq_along(d)/(length(d)/n)))
0赞 salvu 2/4/2017
我知道这已经很老了,但它可能对那些在这里绊倒的人有所帮助。尽管 OP 的问题是将向量拆分为大小相等的块,但如果向量恰好不是除数的倍数,则最后一个缺口的大小将与块不同。要拆分为我用的.我将其与 31 个字符串的向量一起使用,并获得了 3 个向量的 10 个句子和 1 个句子之一的列表。n-chunksmax <- length(d)%/%n
0赞 Spooked 10/22/2020
@Harlan 有没有办法洗牌拆分?您的解决方案对我来说效果很好,但我想确保拆分是随机分配的,而不仅仅是连续的
117赞 mathheadinclouds 4/29/2013 #6
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 

评论

0赞 Drumy 10/21/2020
这是我迄今为止尝试过的最快的方法!设置速度提高两次,使用速度比在我的数据上使用快 4 倍。labels = FALSEcut()ceiling(seq_along(x) / n
1赞 Drumy 10/21/2020
更正:这是方法中最快的。下面@verbarmour的回答是总体上最快的。它的速度非常快,因为它不必与因子一起工作,也不需要排序。这个答案值得更多的点赞。split()
6赞 Zak D 6/23/2013 #7

我需要相同的函数并阅读了以前的解决方案,但是我还需要将不平衡块放在最后,即如果我有 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
7赞 eAndy 9/15/2013 #8

这是另一个变体。

注意:在此示例中,您将在第二个参数中指定 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,|
2赞 user1587280 12/5/2014 #9

功能归功于 @Sebastian

chunk <- function(x,y){
         split(x, factor(sort(rank(row.names(x))%%y)))
         }
2赞 verbamour 12/24/2014 #10

如果你不喜欢,也不介意NA填充你的短尾巴:split()

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]) 的列是您要查找的机器人。

20赞 verbamour 12/24/2014 #11

如果你不喜欢你不喜欢(它晃来晃去的NA),有这个:split()matrix()

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()

评论

1赞 Drumy 10/21/2020
这太快了!
1赞 nelliott 12/8/2021
这也执行大小为 n 而不是 n 块的块。
1赞 Jeff 3/1/2023
正是我需要防止“内存不足”错误。谢谢!
0赞 theforestecologist 8/29/2023
伟大。有没有办法快速设置它,使每个组每次都随机化?
23赞 Scott Worland 1/9/2015 #12

尝试 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

评论

2赞 Kalin 2/22/2018
这不适用于拆分此注释中定义的 、 或。特别是,它会对结果进行排序,根据应用程序的不同,结果可能会或可能不会。xyz
0赞 Kalin 2/22/2018
相反,这个评论
54赞 zhan2383 4/21/2016 #13

简化版本:

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

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

评论

0赞 alexvpickering 7/22/2016
我喜欢这个,因为它为您提供了尽可能大小相等的块(适用于划分大型任务,例如容纳有限的 RAM 或跨多个线程运行任务)。
7赞 Keith Hughitt 8/25/2016
这很有用,但请记住,这仅适用于数值向量。
0赞 drmariod 4/5/2018
@KeithHughitt这可以通过因子来解决,并将水平返回为数字。或者至少我是这样实现的。
2赞 Richard DiSalvo 9/15/2020
@drmariod也可以通过做来扩展split(x, sort(1:length(x) %% n))
1赞 Richard DiSalvo 12/15/2021
@JessicaBurnett我认为是这段代码中最慢的部分(因为它调用了)。因此,可以考虑使用 data.frame 并执行类似 的操作,然后在代码的其余部分使用 group 列。split()as.factordata$group <- sort(1:length(data) %% n)
2赞 rferrisx 3/27/2017 #14

我需要一个函数来接受 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 切成更小的块,但这可能是不可避免的。

5赞 Philip Michaelsen 2/8/2018 #15

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

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])
    }))
}
3赞 Laura Paladini 8/21/2018 #16

很抱歉,如果这个答案来得太晚了,但也许它对其他人有用。实际上,这个问题有一个非常有用的解决方案,在 ?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

评论

3赞 Matifou 9/11/2018
如果每个组中的值数量不相等,这将中断!
11赞 Matifou 9/11/2018 #17

还有一种可能性是来自包的函数:splitIndicesparallel

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

注意:这仅适用于数值。如果要拆分字符向量,则需要进行一些索引: lapply(splitIndices(20, 3), \(x) letters[1:20][x])

评论

0赞 Julien 7/31/2022
仅适用于数值
1赞 Sebastian 9/19/2018 #18

我想出了这个解决方案:

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_alongrank(x)

评论

1赞 Sebastian 9/19/2018
对于那些担心 rep(seq_along(x), each = elements.per.chunk) 可能过于紧张的人:是的,确实如此。您可以尝试我之前建议的修改版本:chunk <- function(x,n) split(x, factor(seq_along(x)%%n))
0赞 sharchaea 11/6/2020
对我来说,它会产生以下错误:no applicable method for 'extract_' applied to an object of class "c('integer', 'numeric')
26赞 FXQuantTrader 11/1/2018 #19

使用基数 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
-1赞 Valentas 7/31/2019 #20

这会拆分为大小为 ⌊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))
0赞 Iyar Lin 10/5/2021 #21

这是另一个,允许您控制是否要对结果进行排序:

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
0赞 cmilando 10/26/2022 #22

不确定这是否回答了 OP 的问题,但我认为这里很有用%%

df # some data.frame
N_CHUNKS <- 10
I_VEC <- 1:nrow(df)
df_split <- split(df, sort(I_VEC %% N_CHUNKS))