Dividir un vector en trozos en R


227

Tengo que dividir un vector en n fragmentos del mismo tamaño en R. No pude encontrar ninguna función base para hacer eso. Además, Google no me llevó a ninguna parte. Así que aquí está lo que se me ocurrió, espero que ayude a alguien en algún lugar.

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

Cualquier comentario, sugerencia o mejora son realmente bienvenidos y apreciados.

Saludos, Sebastian


55
Sí, no está claro si lo que obtienes es la solución para "n trozos de igual tamaño". Pero tal vez esto también te lleve allí: x <- 1:10; n <- 3; split (x, cut (x, n, labels = FALSE))
mdsumner el

tanto la solución en la pregunta como la solución en el comentario anterior son incorrectas, ya que podrían no funcionar si el vector tiene entradas repetidas. Pruebe esto:> 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> fragmento (foo, 2) (da un resultado incorrecto)> fragmento (foo, 3) (también incorrecto)
mathheadinclouds

(continuando el comentario anterior) ¿por qué? rank (x) no necesita ser un entero> rank (c (1,1,2,3)) [1] 1.5 1.5 3.0 4.0, por eso falla el método en la pregunta. este funciona (gracias a Harlan a continuación)> chunk2 <- function (x, n) split (x, cut (seq_along (x), n, labels = FALSE))
mathheadinclouds

2
> Split (foo, corte (foo, 3, etiquetas = FALSO)) (también mal)
mathheadinclouds

1
Como sugiere @mathheadinclouds, los datos de ejemplo son un caso muy especial. Los ejemplos que son más generales serían más útiles y mejores pruebas. Por ejemplo, x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)da ejemplos con datos faltantes, valores repetidos, que aún no están ordenados, y están en diferentes clases (entero, carácter, factor).
Kalin

Respuestas:


313

Una línea dividida en trozos de tamaño 20:

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

Más detalles: creo que todo lo que necesitas es seq_along(), split()y 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

34
La pregunta pide ntrozos de igual tamaño. Esto te da un número desconocido de trozos de tamaño n. Tuve el mismo problema y utilicé las soluciones de @mathheadinclouds.
rrs

44
Como se puede ver en la salida de d1, esta respuesta no divide a d en grupos de igual tamaño (4 es obviamente más corto). Por lo tanto, no responde la pregunta.
Calimo

99
@rrs: split (d, ceiling (seq_along (d) / (length (d) / n)))
gkcn

Sé que esto es bastante antiguo, pero puede ser de ayuda para quienes tropiezan aquí. Aunque la pregunta del OP era dividirse en fragmentos de igual tamaño, si el vector no es un múltiplo del divisor, el último fragmento tendrá un tamaño diferente al fragmento. Para dividir en n-chunksque solía max <- length(d)%/%n. Utilicé esto con un vector de 31 cadenas y obtuve una lista de 3 vectores de 10 oraciones y uno de 1 oración.
salvu


36
simplified version...
n = 3
split(x, sort(x%%n))

Me gusta esto, ya que le da trozos que tienen el mismo tamaño posible (bueno para dividir tareas grandes, por ejemplo, para acomodar RAM limitada o ejecutar una tarea en varios subprocesos).
alexvpickering

3
Esto es útil, pero tenga en cuenta que esto solo funcionará en vectores numéricos.
Keith Hughitt

@KeithHughitt esto se puede resolver con factores y devolviendo los niveles como numéricos. O al menos así es como lo implementé.
drmariod 05 de

20

Pruebe la función 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
Esto no funciona para dividir la x, yo zdefinido en este comentario . En particular, clasifica los resultados, que pueden o no estar bien, dependiendo de la aplicación.
Kalin

Más bien, este comentario .
Kalin

18

Esto lo dividirá de manera diferente a lo que tienes, pero creo que sigue siendo una estructura de lista bastante buena:

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])
}

Lo que te dará lo siguiente, dependiendo de cómo quieras formatearlo:

> 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

Ejecutar un par de tiempos usando esta configuración:

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

Luego tenemos los siguientes resultados:

> 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 

EDITAR: Cambiar de as.factor () a as.character () en mi función lo hizo el doble de rápido.


