Розділіть вектор на шматки в R


227

Я повинен розбити вектор на п ятки однакового розміру в Р. Я не зміг знайти жодної базової функції для цього. Також Google ніде мене не дістав. Тож ось що я придумав, сподіваюся, це допомагає комусь десь.

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

Будь-які коментарі, пропозиції чи вдосконалення дійсно вітаються та вітаються.

Ура, Себастьян


5
Так, дуже незрозуміло, що ви отримаєте - це рішення "п яти однакових розмірів". Але, можливо, і вас там потрапить: x <- 1:10; n <- 3; split (x, cut (x, n, label = FALSE))
mdsumner

і рішення в питанні, і рішення в попередньому коментарі є невірними, оскільки вони можуть не працювати, якщо вектор повторює записи. Спробуйте так:> foo <- c (rep (1, 12), rep (2,3), rep (3,3)) [1] 1 1 1 1 1 1 1 1 1 1 1 2 2 2 3 3 3> шматок (foo, 2) (дає неправильний результат)> chunk (foo, 3) (також неправильно)
mathheadinclouds

(продовження попереднього коментаря) чому? rank (x) не повинен бути цілим числом> rank (c (1,1,2,3)) [1] 1.5 1.5 3.0 4.0, тому метод у питанні не вдається. це працює (завдяки Харлану нижче)> chunk2 <- функція (x, n) розділити (x, вирізати (seq_along (x), n, мітки = FALSE))
mathheadinclouds

2
> split (foo, cut (foo, 3, labels = FALSE)) (також неправильно)
mathheadinclouds

1
Як підказує @mathheadinclouds, приклади даних є дуже особливим випадком. Приклади, які є більш загальними, були б кориснішими та кращими тестами. Наприклад, x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)наводяться приклади з відсутніми даними, повтореними значеннями, які вже не відсортовані, і знаходяться в різних класах (ціле число, символ, фактор).
Калин

Відповіді:


313

Однолінійне розщеплення d на шматки розміром 20:

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

Детальніше: Я думаю , що все , що вам потрібно seq_along(), split()і 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
Питання задає nшматки однакового розміру. Ви отримуєте невідому кількість розмірів n. У мене була така ж проблема і я використовував рішення від @mathheadinclouds.
rrs

4
Як видно з виводу d1, ця відповідь не розділяє d на групи однакового розміру (4 очевидно коротше). Таким чином, це не дає відповіді на запитання.
Калімо

9
@rrs: split (d, стеля (seq_along (d) / (length (d) / n)))
gkcn

Я знаю, що це досить старе, але може допомогти тим, хто тут спіткнувся. Хоча питання ОП полягало в тому, щоб розділити на шматки однакового розміру, якщо вектор трапиться не кратним дільником, останній штрих матиме інший розмір, ніж шматок. Для розколу n-chunksя використовував max <- length(d)%/%n. Я використовував це з вектором 31 рядок і отримав список з 3 векторів з 10 речень і одного з 1 речення.
сальву


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

Мені це подобається, оскільки він дає вам шматки, які мають однаковий розмір (добре для поділу великих завдань, наприклад, для розміщення обмеженої оперативної пам’яті або для виконання завдання по декількох потоках).
alexvpickering

3
Це корисно, але пам’ятайте, що це працюватиме лише на числових векторах.
Кіт Хугітт

@KeithHughitt це можна вирішити за допомогою факторів та повернення рівнів як числових. Або принаймні так я це реалізував.
drmariod

20

Спробуйте функцію 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
Це не працює для дроблячи x, yабо zвизначено в цьому коментарі . Зокрема, він сортує результати, які можуть бути або не бути нормальними, залежно від програми.
Калін


18

Це розділить його по-різному на те, що у вас є, але все ще досить приємна структура списку:

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

Що дасть вам наступне, залежно від того, як ви хочете його форматувати:

> 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

Запуск декількох синхронізацій за допомогою цих налаштувань:

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

Тоді ми маємо такі результати:

> 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: Зміна моєї функції від as.factor () до as.character () зробила її вдвічі швидшою.


13

Ще кілька варіантів ворсу ...

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

