Разбить вектор на куски в R

227

Я должен разделить вектор на n кусков одинакового размера в R. Я не смог найти ни одной базовой функции для этого. Кроме того, 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
Да, очень неясно, что вы получаете решение для «n кусков одинакового размера». Но, может быть, это тебя и туда доставит: x <- 1:10; n <- 3; split (x, cut (x, n, метки = 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 1 2 2 2 3 3 3> чанк (foo, 2) (дает неправильный результат)> чанк (foo, 3) (также неверный)
mathheadinclouds
(продолжение предыдущего комментария) почему? rank (x) не обязательно должен быть целым числом> rank (c (1,1,2,3)) [1] 1,5 1,5 3,0 4,0, поэтому метод, о котором идет речь, не работает. этот работает (спасибо Харлану ниже)> chunk2 <- функция (x, n) split (x, cut (seq_along (x), n,
tags
2
> split (foo, cut (foo, 3, label = FALSE)) (также неверно)
mathheadinclouds
1
Как подсказывает @mathheadinclouds, пример данных - это особый случай. Примеры, которые являются более общими, были бы более полезными и лучшими тестами. Например, x <- c(NA, 4, 3, NA, NA, 2, 1, 1, NA ); y <- letters[x]; z <- factor(y)приводятся примеры с отсутствующими данными, повторяющимися значениями, которые еще не отсортированы и относятся к разным классам (целое число, символ, фактор).
Калин

Ответы:

314

Однострочник разделяется на куски размером 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.
РРП
4
Как видно из вывода d1, этот ответ не разбивает d на группы одинакового размера (4, очевидно, короче). Таким образом, это не отвечает на вопрос.
Калимо
9
@rrs: split (d, потолок (seq_along (d) / (длина (d) / n)))
gkcn
Я знаю, что это довольно старо, но это может помочь тем, кто спотыкается здесь. Хотя вопрос ОП заключался в том, чтобы разбить на куски одинакового размера, если вектор окажется не кратным делителю, последний отрезок будет иметь размер, отличный от порции. Чтобы разделить на n-chunksя использовал max <- length(d)%/%n. Я использовал это с вектором из 31 строки и получил список из 3 векторов из 10 предложений и одного из 1 предложения.
Salvu
75
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE)) 
mathheadinclouds
источник
36
simplified version...
n = 3
split(x, sort(x%%n))
zhan2383
источник
Мне это нравится, так как оно дает вам куски одинакового размера, насколько это возможно (хорошо для разделения большой задачи, например, для размещения ограниченного объема ОЗУ или для выполнения задачи в нескольких потоках).
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 

РЕДАКТИРОВАТЬ: переход от 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
FXQuantTrader
источник
9

Вы можете объединить split / cut, как предлагает mdsummer, с квантилем для создания четных групп:

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

Это дает тот же результат для вашего примера, но не для перекошенных переменных.

SiggyF
источник
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)))

если вы хотите, чтобы он был заказан, разбросайте его

frankc
источник
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 SIZE во втором параметре

  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,|
eAndy
источник
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(), он возвращает список, но не тратит время и пространство на метки, поэтому он может быть более производительным.

verbamour
источник
2

Благодарим @Sebastian за эту функцию

chunk <- function(x,y){
         split(x, factor(sort(rank(row.names(x))%%y)))
         }
Сообщество
источник
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]) - это те дроиды, которых вы ищете.

verbamour
источник
2

Мне нужна функция, которая принимает аргумент data.table (в кавычках) и другой аргумент, который является верхним пределом количества строк в подмножествах этого исходного data.table. Эта функция генерирует любое количество data.tables, которое позволяет верхний предел:

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

Эта функция дает мне ряд data.tables с именем df_ [число] с начальной строкой из исходного data.table в имени. Последний файл data.table может быть коротким и заполненным NA, поэтому вам нужно вернуть его обратно к любым оставшимся данным. Этот тип функций полезен, потому что определенное программное обеспечение ГИС имеет ограничения, например, на количество выводов адреса, которые вы можете импортировать. Поэтому разделение data.tables на более мелкие куски не рекомендуется, но этого нельзя избежать.

rferrisx
источник
2

Извините, если этот ответ приходит так поздно, но, возможно, он может быть полезен для кого-то еще. На самом деле есть очень полезное решение этой проблемы, объясненное в конце? 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
Лаура Паладини
источник
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
Matifou
источник
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 (each = chunk.size), чтобы он работал. Использование seq_along действует как rank (x) в моем предыдущем решении, но на самом деле может дать правильный результат с дублирующимися записями.

Себастьян
источник
Для тех, кто обеспокоен, что rep (seq_along (x), each = 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))
Valentas
источник