Dividir um vetor em pedaços em R


227

Eu tenho que dividir um vetor em n pedaços de tamanho igual em R. Eu não consegui encontrar nenhuma função básica para fazer isso. Além disso, o Google não me levou a lugar algum. Então, aqui está o que eu criei, espero que ajude alguém em algum 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

Quaisquer comentários, sugestões ou melhorias são realmente bem-vindos e apreciados.

Cheers, Sebastian


5
Sim, não está claro se o que você obtém é a solução para "n pedaços de tamanho igual". Mas talvez isso também leve você até lá: x <- 1:10; n <- 3; fendidas (x, corte (X, n, etiquetas = FALSE))
mdsumner

a solução na pergunta e a solução no comentário anterior estão incorretas, pois podem não funcionar se o vetor tiver entradas repetidas. Tente o seguinte:> 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> pedaço (foo, 2) (dá resultado errado)> pedaço (foo, 3) (também errado)
mathheadinclouds

(continuando o comentário anterior) por quê? rank (x) não precisa ser um número inteiro> rank (c (1,1,2,3)) [1] 1,5 1,5 3,0 4,0 e é por isso que o método na pergunta falha. Este funciona (graças a Harlan abaixo)> chunk2 <- função (x, n) split (x, corte (seq_along (x), n, etiquetas = FALSE))
mathheadinclouds

2
> Split (foo, corte (foo, 3, rótulos = FALSE)) (também errado)
mathheadinclouds

1
Como sugere @mathheadinclouds, os dados de exemplo são um caso muito especial. Exemplos mais gerais seriam testes mais úteis e melhores. Por exemplo, x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)fornece exemplos com dados ausentes, valores repetidos, que ainda não foram classificados e estão em diferentes classes (número inteiro, caractere, fator).
Kalin

Respostas:


313

Uma linha que divide d em pedaços de tamanho 20:

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

Mais detalhes: Eu acho que tudo que você precisa é seq_along(), split()e 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
A pergunta pede npedaços de tamanho igual. Isso gera um número desconhecido de pedaços de tamanho n. Eu tive o mesmo problema e usei as soluções de @mathheadinclouds.
rrs 21/04

4
Como se pode ver pela saída de d1, essa resposta não divide d em grupos de tamanho igual (4 é obviamente mais curto). Assim, não responde à pergunta.
Calimo 23/01

9
@rrs: split (d, teto (seq_along (d) / (length (d) / n)))
gkcn

Eu sei que isso é bastante antigo, mas pode ser útil para quem tropeça aqui. Embora a pergunta do OP fosse dividir em pedaços de tamanho igual, se o vetor não for um múltiplo do divisor, o último chink terá um tamanho diferente do chunk. Para dividir n-chunkseu costumava max <- length(d)%/%n. Eu usei isso com um vetor de 31 strings e obtive uma lista de 3 vetores de 10 frases e um de 1 frase.
salvu


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

Eu gosto disso, pois fornece pedaços do mesmo tamanho possível (bom para dividir tarefas grandes, por exemplo, para acomodar RAM limitada ou para executar uma tarefa em vários threads).
alexvpickering

3
Isso é útil, mas lembre-se de que isso funcionará apenas em vetores numéricos.
precisa saber é o seguinte

@KeithHughitt, isso pode ser resolvido com fatores e retornando os níveis como numéricos. Ou pelo menos foi assim que eu o implementei.
Drmariod

20

Experimente a função 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
Isso não funciona para dividir o x, you zdefinido no este comentário . Em particular, ele classifica os resultados, o que pode ou não ser bom, dependendo do aplicativo.
Kalin

Pelo contrário, este comentário .
Kalin

18

Isso o dividirá de maneira diferente do que você tem, mas ainda é uma estrutura de lista bastante interessante:

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

O que fornecerá o seguinte, dependendo de como você deseja que ele seja formatado:

> 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

Executando algumas temporizações usando estas configurações:

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

Então temos os seguintes 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 

EDIT: Alterar de as.factor () para as.character () na minha função tornou duas vezes mais rápido.


13

Mais algumas variantes para a pilha ...

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

