R如何将数值向量在某个位置进行分割?

21

我想知道如何将一个向量按指定的索引分成两个部分:

splitAt <- function(x, pos){
  list(x[1:pos-1], x[pos:length(x)])
}

a <- c(1, 2, 2, 3)

> splitAt(a, 4)
[[1]]
[1] 1 2 2

[[2]]
[1] 3

我的问题是:一定有现成的函数可以实现这个功能,但我找不到它?也许split是一个可能性?如果pos=0pos>length(a),那么我的天真实现也无法工作。

3个回答

33

一个改进的方法是:

splitAt <- function(x, pos) unname(split(x, cumsum(seq_along(x) %in% pos)))

现在它可以接受一个位置向量:

splitAt(a, c(2, 4))
# [[1]]
# [1] 1
# 
# [[2]]
# [1] 2 2
# 
# [[3]]
# [1] 3

如果pos <= 0或者pos >= length(x),它会以适当的方式(主观)运行并在单个列表项中返回整个原始向量。如果您希望它出现错误,请在函数顶部使用stopifnot


谢谢,这对我很有效!我仍然很惊讶基本的R语言中没有实施 splitAt 函数... - user1981275
这个函数在使用非常大的 x 时非常慢,可能是由于 seq_along(x) 创建了一个非常长的向量,然后 %in% 需要匹配这个非常长的向量。 - Calimo
@Calimo:不,如果你对其进行性能分析,你会发现大部分时间都花在了比较慢的“split”内部。当然你可以避免使用它,但这会使代码的可读性和紧凑性受到很大程度的影响。 - flodel

8

我试图使用flodel的答案,但在我的情况下,这个函数对于非常大的x值太慢了(而且必须反复调用)。因此,我创建了下面这个函数,它更快,但也很丑陋,并且不能正常工作。特别是它不检查任何东西,而且至少对于pos >= length(x)pos <= 0会返回有错误的结果(如果您对输入不确定并且不太关心速度,可以自行添加这些检查),而且还可能存在其他情况,所以请小心。

splitAt2 <- function(x, pos) {
    out <- list()
    pos2 <- c(1, pos, length(x)+1)
    for (i in seq_along(pos2[-1])) {
        out[[i]] <- x[pos2[i]:(pos2[i+1]-1)]
    }
    return(out)
}

然而,当输入的x长度为100万时,splitAt2 的运行速度约快了20倍:

library(microbenchmark)
W <- rnorm(1e6)
splits <- cumsum(rep(1e5, 9))
tm <- microbenchmark(
                     splitAt(W, splits),
                     splitAt2(W, splits),
                     times=10)
tm

谢谢!另外,根据上面的简单示例,splitAt2 的性能更好。 - user1981275
4
+1 - 一种比较漂亮的重写可能是:function(x, pos) {pos <- c(1L, pos, length(x) + 1L); Map(function(x, i, j) x[i:j], list(x), head(pos, -1L), tail(pos, -1L) - 1L)}。当分割数增加时,它似乎也更快,不确定原因。 - flodel
@user1981275 定义“更好”的含义。如果“更好”意味着更快,我同意,但作为通用函数,健壮性是关键,这种情况下flodel的版本更好。 - Calimo
@flodel,确实你的重写在分割数量非常大的情况下更快。我也无法解释为什么。 - Calimo

5

另一个可能比flodel's solution更快和/或更易读/优雅的替代方案:

splitAt <- function(x, pos) {
  unname(split(x, findInterval(x, pos)))
}

在我的实践中,它不像flodel的解决方案那样有效。 - Shixiang Wang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接