Membagi vektor menjadi potongan-potongan di R

227

Saya harus membagi vektor menjadi n potongan dengan ukuran yang sama dalam R. Saya tidak dapat menemukan fungsi dasar untuk melakukan itu. Google juga tidak membantu saya. Jadi, inilah yang saya hasilkan, semoga membantu seseorang di suatu tempat.

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

Setiap komentar, saran atau perbaikan sangat disambut dan dihargai.

Salam, Sebastian

Sebastian
sumber
5
Ya, sangat tidak jelas bahwa apa yang Anda dapatkan adalah solusi untuk "n potongan berukuran sama". Tapi mungkin ini membuat Anda di sana juga: x <- 1:10; n <- 3; split (x, cut (x, n, label = FALSE))
mdsumner
baik solusi dalam pertanyaan, dan solusi dalam komentar sebelumnya tidak benar, karena mereka mungkin tidak berfungsi, jika vektor memiliki entri berulang. Coba ini:> 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 1 1 1 2 2 2 3 3 3> chunk (foo, 2) (memberikan hasil yang salah)> chunk (foo, 3) (juga salah)
mathheadinclouds
(melanjutkan komentar sebelumnya) mengapa? rank (x) tidak perlu bilangan bulat> rank (c (1,1,2,3)) [1] 1,5 1,5 3,0 4,0 jadi itu sebabnya metode dalam pertanyaan gagal. ini berfungsi (terima kasih kepada Harlan di bawah)> chunk2 <- function (x, n) split (x, cut (seq_along (x), n, label = FALSE))
mathheadinclouds
2
> split (foo, cut (foo, 3, label = FALSE)) (juga salah)
mathheadinclouds
1
Seperti @mathheadinclouds menyarankan, contoh data adalah kasus yang sangat istimewa. Contoh yang lebih umum akan lebih bermanfaat dan tes lebih baik. Misalnya x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)memberikan contoh dengan data yang hilang, nilai berulang, yang belum diurutkan, dan berada di kelas yang berbeda (integer, karakter, faktor).
Kalin

Jawaban:

313

Pemisah satu lapis d menjadi potongan ukuran 20:

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

Lebih detail: Saya pikir yang Anda butuhkan hanyalah seq_along(), split()dan 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
sumber
34
Pertanyaannya meminta npotongan dengan ukuran yang sama. Ini memberi Anda jumlah potongan yang tidak diketahui n. Saya memiliki masalah yang sama dan menggunakan solusi dari @mathheadinclouds.
rrs
4
Seperti yang dapat dilihat dari output d1, jawaban ini tidak membagi d menjadi kelompok-kelompok dengan ukuran yang sama (4 jelas lebih pendek). Karena itu tidak menjawab pertanyaan.
Calimo
9
@ rrs: split (d, plafon (seq_along (d) / (panjang (d) / n)))
gkcn
Saya tahu ini cukup tua tetapi mungkin bisa membantu mereka yang tersandung di sini. Meskipun pertanyaan OP adalah untuk memecah menjadi ukuran yang sama, jika vektor tidak menjadi kelipatan dari pembagi, celah terakhir akan memiliki ukuran yang berbeda dari potongan. Untuk dibagi menjadi n-chunkssaya gunakan max <- length(d)%/%n. Saya menggunakan ini dengan vektor 31 string dan memperoleh daftar 3 vektor dari 10 kalimat dan satu dari 1 kalimat.
salvu
75
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 
matematika seperti awan
sumber
36
simplified version...
n = 3
split(x, sort(x%%n))
zhan2383
sumber
Saya suka ini karena memberi Anda potongan yang sama besarnya mungkin (baik untuk membagi tugas besar misalnya untuk mengakomodasi RAM terbatas atau untuk menjalankan tugas di beberapa utas).
alexvpickering
3
Ini berguna, tetapi perlu diingat ini hanya akan bekerja pada vektor numerik.
Keith Hughitt
@KeithHughitt ini dapat diselesaikan dengan faktor dan mengembalikan level sebagai numerik. Atau setidaknya begitulah cara saya mengimplementasikannya.
drmariod
20

Cobalah fungsi 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
sumber
2
Ini tidak bekerja untuk membelah up x, yatau zdidefinisikan dalam komentar ini . Secara khusus, ini mengurutkan hasil, yang mungkin atau mungkin tidak apa-apa, tergantung pada aplikasi.
Kalin
Sebaliknya, komentar ini .
Kalin
18

Ini akan membaginya secara berbeda dengan apa yang Anda miliki, tetapi saya pikir struktur daftar masih cukup bagus:

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

Yang akan memberi Anda yang berikut, tergantung pada bagaimana Anda ingin diformat:

> 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

Menjalankan beberapa pengaturan waktu menggunakan pengaturan ini:

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

Maka kami memiliki hasil sebagai berikut:

> 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: Mengubah dari as.factor () ke as.character () dalam fungsi saya membuatnya dua kali lebih cepat.

Tony Breyal
sumber
13

Beberapa varian lagi untuk tumpukan ...

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

