用 R 按照一组区间对数据进行子集筛选

4

我希望能够根据一组区间从向量中排除值。

示例数据:

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似乎是最好的,但我不喜欢它——它不够健壮——你需要检查重叠的时间间隔等。

是否有更好的解决这个看似简单的问题的方法?谢谢

真实数据:我有一些时间(日期时间、强度)上测量的光强度。我还有一些日期时间间隔,表示测量设备在此期间处于维护状态(开始、结束)。现在我想有效地清理数据=排除在维护期间测得的值。


这个对你有用吗?我不确定你期望什么输出,但是根据我所看到的编写了这个。mydata[mydata > mIntervals$start & mydata < mIntervals$end] <- FALSE 编辑:必须是单引号。 - Bas
通过修改你的 findInterval 函数,你可以使用 mydata[findInterval(mydata, mIntervals$start) <= findInterval(mydata, mIntervals$end)] - alexis_laz
1
@Bas:这样做不行——mydata和mIntervals的长度不同,结果不是我想要的。但还是谢谢。 - Dead Vil
5个回答

8

使用 data.table 的开发版本 (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)]

6

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

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

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

2
我不知道这会有多么有效,但是...
vbetween <- Vectorize(dplyr::between, vectorize.args = c("left", "right"), SIMPLIFY=F)
mydata[!Reduce("|", vbetween(mydata, mIntervals$start, mIntervals$end))]

1
这只是隐藏了循环... 另外,据我所知 dplyr::between() 不允许开放边界(这似乎是 OP 需要的)。 - Arun

0
我想展示另一种使用data.table包和rolljoin的方法。
首先,您需要将区间的数据框进行melt和order操作:
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]

仅考虑具有 "end" 值的数据,因为您已经使用了 -Inf(与 mIntervals 中下一个最接近的值合并)。

final.dt[variable == "end"]

非常快速和灵活。

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