Podziel wektor na części w R.

227

Muszę podzielić wektor na n części o jednakowej wielkości w R. Nie mogłem znaleźć żadnej funkcji bazowej, aby to zrobić. Również Google nigdzie mnie nie doprowadził. Oto, co wymyśliłem, mam nadzieję, że pomoże komuś gdzieś.

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

Wszelkie komentarze, sugestie lub ulepszenia są bardzo mile widziane i doceniane.

Na zdrowie, Sebastian

Sebastian
źródło
5
Tak, jest bardzo niejasne, że otrzymujesz rozwiązanie dla „n kawałków równej wielkości”. Ale może cię to też tam prowadzi: x <- 1:10; n <- 3; split (x, cut (x, n, labels = FALSE))
mdsumner
zarówno rozwiązanie w pytaniu, jak i rozwiązanie w poprzednim komentarzu są niepoprawne, ponieważ mogą nie działać, jeśli wektor ma powtarzające się wpisy. Spróbuj tego:> 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> porcja (foo, 2) (daje zły wynik)> porcja (foo, 3) (również źle)
mathheadinclouds
(ciąg dalszy poprzedzający komentarz) dlaczego? rank (x) nie musi być liczbą całkowitą> rank (c (1,1,2,3)) [1] 1,5 1,5 3,0 4,0, dlatego metoda w pytaniu zawodzi. ten działa (dzięki Harlan poniżej)> chunk2 <- funkcja (x, n) split (x, cut (seq_along (x), n, labels = FALSE))
mathheadinclouds
2
> split (foo, cut (foo, 3, labels = FALSE)) (również źle)
mathheadinclouds
1
Jak sugeruje @mathheadinclouds, przykładowe dane są bardzo szczególnym przypadkiem. Bardziej ogólne przykłady byłyby bardziej przydatne i lepsze testy. Np. x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)Podaje przykłady z brakującymi danymi, powtarzanymi wartościami, które nie są jeszcze posortowane i należą do różnych klas (liczba całkowita, znak, czynnik).
Kalin

Odpowiedzi:

313

Jednoliniowy podział d na kawałki o rozmiarze 20:

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

Więcej szczegółów: Myślę, że wszystko, co potrzebne jest seq_along(), split()i 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
źródło
34
Pytanie dotyczy nkawałków o równej wielkości. To daje nieznaną liczbę kawałków wielkości n. Miałem ten sam problem i korzystałem z rozwiązań z @mathheadinclouds.
2014 r. O
4
Jak widać z wyjścia d1, ta odpowiedź nie dzieli d na grupy o jednakowej wielkości (4 jest oczywiście krótszy). Zatem nie odpowiada na pytanie.
Calimo
9
@rrs: split (d, ceiling (seq_along (d) / (length (d) / n)))
gkcn
Wiem, że to dość stare, ale może być pomocne dla tych, którzy się tu potykają. Chociaż kwestią PO było podzielenie na kawałki o równej wielkości, jeśli wektor nie będzie wielokrotnością dzielnika, ostatni otwór będzie miał inny rozmiar niż kawałek. Do podziału n-chunksużyłem max <- length(d)%/%n. Użyłem tego z wektorem 31 ciągów i uzyskałem listę 3 wektorów po 10 zdań i jednego z 1 zdania.
salvu
75
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 
mathheadinclouds
źródło
36
simplified version...
n = 3
split(x, sort(x%%n))
zhan2383
źródło
Podoba mi się to, ponieważ daje ci kawałki możliwie jak największej wielkości (dobre do dzielenia dużych zadań, np. W celu dostosowania ograniczonej pamięci RAM lub uruchamiania zadania w wielu wątkach).
alexvpickering
3
Jest to przydatne, ale należy pamiętać, że będzie działać tylko na wektorach numerycznych.
Keith Hughitt
@KeithHughitt można to rozwiązać za pomocą czynników i zwracając poziomy liczbowo. A przynajmniej tak to zaimplementowałem.
drmariod
20

Wypróbuj funkcję 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
źródło
2
To nie działa na rozszczepienie w górę x, ylub zzdefiniowane w niniejszym komentarzu . W szczególności sortuje wyniki, które mogą, ale nie muszą być poprawne, w zależności od aplikacji.
Kalin
Raczej ten komentarz .
Kalin
18

To podzieli to inaczej niż to, co masz, ale myślę, że nadal jest całkiem ładną strukturą listy:

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

Co da ci następujące, w zależności od tego, jak chcesz to sformatować:

> 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

Uruchamianie kilku taktów za pomocą tych ustawień:

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

Następnie mamy następujące wyniki:

> 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 

EDYCJA: Zmiana z as.factor () na as.character () w mojej funkcji sprawiła, że ​​stało się to dwa razy szybciej.

Tony Breyal
źródło
13

Jeszcze kilka wariantów stosu ...

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

Pamiętaj, że nie musisz factortutaj używać tej funkcji, ale nadal chcesz sortwłączyć 1 2 3 10/ wyłączyć swój pierwszy wektor :

