Dividi un vettore in blocchi in R


227

Devo dividere un vettore in n pezzi di uguale dimensione in R. Non sono riuscito a trovare alcuna funzione di base per farlo. Inoltre Google non mi ha portato da nessuna parte. Quindi ecco cosa mi è venuto in mente, spero che aiuti qualcuno da qualche parte.

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

Eventuali commenti, suggerimenti o miglioramenti sono davvero benvenuti e apprezzati.

Saluti, Sebastian


5
Sì, non è molto chiaro che ciò che ottieni è la soluzione a "n pezzi di uguale dimensione". Ma forse questo ti porta anche lì: x <- 1:10; n <- 3; split (x, cut (x, n, labels = FALSE))
mdsumner

sia la soluzione nella domanda, sia la soluzione nel commento precedente sono errate, in quanto potrebbero non funzionare se il vettore ha ripetute voci. Prova questo:> foo <- c (rep (1, 12), rep (2,3), rep (3,3)) [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 3 3 3> chunk (foo, 2) (dà un risultato sbagliato)> chunk (foo, 3) (anche sbagliato)
mathheadinclouds

(continuando il commento precedente) perché? rank (x) non ha bisogno di essere un numero intero> rank (c (1,1,2,3)) [1] 1.5 1.5 3.0 4.0 quindi è per questo che il metodo nella domanda fallisce. questo funziona (grazie a Harlan in basso)> chunk2 <- funzione (x, n) split (x, cut (seq_along (x), n, labels = FALSE))
mathheadinclouds

2
> split (foo, cut (foo, 3, labels = FALSE)) (anche sbagliato)
mathheadinclouds

1
Come suggerisce @mathheadinclouds, i dati di esempio sono un caso molto speciale. Esempi più generali sarebbero test più utili e migliori. Ad esempio, x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)fornisce esempi con dati mancanti, valori ripetuti, che non sono già ordinati e che appartengono a classi diverse (numero intero, carattere, fattore).
Kalin,

Risposte:


313

Un pezzo unico che divide d in pezzi di taglia 20:

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

Maggiori dettagli: Penso che tutto quello che serve è 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
La domanda richiede npezzi di uguale dimensione. Questo ti dà un numero sconosciuto di pezzi di dimensioni n. Ho avuto lo stesso problema e ho usato le soluzioni di @mathheadinclouds.
RRS

4
Come si può vedere dall'output di d1, questa risposta non divide d in gruppi di uguali dimensioni (4 è ovviamente più breve). Quindi non risponde alla domanda.
Calimo,

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

So che questo è piuttosto vecchio, ma potrebbe essere di aiuto a coloro che inciampano qui. Sebbene la domanda del PO fosse quella di dividere in blocchi di uguali dimensioni, se il vettore non è un multiplo del divisore, l'ultimo chink avrà una dimensione diversa da quella del chunk. Per dividere in n-chunksho usato max <- length(d)%/%n. L'ho usato con un vettore di 31 stringhe e ho ottenuto un elenco di 3 vettori di 10 frasi e uno di 1 frase.
salvu


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