Зауважте, що тут вам не потрібно використовувати цю factorфункцію, але ви все ще хочете, щоб sortваш перший вектор був 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

Або ви можете призначити індекси символів, обміняйте цифри лівими галочками вище:

> 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

Або ви можете використовувати імена простих слів, що зберігаються у векторі. Зауважте, що використання sortдля отримання послідовних значень в xалфавіті міток:

> 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

Використання базових 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

А як уже говорилося, якщо ви хочете відсортовані індекси, просто:

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

Ви можете поєднати розділення / вирізання, як пропонує mdsummer, з квантилем, щоб створити рівні групи:

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

Це дає такий же результат для вашого прикладу, але не для перекошених змінних.


7

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

можливо, це більш зрозуміло, але та ж ідея:
split(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))

якщо ви хочете, щоб його замовили, киньте навколо нього сорт


6

Мені потрібна була та сама функція і я читав попередні рішення, однак мені також потрібно було, щоб неврівноважений шматок був в кінці, тобто якщо у мене є 10 елементів, щоб розділити їх на 3 вектори, то в моєму результаті повинні бути вектори з 3, 3,4 елемента відповідно. Тому я використав наступне (я залишив код неоптимізований для читабельності, інакше не потрібно мати багато змінних):

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

Ось ще один варіант.

ПРИМІТКА. У цьому прикладі ви вказуєте розмір CHUNK у другому параметрі

  1. всі шматки рівномірні, крім останнього;
  2. останні в гіршому випадку будуть меншими, ніколи не більшими за розмір шматка.

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

Проста функція розщеплення вектора простим використанням індексів - не потрібно надто ускладнювати це

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

Якщо вам не подобається split() і вам не подобається matrix()(з висячими НС), ось це:

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

Мовляв split(), він повертає список, але не витрачає часу чи місця з мітками, тому може бути більш ефективним.



2

Якщо вам не подобається, split()і ви не заперечуєте проти того, щоб національні офіцери висували короткий хвіст:

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

Стовпці повернутої матриці ([, 1: ncol]) - це дроїди, які ви шукаєте.


2

Мені потрібна функція, яка бере аргумент data.table (у лапках) та інший аргумент, який є верхньою межею щодо кількості рядків у підмножинах цього вихідного таблиця data.table. Ця функція створює будь-яку кількість таблиць даних, верхня межа яких дозволяє:

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

Ця функція дає мені ряд даних.tables з іменем df_ [номер] із початковим рядком з вихідного data.table у назві. Останній таблиця даних може бути коротким і заповненим NA, тому вам доведеться повернути його назад до тих даних, що залишилися. Цей тип функції корисний, оскільки певне програмне забезпечення ГІС має обмеження на кількість адресних штифтів, наприклад, ви можете імпортувати. Таким чином, нарізання таблиць data.tables на менші шматки, можливо, не рекомендується, але це неможливо уникнути.


2

Вибачте, якщо ця відповідь приходить так пізно, але, можливо, вона може бути корисною для когось іншого. Насправді існує дуже корисне рішення цієї проблеми, пояснене в кінці розколу.

> 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
це порушиться, якщо в кожній групі буде неоднакова кількість значень!
Матифу

2

Ще одна можливість - це splitIndicesфункція з пакета parallel:

library(parallel)
splitIndices(20, 3)

Дає:

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

Нічого собі, це питання отримало більше тяги, ніж очікувалося.

Дякую за всі ідеї. Я придумав таке рішення:

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"

Ключовим моментом є використання параметра seq (кожен = chunk.size), щоб змусити його працювати. Використання seq_along діє як раннє (x) у моєму попередньому рішенні, але насправді здатне створити правильний результат із дублюючими записами.


Для тих, хто стурбований тим, що rep (seq_along (x), кожен = elements.per.chunk) може надто напружувати пам'ять: так, це так. Ви можете спробувати модифіковану версію моєї попередньої пропозиції: chunk <- function (x, n) split (x, factor (seq_along (x) %% n))
Себастьян

0

Це розділяється на шматки розміром ⌊n / k⌋ + 1 або ⌊n / k⌋ і не використовує сортування 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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.