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

Sebastian
fuente
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
Harlan
fuente
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
75
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 
Mathheadinclouds
fuente
36
simplified version...
n = 3
split(x, sort(x%%n))
zhan2383
fuente
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
Scott Worland
fuente
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.

revs Tony Breyal
fuente
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
Richard Herron
fuente
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
FXQuantTrader
fuente
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.

SiggyF
fuente
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

frankc
fuente
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
Zak D
fuente
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,|
eAndy
fuente
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])
    }))
}
Philip Michaelsen
fuente
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.

verbamour
fuente
2

Crédito a @Sebastian por esta función

chunk <- function(x,y){
         split(x, factor(sort(rank(row.names(x))%%y)))
         }
Comunidad
fuente
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.

verbamour
fuente
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.

rferrisx
fuente
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
Laura Paladini
fuente
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
Matifou
fuente
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.

Sebastian
fuente
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))
Valentas
fuente