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

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

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

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

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

verbamour
fonte
2

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

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

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

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

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