R:找到三个或更多连续的负数,并从数据帧中删除行

时间:2016-04-11 00:23:23

标签: r dataframe

以下是我的示例数据:

df = data.frame(id=rep(c(123,456,789),each=5),day=rep(c(1:5),3),measure=c(2.2,3.4,2.1,-0.2,-1.2,3.4,2.4,-2.2,-3.1,-1.7,3.9,5.4,-1,3.2,4.2))

    id day measure
1  123   1     2.2
2  123   2     3.4
3  123   3     2.1
4  123   4    -0.2
5  123   5    -1.2
6  456   1     3.4
7  456   2     2.4
8  456   3    -2.2
9  456   4    -3.1
10 456   5    -1.7
11 789   1     3.9
12 789   2     5.4
13 789   3    -1.0
14 789   4     3.2
15 789   5     4.2

每个人都有五天的数据。

我想在每个人中找到df $ measure中的位置,其中有三个或更多连续负值在每个单独的内容中,并删除这些行。如果有两个或更少的连续负值,只需将值设置为0。

个人123最后有两个负值,因此将值更改为0 个别456最后有三个负值,所以删除这些行 单个789在第3天有一个负值,因此将值更改为0

结果:

    id day measure
1  123   1     2.2
2  123   2     3.4
3  123   3     2.1
4  123   4    0
5  123   5    0
6  456   1     3.4
7  456   2     2.4
8 789   1     3.9
9 789   2     5.4
10 789   3    0
11 789   4     3.2
12 789   5     4.2

到目前为止我所拥有的:

如果我首先将df $ measure中的所有负值都转换为0 ..

df$measure[df$measure < 0] <- 0

然后以某种方式使用rle:

m = rle(df$measure)

Run Length Encoding
  lengths: int [1:12] 1 1 1 2 1 1 3 1 1 1 ...
  values : num [1:12] 2.2 3.4 2.1 0 3.4 2.4 0 3.9 5.4 0 ...

从m $ length和m $值计算得出连续3个或更多的0的索引 - 需要删除的那些。

但需要为每个ID单独检查吗?

实现这一目标的最有效方法是什么?

2 个答案:

答案 0 :(得分:4)

我们从&#39;衡量&#39;得到逻辑向量的rle!df$measure - 0为0,其他所有为FALSE为TRUE)。 ,分配&#39;值&#39;具有&#39;长度的矢量(来自rle)&#39;小于3到FALSE,否定它(!)并对数据集进行子集化。

df[!inverse.rle(within.list(rle(!df$measure), values[lengths<3] <- FALSE)),]
#    id day measure
#1  123   1     2.2
#2  123   2     3.4
#3  123   3     2.1
#4  123   4     0.0
#5  123   5     0.0
#6  456   1     3.4
#7  456   2     2.4
#11 789   1     3.9
#12 789   2     5.4
#13 789   3     0.0
#14 789   4     3.2
#15 789   5     4.2

注意:上述结果与OP的预期输出相匹配,因为0值在相邻的id&#39;之间不连续。如果我们需要在每个ID中执行此操作,请按技术使用任何组。在base R中,我们可以使用ave

执行此操作
indx <- with(df, !ave(!measure, id, FUN = function(x) {
                 inverse.rle(within.list(rle(x), values[lengths<3] <- FALSE))
              }))
df[indx,]
#    id day measure
#1  123   1     2.2
#2  123   2     3.4
#3  123   3     2.1
#4  123   4     0.0
#5  123   5     0.0
#6  456   1     3.4
#7  456   2     2.4
#11 789   1     3.9
#12 789   2     5.4
#13 789   3     0.0
#14 789   4     3.2
#15 789   5     4.2

或者我们可以使用rleid中的data.table。转换&#39; data.frame&#39;到&#39; data.table&#39; (setDT(df)),按&#39; id&#39;分组和否定的&#39;度量&#39;的run-length-id,得到一个逻辑索引列(!(!measure & .N >2))来对数据集中的行进行子集化。

library(data.table)
setDT(df)[df[, !(!measure & .N >2), .(id, rleid(!measure))]$V1]
#     id day measure
# 1: 123   1     2.2
# 2: 123   2     3.4
# 3: 123   3     2.1
# 4: 123   4     0.0
# 5: 123   5     0.0
# 6: 456   1     3.4
# 7: 456   2     2.4
# 8: 789   1     3.9
# 9: 789   2     5.4
#10: 789   3     0.0
#11: 789   4     3.2
#12: 789   5     4.2

或者我们可以使用dplyr

library(dplyr)
df %>% 
  group_by(id, gr = cumsum(c(0,abs(diff(!measure))))) %>% 
  filter(!(all(!measure) & n() >2)) %>% 
  ungroup() %>% 
  select(-gr)
#      id   day measure
#    (dbl) (int)   (dbl)
#1    123     1     2.2
#2    123     2     3.4
#3    123     3     2.1
#4    123     4     0.0
#5    123     5     0.0
#6    456     1     3.4
#7    456     2     2.4
#8    789     1     3.9
#9    789     2     5.4
#10   789     3     0.0
#11   789     4     3.2
#12   789     5     4.2

注2:用0替换负值后使用数据。

答案 1 :(得分:4)

另一个基础R版本ave使用旧的&#34;反向否定逻辑检查&#34;获得适当的反击的技巧。

自:

with(df, rev(cumsum(!(rev(measure) < 0))) )
#[1] 9 8 7 6 6 6 5 4 4 4 4 3 2 2 1
# compare the equivalent of df$id groups
#[1] 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3

id结合使用时,您只需检查长度:

df[with(df, ave(measure, list(id, rev(cumsum(!(rev(measure) < 0)))), FUN=length) < 3 ),]

#    id day measure
#1  123   1     2.2
#2  123   2     3.4
#3  123   3     2.1
#4  123   4    -0.2
#5  123   5    -1.2
#6  456   1     3.4
#7  456   2     2.4
#11 789   1     3.9
#12 789   2     5.4
#13 789   3    -1.0
#14 789   4     3.2
#15 789   5     4.2