13

Algunas variantes más de la pila ...

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

Tenga en cuenta que no necesita usar la factorfunción aquí, pero aún así desea que sortsu primer vector sea 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

O puede asignar índices de caracteres, viceversa los números en los ticks de la izquierda arriba:

> 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

O puede usar nombres de palabras simples almacenados en un vector. Tenga en cuenta que usar sortpara obtener valores consecutivos en xorden alfabético las etiquetas:

> 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

12

Usando la base 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

Y como ya se mencionó si desea índices ordenados, simplemente:

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

9

Puede combinar la división / corte, como lo sugiere mdsummer, con cuantil para crear grupos pares:

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

Esto da el mismo resultado para su ejemplo, pero no para variables sesgadas.


7

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

Quizás esto sea más claro, pero la misma idea:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

si quieres ordenarlo, arroja una especie a su alrededor


6

Necesitaba la misma función y he leído las soluciones anteriores, sin embargo, también necesitaba tener el fragmento desequilibrado para estar al final, es decir, si tengo 10 elementos para dividirlos en vectores de 3 cada uno, entonces mi resultado debería tener vectores con 3, 3,4 elementos respectivamente. Así que usé lo siguiente (dejé el código sin optimizar para facilitar la lectura, de lo contrario no es necesario tener muchas variables):

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

6

Aquí hay otra variante.

NOTA: con esta muestra, especificará el TAMAÑO DE CHUNK en el segundo parámetro

  1. todos los trozos son uniformes, excepto el último;
  2. el último, en el peor de los casos, será más pequeño, nunca más grande que el tamaño del fragmento.

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,|

4

Función simple para dividir un vector simplemente usando índices: no es necesario complicarlo demasiado

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

Si no le gusta split() y no le gusta matrix()(con sus NA colgantes), hay esto:

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

Al igual que split(), devuelve una lista, pero no pierde tiempo ni espacio con etiquetas, por lo que puede ser más eficiente.


2

Crédito a @Sebastian por esta función

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

2

Si no te gusta split()y no te molesta que los NA rellenen tu cola corta:

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

Las columnas de la matriz devuelta ([, 1: ncol]) son los droides que está buscando.


2

Necesito una función que tome el argumento de un data.table (entre comillas) y otro argumento que es el límite superior en el número de filas en los subconjuntos de ese data.table original. Esta función produce cualquier número de tablas de datos que el límite superior permita:

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

Esta función me da una serie de tablas de datos llamadas df_ [número] con la fila inicial de la tabla de datos original en el nombre. La última tabla de datos puede ser corta y estar llena de NA, por lo que debe volver a configurar los datos restantes. Este tipo de función es útil porque, por ejemplo, cierto software GIS tiene límites sobre cuántos pines de dirección puede importar. Por lo tanto, puede que no sea recomendable dividir data.tables en fragmentos más pequeños, pero es posible que no se pueda evitar.


2

Lo siento si esta respuesta llega tan tarde, pero tal vez pueda ser útil para otra persona. En realidad, hay una solución muy útil para este problema, explicada al final de? 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
¡Esto se romperá si hay un número desigual de valores en cada grupo!
Matifou

2

Otra posibilidad más es la splitIndicesfunción del paquete parallel:

library(parallel)
splitIndices(20, 3)

Da:

[[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

0

Wow, esta pregunta obtuvo más tracción de lo esperado.

Gracias por todas las ideas. Se me ocurrió esta solución:

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"

La clave es usar el parámetro seq (each = chunk.size) para que funcione. Usar seq_along actúa como rank (x) en mi solución anterior, pero en realidad es capaz de producir el resultado correcto con entradas duplicadas.


Para aquellos interesados ​​en que rep (seq_along (x), each = elements.per.chunk) podría ser demasiado agotador para la memoria: sí, lo hace. Puede probar una versión modificada de mi sugerencia anterior: chunk <- function (x, n) split (x, factor (seq_along (x) %% n))
Sebastian

0

Esto se divide en trozos de tamaño ⌊n / k⌋ + 1 o ⌊n / k⌋ y no utiliza el orden 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))
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.