在一列数据中查找最近的负数值

4

我有一个数据框 df:

library(tidyverse)
t <- c(103,104,108,120,127,129,140,142,150,151,160,177,178,183,186,187,191,194,198,199)
w <- c(1,1,1,-1,-1,-1,-1,-1,1,1,-1,-1,1,1,1,-1,1,1,-1,-1)

df <- data_frame(t, w)

> dput(df)
structure(list(t = c(103, 104, 108, 120, 127, 129, 140, 142, 
150, 151, 160, 177, 178, 183, 186, 187, 191, 194, 198, 199), 
w = c(1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 
-1, 1, 1, -1, -1)), .Names = c("t", "w"), row.names = c(NA, 
-20L), class = c("tbl_df", "tbl", "data.frame"))

> df
# A tibble: 20 x 2
       t     w
   <dbl> <dbl>
 1   103  1.00
 2   104  1.00
 3   108  1.00
 4   120 -1.00
 5   127 -1.00
 6   129 -1.00
 7   140 -1.00
 8   142 -1.00
 9   150  1.00
10   151  1.00
11   160 -1.00
12   177 -1.00
13   178  1.00
14   183  1.00
15   186  1.00
16   187 -1.00
17   191  1.00
18   194  1.00
19   198 -1.00
20   199 -1.00

现在,如果 w 中的值大于零,则找到w最近上一个负数并将相应的t值之差分配给一个新列d。否则,d等于零。即所需输出如下:
       t     w   d
     103  1.00  NA   (there is no previous w < 0)
     104  1.00  NA   (there is no previous w < 0)
     108  1.00  NA   (there is no previous w < 0)
     120 -1.00   0
     127 -1.00   0
     129 -1.00   0
     140 -1.00   0
     142 -1.00   0
     150  1.00   8   = 150 - 142
     151  1.00   9   = 151 - 142
     160 -1.00   0
     177 -1.00   0
     178  1.00   1   = 178 - 177
     183  1.00   6   = 183 - 177
     186  1.00   9   = 186 - 177
     187 -1.00   0
     191  1.00   4   = 191 - 187
     194  1.00   7   = 194 - 187
     198 -1.00   0
     199 -1.00   0

(上面的NA可能也是零。)
自昨天以来,我一直在尝试使用findInterval()which()等方法解决这个问题,但没有成功。我另一个想法是在lag()函数中引入一个可变移位量...
理想情况下,我希望有一个类似于tidyverse的解决方案。
非常感谢任何帮助。
提前致谢!

你可能会发现 tidyr::fill 很有用。 - Chris Holbrook
2个回答

8

使用 data.table(因为 tidyverse 目前没有非等连接):

library(data.table)
DT = data.table(df)

DT[, v := 0]
DT[w > 0, v := 
  DT[w < 0][.SD, on=.(t < t), mult="last", i.t - x.t]
]

      t  w  v
 1: 103  1 NA
 2: 104  1 NA
 3: 108  1 NA
 4: 120 -1  0
 5: 127 -1  0
 6: 129 -1  0
 7: 140 -1  0
 8: 142 -1  0
 9: 150  1  8
10: 151  1  9
11: 160 -1  0
12: 177 -1  0
13: 178  1  1
14: 183  1  6
15: 186  1  9
16: 187 -1  0
17: 191  1  4
18: 194  1  7
19: 198 -1  0
20: 199 -1  0

它将新列初始化为0,然后将其替换为其中w>0的行的子集。替换使用数据子集.SD与表格部分DT[w<0]进行连接。连接语法是x[i, on=, j],在这种情况下...
  • x = DT[w < 0]
  • i = .SD = DT[w > 0]
连接使用i的每一行根据on=中的规则查找x中的行。当找到多个匹配时,我们只取最后一个(mult="last")。 j用于执行连接,这里计算两个列之间的差异。为了消除来自每个表的列的歧义,我们使用前缀x.*i.*
使用cummax。我不确定它是否具有普遍性,但它适用于此示例:
DT[, v := t - cummax(t*(w < 0))]
DT[cumsum(w < 0) == 0, v := NA]

我猜这需要t列按照递增顺序排序。


2
这两种变体都很好,因为t列在此处始终是升序的。而第二个变体也可以很容易地在dplyr中实现。谢谢! - user7647857

4

一种tidverse的方法:

首先,创建一个中间列(t2),如果是正数则为NA,如果是负数则为t。

df <- mutate(df, t2 = case_when(w > 0 ~ as.numeric(NA), TRUE ~ t)) 

#fill NA in t2 so that for each row, t2 is value of t when w was last neg
df <- fill(df, t2)

#> df
# A tibble: 20 x 3
#       t     w    t2
#   <dbl> <dbl> <dbl>
# 1   103     1    NA
# 2   104     1    NA
# 3   108     1    NA
# 4   120    -1   120
# 5   127    -1   127
# 6   129    -1   129
# 7   140    -1   140
# 8   142    -1   142
# 9   150     1   142
#10   151     1   142
#11   160    -1   160
#12   177    -1   177
#13   178     1   177
#14   183     1   177
#15   186     1   177
#16   187    -1   187
#17   191     1   187
#18   194     1   187
#19   198    -1   198
#20   199    -1   199

然后从t中减去t2

df$d <- with(df, t - t2)

#> df
# A tibble: 20 x 4
#       t     w    t2     d
#   <dbl> <dbl> <dbl> <dbl>
# 1   103     1    NA    NA
# 2   104     1    NA    NA
# 3   108     1    NA    NA
# 4   120    -1   120     0
# 5   127    -1   127     0
# 6   129    -1   129     0
# 7   140    -1   140     0
# 8   142    -1   142     0
# 9   150     1   142     8
#10   151     1   142     9
#11   160    -1   160     0
#12   177    -1   177     0
#13   178     1   177     1
#14   183     1   177     6
#15   186     1   177     9
#16   187    -1   187     0
#17   191     1   187     4
#18   194     1   187     7
#19   198    -1   198     0
#20   199    -1   199     0

1
这也非常好,可以使用管道。谢谢! - user7647857

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