按 R 中的间隔集对数据进行子集化

subset data by set of intervals in R

我想根据一组间隔从向量中排除值。

示例数据:

mydata <-  sort(runif(100,0,300))
mIntervals <- data.frame(start = c(2,50,97,159) , end = c(5,75, 120, 160))

解决方案 1: 使用简单的 subset() - 不适合 - mIntervals 的长度可能相当大

解决方案 2: 使用嵌套 for 循环:

valid <- vector(length(mydata))
valid <- TRUE
for(i in 1:length(mydata){
 for(j in 1:length(mIntervals){
  if(mydata[i] > mIntervals[j,]$start & mydata[i] < mIntervals[j,]$end){
   valid[i] <- FALSE
  }
 }
} 
mydata[valid]

此解决方案在 R 中花费的时间太长。

解决方案3:函数findIntervals

   require(FSA)
   valid <- findInterval(mydata, sort(c(mIntervals$start, mIntervals$end)))
   mydata[is.even(valid)]

解决方法4:用了一些包'Intervals',但是也没有合适的函数(可能是interval_overlap())

已经讨论了非常相似(但不完全相同)的问题 。但是有整数向量的解决方案,而不是连续变量。

我没有更多的想法。解决方案编号3 似乎是最好的,但我不喜欢它 - 它不够健壮 - 你必须检查重叠间隔等。

这个看起来很简单的问题有更好的解决方案吗?谢谢

真实数据:我在某些时间测量了光强度(日期时间、强度)。我还有测量设备正在维护(开始、结束)的日期时间间隔。 现在我想清理数据 = 排除在维护期间测量的值(高效!)。

这是一个 Rcpp 实现:

library(Rcpp);
set.seed(12L);
mydata <- sort(runif(100L,0,300));
mIntervals <- data.frame(start=c(2,50,97,159),end=c(5,75,120,160));
cppFunction('
    LogicalVector inIntervals(DoubleVector v, DoubleVector starts, DoubleVector ends ) {
        if (starts.size()!=ends.size())
            throw new std::invalid_argument("starts and ends must be same length.");
        LogicalVector res(v.size(),false);
        for (int i = 0; i < v.size(); ++i) {
            double val = v[i];
            for (int j = 0; j < starts.size(); ++j)
                if (val>starts[j] && val<ends[j]) {
                    res(i) = true;
                    break;
                }
        }
        return res;
    }
');
mydata[!inIntervals(mydata,mIntervals$start,mIntervals$end)];
##  [1]   6.863323  10.168687  13.765236  16.585860  20.808275  28.508376  29.355912
##  [8]  30.534403  33.809681  37.152610  42.659676  45.787152  46.319152  47.274177
## [15]  47.877135  49.281417  78.640425  79.475513  80.383078  80.814563  88.273175
## [22]  93.344382  94.136411  94.736104  96.603457 126.327013 130.399146 131.800295
## [29] 131.828798 137.282145 148.542361 151.430386 162.212264 162.541752 165.648249
## [36] 166.758025 167.388096 172.243474 172.603380 176.544549 182.477693 189.979382
## [43] 192.404449 192.499610 199.703949 200.945789 202.035664 208.173427 210.533571
## [50] 212.949140 214.431451 215.524016 224.951507 225.608016 229.180120 230.324658
## [57] 232.415456 236.278594 236.350904 244.164168 244.218976 244.669498 245.332560
## [64] 247.184695 253.110672 253.267796 263.339092 263.352697 264.826916 267.979469
## [71] 282.326263 282.786520 285.996158 291.379637 293.290767 294.260683

我不知道这会有多高效,但是...

vbetween <- Vectorize(dplyr::between, vectorize.args = c("left", "right"), SIMPLIFY=F)
mydata[!Reduce("|", vbetween(mydata, mIntervals$start, mIntervals$end))]

使用data.tabledevelopment version (1.9.7),我们可以尝试%anywhere%:

library(data.table)
# %anywhere% returns TRUE if mydata is within any mIntervals, else FALSE
ans <- mydata[!mydata %anywhere% mIntervals] 

这将包括端点,但是因为 incbounds = TRUE 是默认设置。如果需要排除端点,可以使用以下语法:

mydata[!anywhere(mydata, mIntervals[, 1], mIntervals[, 2], incbounds = FALSE)]

如果重新排列间隔,可以使用 cut 函数,然后只取出奇数间隔:

NEWinterval <- c(2,5,50,75,97,120,159,160)
mydata[cut(mydata, NEWinterval,labels = F) %% 2 != 0]

我想展示另一种使用 data.table 包和 rolljoin 的方法。

首先你融化并排序间隔的数据框:

mIntervals.dt <- data.table(mIntervals)
Intervals.melt <- melt(mIntervals.dt, measure.vars = c("start", "end"))

订购数据并使用滚动连接:

mydata.dt <- data.table(mydata)
setkey(Intervals.melt, value)
setkey(mydata.dt) 

final.dt <- Intervals.melt[mydata.dt, roll = -Inf]

由于您使用了 -Inf(与 mIntervals 中下一个最接近的值合并),因此仅获取具有 "end" 值的数据。

final.dt[variable == "end"]

非常快速和灵活。