Teilen Sie einen Vektor in R in Stücke

227

Ich muss einen Vektor in n gleich große Blöcke in R aufteilen. Ich konnte keine Basisfunktion dafür finden. Auch Google hat mich nicht weitergebracht. Also hier ist, was ich mir ausgedacht habe, hoffentlich hilft es jemandem wo.

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

Kommentare, Vorschläge oder Verbesserungen sind sehr willkommen und werden geschätzt.

Prost, Sebastian

Sebastian
quelle
5
Ja, es ist sehr unklar, dass Sie die Lösung für "n gleich große Stücke" erhalten. Aber vielleicht bringt dich das auch dorthin: x <- 1:10; n <- 3; split (x, cut (x, n, label = FALSE))
mdsumner
Sowohl die Lösung in der Frage als auch die Lösung im vorhergehenden Kommentar sind insofern falsch, als sie möglicherweise nicht funktionieren, wenn der Vektor wiederholte Einträge hat. Versuchen Sie Folgendes:> 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) (gibt falsches Ergebnis)> chunk (foo, 3) (auch falsch)
mathheadinclouds
(Fortsetzung des vorhergehenden Kommentars) Warum? Rang (x) muss keine Ganzzahl sein> Rang (c (1,1,2,3)) [1] 1,5 1,5 3,0 4,0, daher schlägt die Methode in der Frage fehl. Dieser funktioniert (dank Harlan unten)> chunk2 <- Funktion (x, n) split (x, cut (seq_along (x), n, label = FALSE))
mathheadinclouds
2
> split (foo, cut (foo, 3, label = FALSE)) (auch falsch)
mathheadinclouds
1
Wie @mathheadinclouds vorschlägt, sind die Beispieldaten ein ganz besonderer Fall. Beispiele, die allgemeiner sind, wären nützlichere und bessere Tests. ZB x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)gibt Beispiele mit fehlenden Daten, wiederholten Werten, die noch nicht sortiert sind und in verschiedenen Klassen (Ganzzahl, Zeichen, Faktor) sind.
Kalin

Antworten:

313

Ein Einzeiler, der d in Stücke der Größe 20 aufteilt:

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

Weitere Details: Ich denke , alles was Sie brauchen seq_along(), split()und 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
quelle
34
Die Frage fragt nach ngleich großen Stücken. Dadurch erhalten Sie eine unbekannte Anzahl von Blöcken n. Ich hatte das gleiche Problem und verwendete die Lösungen von @mathheadinclouds.
rrs
4
Wie man aus der Ausgabe von d1 sehen kann, teilt diese Antwort d nicht in gleich große Gruppen auf (4 ist offensichtlich kürzer). Somit beantwortet es die Frage nicht.
Calimo
9
@rrs: split (d, Decke (seq_along (d) / (Länge (d) / n)))
gkcn
Ich weiß, dass dies ziemlich alt ist, aber es kann für diejenigen hilfreich sein, die hier stolpern. Obwohl die Frage des OP darin bestand, sich in gleich große Blöcke aufzuteilen, hat der letzte Riss eine andere Größe als der Teil, wenn der Vektor kein Vielfaches des Divisors ist. Zum Aufteilen habe n-chunksich verwendet max <- length(d)%/%n. Ich habe dies mit einem Vektor von 31 Zeichenfolgen verwendet und eine Liste von 3 Vektoren mit 10 Sätzen und einem von 1 Satz erhalten.
Salvu
75
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 
Mathheadinclouds
quelle
35
simplified version...
n = 3
split(x, sort(x%%n))
zhan2383
quelle
Ich mag dies, weil es Ihnen Blöcke gibt, die so gleich groß wie möglich sind (gut zum Aufteilen großer Aufgaben, z. B. um begrenzten RAM unterzubringen oder um eine Aufgabe über mehrere Threads auszuführen).
Alexvpickering
3
Dies ist nützlich, aber denken Sie daran, dass dies nur für numerische Vektoren funktioniert.
Keith Hughitt
@KeithHughitt Dies kann mit Faktoren gelöst werden und die Ebenen als numerisch zurückgeben. Zumindest habe ich das so umgesetzt.
Drmariod
20

Probieren Sie die Funktion ggplot2 aus 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
quelle
2
Dies gilt nicht für die Spaltung aufzuarbeiten die x, yoder zin definiert diesen Kommentar . Insbesondere werden die Ergebnisse sortiert, die je nach Anwendung in Ordnung sein können oder nicht.
Kalin
Eher dieser Kommentar .
Kalin
18

Dies wird es anders aufteilen als das, was Sie haben, aber es ist immer noch eine nette Listenstruktur, denke ich:

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

Je nachdem, wie Sie es formatieren möchten, erhalten Sie Folgendes:

> 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

Ausführen einiger Timings mit diesen Einstellungen:

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

Dann haben wir folgende Ergebnisse:

> 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 

BEARBEITEN: Der Wechsel von as.factor () zu as.character () in meiner Funktion hat es doppelt so schnell gemacht.

Tony Breyal
quelle
13

Noch ein paar Varianten zum Stapel ...

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

Beachten Sie, dass Sie die factorFunktion hier nicht verwenden müssen , aber dennoch sortIhren ersten Vektor verwenden möchten 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