Catatan, bahwa Anda tidak perlu menggunakan factorfungsi di sini, tetapi Anda tetap ingin sorto / w vektor pertama Anda adalah 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

Atau Anda dapat menetapkan indeks karakter, dan sebaliknya angka dalam kutu kiri di atas:

> 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

Atau Anda dapat menggunakan nama kata biasa yang disimpan dalam vektor. Perhatikan bahwa menggunakan sortuntuk mendapatkan nilai berurutan dalam xmengabjadkan label:

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

Menggunakan 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

Dan seperti yang telah disebutkan jika Anda ingin indeks yang diurutkan, cukup:

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

Anda bisa menggabungkan split / cut, seperti yang disarankan oleh mdsummer, dengan quantile untuk membuat grup genap:

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

Ini memberikan hasil yang sama untuk contoh Anda, tetapi tidak untuk variabel miring.

SiggyF
sumber
7

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

mungkin ini lebih jelas, tetapi gagasan yang sama:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

Jika Anda ingin memesannya, letakkan semacam itu di sekitarnya

frankc
sumber
6

Saya memerlukan fungsi yang sama dan telah membaca solusi sebelumnya, namun saya juga perlu memiliki potongan yang tidak seimbang pada akhirnya yaitu jika saya memiliki 10 elemen untuk membaginya menjadi vektor masing-masing 3, maka hasil saya harus memiliki vektor dengan 3, Masing-masing 3,4 elemen. Jadi saya menggunakan yang berikut (saya meninggalkan kode tidak dioptimalkan untuk dibaca, jika tidak, tidak perlu memiliki banyak variabel):

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
sumber
6

Berikut varian lainnya.

CATATAN: dengan sampel ini Anda menentukan UKURAN CHUNK pada parameter kedua

  1. semua potongan seragam, kecuali yang terakhir;
  2. yang paling buruk akan lebih kecil, tidak pernah lebih besar dari ukuran chunk.

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

Fungsi sederhana untuk memisahkan vektor hanya dengan menggunakan indeks - tidak perlu terlalu rumit

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

Jika Anda tidak suka split() dan tidak suka matrix()(dengan NAS-nya yang menggantung), ini dia:

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

Seperti split(), ia mengembalikan daftar, tetapi tidak membuang-buang waktu atau ruang dengan label, jadi itu mungkin lebih berkinerja.

kata kerja
sumber
2

Kredit ke @Sebastian untuk fungsi ini

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

Jika Anda tidak suka split()dan Anda tidak keberatan NA melapisi ekor pendek Anda:

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

Kolom dari matriks yang dikembalikan ([, 1: ncol]) adalah droid yang Anda cari.

kata kerja
sumber
2

Saya membutuhkan fungsi yang mengambil argumen dari data.table (dalam tanda kutip) dan argumen lain yang merupakan batas atas pada jumlah baris dalam subset dari data.table yang asli. Fungsi ini menghasilkan berapa pun jumlah data. Tabel yang batas atas memungkinkan untuk:

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

Fungsi ini memberi saya serangkaian data.tabel bernama df_ [angka] dengan baris awal dari data asli.tabel dalam nama. Tabel data terakhir bisa pendek dan diisi dengan NAS sehingga Anda harus mengelompokkannya kembali ke data apa pun yang tersisa. Jenis fungsi ini berguna karena perangkat lunak SIG tertentu memiliki batasan pada berapa banyak pin alamat yang dapat Anda impor, misalnya. Jadi mengiris data. Tabel menjadi potongan yang lebih kecil mungkin tidak disarankan, tetapi mungkin tidak dapat dihindari.

rferrisx
sumber
2

Maaf jika jawaban ini datang sangat terlambat, tapi mungkin ini bisa berguna untuk orang lain. Sebenarnya ada solusi yang sangat berguna untuk masalah ini, dijelaskan di akhir split.

> 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
sumber
3
ini akan rusak jika ada jumlah nilai yang tidak sama di setiap grup!
Matifou
2

Namun kemungkinan lain adalah splitIndicesfungsi dari paket parallel:

library(parallel)
splitIndices(20, 3)

Memberi:

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

Wow, pertanyaan ini mendapat daya tarik lebih dari yang diharapkan.

Terima kasih untuk semua idenya. Saya telah menemukan solusi ini:

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"

Kuncinya adalah menggunakan parameter seq (setiap = chunk.size) sehingga membuatnya berfungsi. Menggunakan seq_along bertindak seperti peringkat (x) dalam solusi saya sebelumnya, tetapi sebenarnya mampu menghasilkan hasil yang benar dengan entri duplikat.

Sebastian
sumber
Bagi yang khawatir bahwa rep (seq_along (x), masing-masing = elements.per.chunk) mungkin terlalu tegang pada memori: ya itu. Anda dapat mencoba versi modifikasi dari saran saya sebelumnya: chunk <- function (x, n) split (x, factor (seq_along (x) %% n))
Sebastian
0

Ini terbagi menjadi potongan-potongan ukuran ⌊n / k⌋ + 1 atau ⌊n / k⌋ dan tidak menggunakan jenis 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
sumber