使用右闭区间的findInterval()函数

20

R中伟大的findInterval()函数在其文档中显示,使用左闭子区间作为其vec参数,如下所示:

如果i <- findInterval(x,v),我们有v[i[j]] <= x[j] < v[i[j] + 1]

如果我想要右闭子区间,我的选择是什么?我能想到最好的办法是:

findInterval.rightClosed <- function(x, vec, ...) {
  fi <- findInterval(x, vec, ...)
  fi - (x==vec[fi])
}

另一个也可以:

findInterval.rightClosed2 <- function(x, vec, ...) {
  length(vec) - findInterval(-x, -rev(vec), ...)
}

这是一个小测试:

x <- c(3, 6, 7, 7, 29, 37, 52)
vec <- c(2, 5, 6, 35)
findInterval(x, vec)
# [1] 1 3 3 3 3 4 4
findInterval.rightClosed(x, vec)
# [1] 1 2 3 3 3 4 4
findInterval.rightClosed2(x, vec)
# [1] 1 2 3 3 3 4 4

但是,如果有更好的解决方案,我希望能看到。所谓“更好”,就是指“某种程度上更令人满意”、“不会感觉很拙劣”或甚至是“更有效率”。=)

(请注意,findInterval()函数还有一个rightmost.closed参数,但它是不同的——它仅适用于最后一个子区间,并具有不同的含义。)


你对于 findInterval(x, c(-Inf, head(vec, -1))) 有什么看法? - sgibb
@sgibb,那似乎不起作用,我添加了一个示例,您的代码没有给出相同的结果。 - Ken Williams
我有点困惑,但是 findInterval(x-1,vec) 是否是你要找的东西? - thelatemail
@thelatemail,只要它是整数? - BenBarnes
我认为将答案发布在自己的问题下是可以的。 我会投票支持你的 findInterval.rightClosed2 - flodel
另一个理想的属性是它可以与除数字之外的类型一起使用(例如POSIXt对象,但最好是任何具有比较运算符的对象),就像findInterval一样(findInterval适用于任何支持as.double方法的对象)。上面的第一个函数成功了,但第二个失败了,因为没有办法否定POSIXt对象。 - Ken Williams
3个回答

10

编辑: 所有巷道都进行了大清理。

你可以看看cut命令。默认情况下,cut会创建左开右闭的区间,你可以使用相应的参数(right)来改变它。以你的例子为例:

x <- c(3, 6, 7, 7, 29, 37, 52)
vec <- c(2, 5, 6, 35)
cutVec <- c(vec, max(x)) # for cut, range of vec should cover all of x

现在创建四个函数,它们应该做同样的事情:两个来自OP,一个来自Josh O'Brien,还有cutcut 的两个参数已经从默认设置更改:include.lowest = TRUE 将为最小(最左侧)区间创建两侧都封闭的区间。 labels = FALSE 将导致cut仅返回用于箱子的整数值,而不是创建一个因子,否则它会这样做。

findInterval.rightClosed <- function(x, vec, ...) {
  fi <- findInterval(x, vec, ...)
  fi - (x==vec[fi])
}
findInterval.rightClosed2 <- function(x, vec, ...) {
  length(vec) - findInterval(-x, -rev(vec), ...)
}
cutFun <- function(x, vec){
    cut(x, vec, include.lowest = TRUE, labels = FALSE)
}
# The body of fiFun is a contribution by Josh O'Brien that got fed to the ether.
fiFun <- function(x, vec){
    xxFI <- findInterval(x, vec * (1 + .Machine$double.eps))
}

所有函数都返回相同的结果吗?是的。(请注意在cutFun中使用了cutVec)

mapply(identical, list(findInterval.rightClosed(x, vec)),
  list(findInterval.rightClosed2(x, vec), cutFun(x, cutVec), fiFun(x, vec)))
# [1] TRUE TRUE TRUE

现在需要更严格的向量分类:

x <- rpois(2e6, 10)
vec <- c(-Inf, quantile(x, seq(.2, 1, .2)))

测试是否相同(注意使用 unname

mapply(identical, list(unname(findInterval.rightClosed(x, vec))),
  list(findInterval.rightClosed2(x, vec), cutFun(x, vec), fiFun(x, vec)))
# [1] TRUE TRUE TRUE

并进行基准测试:

library(microbenchmark)
microbenchmark(findInterval.rightClosed(x, vec), findInterval.rightClosed2(x, vec),
  cutFun(x, vec), fiFun(x, vec), times = 50)
# Unit: milliseconds
#                                expr       min        lq    median        uq       max
# 1                    cutFun(x, vec)  35.46261  35.63435  35.81233  36.68036  53.52078
# 2                     fiFun(x, vec)  51.30158  51.69391  52.24277  53.69253  67.09433
# 3  findInterval.rightClosed(x, vec) 124.57110 133.99315 142.06567 155.68592 176.43291
# 4 findInterval.rightClosed2(x, vec)  79.81685  82.01025  86.20182  95.65368 108.51624

从这次运行结果来看,cut似乎是最快的。


谢谢。我记得 cut 不如 findInterval 高效,而且它也不允许像 findInterval 那样在 vec 的右边缘进行截断(参见输出的第 15-17 行),但是这部分可以通过在末尾添加一个 Inf 来解决。 - Ken Williams
为了完整起见,您能否在计时中包括原始问题(OP)的解决方案...? - joran
需要注意的一个细微区别是:findInterval 仅要求分隔点非递减,但 cut 要求它们严格递增。解决这个问题可能会使 cutFun 失去速度上的优势。 - Ken Williams
@eddi,你能提供一个fiFun失败的例子吗?我运行的几个例子似乎返回了预期的结果。 - BenBarnes
我仍然更喜欢OP的解决方案,例如这个帖子:https://dev59.com/12Qo5IYBdhLWcg3wR9nD - user3032689
显示剩余5条评论

4
也许你可以使用选项 left.open: 来实现。
findInterval(x, vec, left.open=T)
[1] 1 2 3 3 3 4 4

是的 - 这个选项是在2016年10月发布的R版本3.3.2中添加的。现在这是正确的方法。 - Ken Williams

-1
如果您的限制是区间,您可以简单地将右侧区间扩大一点:interval+c(0,0.1)即可:findinterval(value, interval+c(0,0.1))。

1
出于各种原因,这不能起作用。最根本的是,回收会导致每个奇数条目上的右区间发生变化,而不是所有条目。此外,这假设您知道数据的离散化宽度大于0.1。 - Matthew Lundberg

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