Oder Sie können Zeichenindizes zuweisen, indem Sie die Zahlen in den linken Häkchen oben angeben:

> 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

Oder Sie können in einem Vektor gespeicherte Klarwortnamen verwenden. Beachten Sie, dass die Beschriftung verwendet wird sort, um aufeinanderfolgende Werte in xalphabetischer Reihenfolge abzurufen:

> 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
quelle
12

Verwenden von Basis-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

Und wie bereits erwähnt, wenn Sie sortierte Indizes wünschen, einfach:

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
quelle
9

Sie können den von mdsummer vorgeschlagenen Split / Cut mit dem Quantil kombinieren, um gleichmäßige Gruppen zu erstellen:

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

Dies ergibt das gleiche Ergebnis für Ihr Beispiel, jedoch nicht für verzerrte Variablen.

SiggyF
quelle
7

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

Vielleicht ist das klarer, aber die gleiche Idee:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

Wenn Sie möchten, dass es bestellt wird, werfen Sie eine Sorte darum

frankc
quelle
6

Ich brauchte die gleiche Funktion und habe die vorherigen Lösungen gelesen, aber ich musste auch den unausgeglichenen Block am Ende haben, dh wenn ich 10 Elemente habe, um sie in Vektoren von jeweils 3 aufzuteilen, sollte mein Ergebnis Vektoren mit 3 haben. Jeweils 3,4 Elemente. Also habe ich Folgendes verwendet (ich habe den Code für die Lesbarkeit nicht optimiert, sonst müssen nicht viele Variablen vorhanden sein):

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.
quelle
6

Hier ist eine andere Variante.

HINWEIS: In diesem Beispiel geben Sie die CHUNK SIZE im zweiten Parameter an

  1. alle Stücke sind bis auf das letzte einheitlich;
  2. Der letzte wird im schlimmsten Fall kleiner sein, niemals größer als die Blockgröße.

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
quelle
4

Einfache Funktion zum Teilen eines Vektors durch einfaches Verwenden von Indizes - dies muss nicht zu kompliziert sein

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
quelle
3

Wenn Sie nicht mögen split() und Sie nicht mögen matrix()(mit seinen baumelnden NAs), gibt es Folgendes:

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

Wie split(), es gibt eine Liste, aber es hat keine Zeit oder Raum mit Etiketten verschwenden, so dass es mehr performant sein kann.

verbamour
quelle
2

Dank an @Sebastian für diese Funktion

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

Wenn Sie nicht mögen split()und es Ihnen nichts ausmacht, dass NAs Ihren kurzen Schwanz auspolstern:

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

Die Spalten der zurückgegebenen Matrix ([, 1: ncol]) sind die Droiden, nach denen Sie suchen.

verbamour
quelle
2

Ich benötige eine Funktion, die das Argument einer data.table (in Anführungszeichen) und ein anderes Argument verwendet, das die Obergrenze für die Anzahl der Zeilen in den Teilmengen dieser ursprünglichen data.table darstellt. Diese Funktion erzeugt eine beliebige Anzahl von data.tables, die die Obergrenze zulässt:

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

Diese Funktion gibt mir eine Reihe von data.tables mit dem Namen df_ [number] mit der Startzeile aus der ursprünglichen data.table im Namen. Die letzte Datentabelle kann kurz und mit NAs gefüllt sein, sodass Sie diese auf die verbleibenden Daten zurücksetzen müssen. Diese Art von Funktion ist nützlich, da bestimmte GIS-Software beispielsweise die Anzahl der zu importierenden Adresspins begrenzt. Das Aufteilen von data.tables in kleinere Blöcke wird möglicherweise nicht empfohlen, ist jedoch möglicherweise nicht vermeidbar.

rferrisx
quelle
2

Tut mir leid, wenn diese Antwort so spät kommt, aber vielleicht kann sie für jemand anderen nützlich sein. Tatsächlich gibt es eine sehr nützliche Lösung für dieses Problem, die am Ende von? Split erläutert wird.

> 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
quelle
3
Dies wird unterbrochen, wenn in jeder Gruppe eine ungleiche Anzahl von Werten vorhanden ist!
Matifou
2

Eine weitere Möglichkeit ist die splitIndicesFunktion aus dem Paket parallel:

library(parallel)
splitIndices(20, 3)

Gibt:

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

Wow, diese Frage hat mehr Traktion als erwartet.

Danke für all die Ideen. Ich habe mir diese Lösung ausgedacht:

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"

Der Schlüssel ist, den Parameter seq (each = chunk.size) zu verwenden, damit es funktioniert. Die Verwendung von seq_along verhält sich in meiner vorherigen Lösung wie Rang (x), kann jedoch mit doppelten Einträgen das richtige Ergebnis erzielen.

Sebastian
quelle
Für die Betroffenen könnte rep (seq_along (x), each = elements.per.chunk) den Speicher zu stark belasten: Ja, das tut es. Sie könnten eine modifizierte Version meines vorherigen Vorschlags ausprobieren: Chunk <- Funktion (x, n) split (x, Faktor (seq_along (x) %% n))
Sebastian
0

Dies teilt sich in Blöcke der Größe ⌊n / k⌋ + 1 oder ⌊n / k⌋ auf und verwendet nicht die Sortierung 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
quelle