> 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

Lub możesz przypisać indeksy znaków, vice liczby w lewych paskach powyżej:

> 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

Lub możesz użyć prostych nazw przechowywanych w wektorze. Zauważ, że używając, sortaby uzyskać kolejne wartości w xkolejności alfabetycznej, etykiety:

> 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
źródło
12

Używając podstawy 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

Jak już wspomniano, jeśli chcesz posortować indeksy, po prostu:

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
źródło
9

Możesz połączyć podział / cięcie, jak sugeruje mdsummer, z kwantylem, aby stworzyć parzyste grupy:

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

To daje taki sam wynik dla twojego przykładu, ale nie dla zmiennych skośnych.

SiggyF
źródło
7

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

być może jest to bardziej jasne, ale ten sam pomysł:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

jeśli chcesz to zamówić, rzuć coś wokół

frankc
źródło
6

Potrzebowałem tej samej funkcji i zapoznałem się z poprzednimi rozwiązaniami, ale musiałem też mieć niezbilansowany fragment na końcu, tj. Jeśli mam 10 elementów do podzielenia ich na wektory po 3, to mój wynik powinien mieć wektory z 3, Odpowiednio 3,4 elementy. Więc użyłem następującego (zostawiłem kod niezoptymalizowany pod kątem czytelności, w przeciwnym razie nie trzeba mieć wielu zmiennych):

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
źródło
6

Oto inny wariant.

UWAGA: w tej próbce określasz ROZMIAR CZASU w drugim parametrze

  1. wszystkie kawałki są jednolite, z wyjątkiem ostatniego;
  2. ten ostatni będzie w najgorszym przypadku mniejszy, nigdy większy niż wielkość porcji.

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
źródło
4

Prosta funkcja dzielenia wektora za pomocą indeksów - nie trzeba tego nadmiernie komplikować

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
źródło
3

Jeśli nie lubisz split() i nie lubisz matrix()(z wiszącymi NA), jest to:

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

Jak split()to zwraca listę, ale nie tracić czasu i przestrzeni z etykietami, więc może być bardziej wydajnych.

verbamour
źródło
2

Podziękowania dla @Sebastian za tę funkcję

chunk <- function(x,y){
         split(x, factor(sort(rank(row.names(x))%%y)))
         }
Społeczność
źródło
2

Jeśli ci się nie podoba split()i nie masz nic przeciwko NA wystającym z krótkiego ogona:

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

Kolumny zwróconej macierzy ([, 1: ncol]) to droidy, których szukasz.

verbamour
źródło
2

Potrzebuję funkcji, która pobiera argument tabeli data.tab (w cudzysłowach) i inny argument, który stanowi górną granicę liczby wierszy w podzestawach oryginalnej tabeli data.tab. Ta funkcja generuje dowolną liczbę danych. Tabele, które pozwala na górny limit:

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

Ta funkcja daje mi serię tabel data.t o nazwie df_ [liczba] z początkowym wierszem z oryginalnej tabeli data.tame w nazwie. Ostatnia tabela data.tab może być krótka i wypełniona NA, więc musisz podgrupować to z powrotem do pozostałych danych. Ten typ funkcji jest przydatny, ponieważ niektóre programy GIS mają na przykład ograniczenia dotyczące liczby pinów adresów, które można zaimportować. Dlatego dzielenie tabel danych na mniejsze części może nie być zalecane, ale nie można tego uniknąć.

rferrisx
źródło
2

Przepraszam, jeśli ta odpowiedź przychodzi tak późno, ale może być przydatna dla kogoś innego. W rzeczywistości istnieje bardzo przydatne rozwiązanie tego problemu, wyjaśnione na końcu „podziału”.

> 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
źródło
3
to się zepsuje, jeśli w każdej grupie będzie nierówna liczba wartości!
Matifou,
2

Jeszcze inną możliwością jest splitIndicesfunkcja z pakietu parallel:

library(parallel)
splitIndices(20, 3)

Daje:

[[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
źródło
0

Wow, to pytanie zyskało większą przyczepność niż się spodziewano.

Dzięki za wszystkie pomysły. Wymyśliłem to rozwiązanie:

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"

Kluczem jest użycie parametru seq (each = chunk.size), aby działał. Używanie seq_along działa w moim poprzednim rozwiązaniu jak ranga (x), ale w rzeczywistości jest w stanie uzyskać poprawny wynik ze zduplikowanymi wpisami.

Sebastian
źródło
Dla zainteresowanych, że rep (seq_along (x), each = elements.per.chunk) może nadmiernie obciążać pamięć: tak. Możesz wypróbować zmodyfikowaną wersję mojej poprzedniej sugestii: porcja <- funkcja (x, n) podział (x, współczynnik (seq_along (x) %% n))
Sebastian,
0

Dzieli się to na kawałki o wielkości ⌊n / k⌋ + 1 lub ⌊n / k⌋ i nie używa sortowania 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
źródło