Observe que você não precisa usar a factorfunção aqui, mas ainda deseja usar o sortseu primeiro vetor 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

Ou você pode atribuir índices de caracteres, vice os números nos carrapatos da esquerda acima:

> 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

Ou você pode usar nomes de palavras simples armazenados em um vetor. Observe que usar sortpara obter valores consecutivos em xordem alfabética dos rótulos:

> 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 os R's básicos 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

E como já mencionado, se você deseja índices ordenados, basta:

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

Você pode combinar a divisão / corte, conforme sugerido pelo mdsummer, com o quantil para criar grupos pares:

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

Isso fornece o mesmo resultado para o seu exemplo, mas não para variáveis ​​assimétricas.


7

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

talvez isso seja mais claro, mas a mesma idéia:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

se você quiser, jogue uma espécie em torno dele


6

Eu precisava da mesma função e li as soluções anteriores, no entanto, também precisava que o pedaço desequilibrado estivesse no final, ou seja, se eu tiver 10 elementos para dividi-los em vetores de 3 cada, meu resultado deverá ter vetores com 3, 3,4 elementos, respectivamente. Então, usei o seguinte (deixei o código não otimizado para facilitar a leitura, caso contrário, não há necessidade de ter muitas variáveis):

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

Aqui está outra variante.

NOTA: com este exemplo, você está especificando CHUNK SIZE no segundo parâmetro

  1. todos os pedaços são uniformes, exceto o último;
  2. o último será, na pior das hipóteses, menor, nunca maior que o tamanho do pedaço.

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

Função simples para dividir um vetor usando simplesmente índices - não é necessário complicar demais

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

Se você não gosta split() e não gosta matrix()(com suas NAs pendentes), existe o seguinte:

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

Como split(), ele retorna uma lista, mas não perde tempo ou espaço com rótulos, por isso pode ter mais desempenho.


2

Crédito para @Sebastian por esta função

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

2

Se você não gosta split()e não se importa com as NAs acariciando sua cauda curta:

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

As colunas da matriz retornada ([, 1: ncol]) são os dróides que você está procurando.


2

Eu preciso de uma função que aceite o argumento de uma data.table (entre aspas) e outro argumento que seja o limite superior do número de linhas nos subconjuntos dessa data.table original. Essa função produz qualquer número de tabelas de dados que o limite 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)
    }

Essa função fornece uma série de tabelas de dados denominadas df_ [número] com a linha inicial da tabela de dados original no nome. A última tabela de dados pode ser curta e preenchida com NAs; portanto, você deve agrupá-las novamente para os dados restantes. Esse tipo de função é útil porque certos softwares GIS têm limites para quantos pinos de endereço você pode importar, por exemplo. Portanto, fatiar tabelas de dados em pedaços menores pode não ser recomendado, mas pode não ser evitável.


2

Desculpe se esta resposta chega tão tarde, mas talvez possa ser útil para outra pessoa. Na verdade, existe uma solução muito útil para esse problema, explicada no final da divisão.

> 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
isso será interrompido se houver um número desigual de valores em cada grupo!
Matifou 10/09/18

2

Ainda outra possibilidade é a splitIndicesfunção do pacote parallel:

library(parallel)
splitIndices(20, 3)

Dá:

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

Uau, essa pergunta teve mais tração do que o esperado.

Obrigado por todas as idéias. Eu vim com esta solução:

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"

A chave é usar o parâmetro seq (each = chunk.size) para fazê-lo funcionar. O uso de seq_along atua como rank (x) na minha solução anterior, mas na verdade é capaz de produzir o resultado correto com entradas duplicadas.


Para aqueles preocupados com o fato de que rep (seq_along (x), cada = elements.per.chunk) pode estar sobrecarregando demais a memória: sim, sim. Você poderia tentar uma versão modificada da minha sugestão anterior: pedaço <- função (x, n) split (x, fator (seq_along (x) %% n))
Sebastian

0

Isso se divide em pedaços de tamanho ⌊n / k⌋ + 1 ou ⌊n / k⌋ e não usa a classificação 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))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.