Разбить вектор на куски в R
Я должен разделить вектор на 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
Любые комментарии, предложения или улучшения действительно приветствуются и приветствуются.
Ура, Себастьян
22 ответа
Однострочник разделяется на куски размером 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
chunk2 <- function(x,n) split(x, cut(seq_along(x), n, labels = FALSE))
Использование базы 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
Попробуйте функцию 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
Если тебе не нравится split()
а тебе не нравится matrix()
(с его свисающими NA), есть это:
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()
, он возвращает список, но не тратит время и пространство на метки, поэтому он может быть более производительным.
Это разделит его по-другому на то, что у вас есть, но я думаю, что все еще довольно хорошая структура списка:
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() в моей функции сделал это в два раза быстрее.
Еще несколько вариантов в кучу...
> 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
Еще одна возможность 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
Вы можете объединить split/cut, как предлагает mdsummer, с квантилем для создания четных групп:
split(x,cut(x,quantile(x,(0:n)/n), include.lowest=TRUE, labels=FALSE))
Это дает тот же результат для вашего примера, но не для перекошенных переменных.
Вот еще один вариант.
ПРИМЕЧАНИЕ: в этом примере вы указываете CHUNK SIZE во втором параметре
- все куски одинаковы, кроме последнего;
- последний в худшем случае будет меньше, а не больше, чем размер куска.
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,|
split(x,matrix(1:n,n,length(x))[1:length(x)])
возможно это более понятно, но идея та жеsplit(x,rep(1:n, ceiling(length(x)/n),length.out = length(x)))
если хотите, чтобы он был заказан, разбросайте его
Мне нужна была та же функция, и я прочитал предыдущие решения, однако мне также нужно было иметь несбалансированный кусок в конце, то есть, если у меня есть 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
Простая функция для разделения вектора путем простого использования индексов - не нужно слишком усложнять это
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])
}))
}
Извините, если этот ответ приходит так поздно, но, возможно, он может быть полезен для кого-то еще. На самом деле есть очень полезное решение этой проблемы, объясненное в конце? 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
Если тебе не нравится 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]) - это те дроиды, которых вы ищете.
Мне нужна функция, которая принимает аргумент 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 на более мелкие фрагменты не рекомендуется, но этого нельзя избежать.
Кредит @Sebastian для этой функции
chunk <- function(x,y){
split(x, factor(sort(rank(row.names(x))%%y)))
}
Вау, этот вопрос получил больше тяги, чем ожидалось.
Спасибо за все идеи. Я пришел с этим решением:
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) в моем предыдущем решении, но на самом деле может дать правильный результат с дублирующимися записями.
Не уверен, что это отвечает на вопрос ОП, но я думаю, что%%
здесь может быть полезно
df # some data.frame
N_CHUNKS <- 10
I_VEC <- 1:nrow(df)
df_split <- split(df, sort(I_VEC %% N_CHUNKS))
Вот еще один, позволяющий вам контролировать, хотите ли вы, чтобы результат был упорядочен или нет:
split_to_chunks <- function(x, n, keep.order=TRUE){
if(keep.order){
return(split(x, sort(rep(1:n, length.out = length(x)))))
}else{
return(split(x, rep(1:n, length.out = length(x))))
}
}
split_to_chunks(x = 1:11, n = 3)
$`1`
[1] 1 2 3 4
$`2`
[1] 5 6 7 8
$`3`
[1] 9 10 11
split_to_chunks(x = 1:11, n = 3, keep.order=FALSE)
$`1`
[1] 1 4 7 10
$`2`
[1] 2 5 8 11
$`3`
[1] 3 6 9
This splits into chunks of size ⌊n/k⌋+1 or ⌊n/k⌋ and does not use the O(n log n) sort.
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))