Mi piace questo perché ti dà blocchi che sono le stesse dimensioni possibili (buono per dividere compiti di grandi dimensioni, ad esempio per ospitare RAM limitata o per eseguire un'attività su più thread).
alexvpickering,

3
Questo è utile, ma tieni presente che funzionerà solo su vettori numerici.
Keith Hughitt,

@KeithHughitt questo può essere risolto con fattori e restituendo i livelli come numerici. O almeno è così che l'ho implementato.
drmariod,

20

Prova la funzione 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
Questo non funziona per suddividere la x, yo zdefiniti in questo commento . In particolare, ordina i risultati, che possono o non possono andare bene, a seconda dell'applicazione.
Kalin,

Piuttosto, questo commento .
Kalin,

18

Questo lo dividerà in modo diverso da quello che hai, ma penso che sia ancora piuttosto una bella struttura di elenco:

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

Che ti darà quanto segue, a seconda di come lo vuoi formattare:

> 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

Esecuzione di un paio di tempi utilizzando queste impostazioni:

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

Quindi abbiamo i seguenti risultati:

> 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: Il passaggio da as.factor () ad as.character () nella mia funzione lo ha reso due volte più veloce.


13

Qualche altra variante alla pila ...

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

Si noti che non è necessario utilizzare la factorfunzione qui, ma si desidera ancora utilizzare sortil primo vettore 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

In alternativa, puoi assegnare indici di caratteri, viceversa i numeri con i segni di spunta in alto a sinistra:

> 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

Oppure puoi usare nomi di parole semplici memorizzati in un vettore. Si noti che l'utilizzo sortper ottenere valori consecutivi in xordine alfabetico delle etichette:

> 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

Utilizzando le R di base 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 come già accennato se vuoi indici ordinati, semplicemente:

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

È possibile combinare la divisione / taglio, come suggerito da mdsummer, con quantile per creare gruppi pari:

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

Questo dà lo stesso risultato per il tuo esempio, ma non per le variabili distorte.


7

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

forse questo è più chiaro, ma la stessa idea:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

se lo vuoi ordinato, lancia una specie attorno ad esso


6

Avevo bisogno della stessa funzione e ho letto le soluzioni precedenti, tuttavia avevo anche bisogno di avere il pezzo sbilanciato alla fine, cioè se avessi 10 elementi per dividerli in vettori di 3 ciascuno, allora il mio risultato dovrebbe avere vettori con 3, 3,4 elementi rispettivamente. Quindi ho usato quanto segue (ho lasciato il codice non ottimizzato per la leggibilità, altrimenti non ho bisogno di avere molte variabili):

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

Ecco un'altra variante.

NOTA: con questo esempio stai specificando la CHUNK SIZE nel secondo parametro

  1. tutti i pezzi sono uniformi, tranne l'ultimo;
  2. l'ultimo sarà nel peggiore dei casi più piccolo, mai più grande della dimensione del pezzo.

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

Semplice funzione per dividere un vettore semplicemente usando gli indici - non c'è bisogno di complicare troppo questo

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 non ti piace split() e non ti piace matrix()(con i suoi NA pendenti), c'è questo:

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

Ad esempio split(), restituisce un elenco, ma non perde tempo o spazio con le etichette, quindi potrebbe essere più performante.


2

Ringraziamo @Sebastian per questa funzione

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

2

Se non ti piace split()e non ti dispiace che gli NA riempiano la tua coda 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))} }

Le colonne della matrice restituita ([, 1: ncol]) sono i droidi che stai cercando.


2

Ho bisogno di una funzione che accetta l'argomento di un data.table (tra virgolette) e un altro argomento che è il limite superiore sul numero di righe nei sottoinsiemi di quel data.table originale. Questa funzione produce qualsiasi numero di dati.tabili consentiti dal limite superiore per:

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

Questa funzione mi dà una serie di data.tables denominati df_ [numero] con la riga iniziale dalla data.table originale nel nome. L'ultima data.table può essere breve e piena di NA, quindi è necessario reimpostarla su tutti i dati rimasti. Questo tipo di funzione è utile perché alcuni software GIS hanno dei limiti su quanti pin di indirizzo è possibile importare, ad esempio. Pertanto, tagliare i data.tables in blocchi più piccoli potrebbe non essere raccomandato, ma potrebbe non essere evitabile.


2

Scusate se questa risposta arriva così tardi, ma forse può essere utile per qualcun altro. In realtà esiste una soluzione molto utile a questo problema, spiegata alla fine della divisione.

> 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
questo si interromperà se ci sarà un numero ineguale di valori in ciascun gruppo!
Matifou,

2

Ancora un'altra possibilità è la splitIndicesfunzione dal pacchetto 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

Caspita, questa domanda ha ottenuto una trazione maggiore del previsto.

Grazie per tutte le idee. Ho trovato questa soluzione:

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 chiave è usare il parametro seq (each = chunk.size) in modo da farlo funzionare. L'uso di seq_along si comporta come rank (x) nella mia precedente soluzione, ma in realtà è in grado di produrre il risultato corretto con voci duplicate.


Per gli interessati che rep (seq_along (x), each = elements.per.chunk) potrebbe essere troppo stressante per la memoria: sì, sì. Potresti provare una versione modificata del mio precedente suggerimento: chunk <- function (x, n) split (x, factor (seq_along (x) %% n))
Sebastian

0

Questo si divide in blocchi di dimensioni ⌊n / k⌋ + 1 o ⌊n / k⌋ e non usa l'ordinamento 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))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.