tags:

views:

213

answers:

5

Hi,

I have to split a vector into n chunks of equal size in R. I couldn't find any base function to do that. Also Google didn't get me anywhere. So here is what I came up with, hopefully it helps someone some where.

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

Any comments, suggestions or improvements are really welcome and appreciated.

Cheers, Sebastian

+1  A: 

You could combine the split/cut, as suggested by mdsummer, with quantile to create even groups:

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

This gives the same result for your example, but not for skewed variables.

DiggyF
+2  A: 

A few more variants to the pile...

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

Note, that you don't need to use the factor function here, but you still want to sort o/w your first vector would be 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

Or you can assign character indices, vice the numbers in left ticks above:

> 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

Or you can use plainword names stored in a vector. Note that using sort to get consecutive values in x alphabetizes the labels:

> 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
richardh
+4  A: 

This will split it differently to what you have, but is still quite a nice list structure I think:

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)))
   }
   split(x, f)
}

Which will give you the following, depending on how you want it formatted:

> 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

Running a couple of timings using these settings:

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

Then we have the following results:

> 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 

EDIT: Changing from as.factor() to as.character() in my function made it twice as fast.

Tony Breyal
+1 for showing the timings which are interesting.
Christine Forrester
+1  A: 

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

perhaps this is more clear, but the same idea:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

if you want it ordered,throw a sort around it

frankc
+3  A: 

I think all you need is seq_along(), split() and ceiling():

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

$`2`
 [1]  1  6  4  5  8  6  6  5  5  3  7  5  2  7  4 11  5  7  2  5

$`3`
 [1]  4  4  8  4 11  4  6  5  3  7  6  2  9  5  5  6  2  4  5  6

$`4`
 [1] 5 4 6 4 3 6 4 5 4 4 6 2 5
Harlan
I'm giving this one +1 for clarity and because it's concise. I also hope this is what Sebastian was looking for. :)
Roman Luštrik