R:修改以多行数据为条件的变量

R: modify a variable conditioned on data from multiple previous rows

您好,我非常感谢您对此提供帮助,我在之前的问题中确实找不到解决方案。

我有一个长格式的 tibble(按 id 分组并按时间排列的行)。 我想基于 "varx" 创建一个变量 "eleg"。条件是 "eleg" = 1 if "varx" in the previous 3 rows == 0 and in the current row varx == 1, if not = 0, for each ID.如果可能的话使用 dplyr.

id <- c(1,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3)
time <- c(1,2,3,4,5,6,7,1,2,3,4,5,6,1,2,3,4)
varx <- c(0,0,0,0,1,1,0,0,1,1,1,1,1,0,0,0,1)
eleg <- c(0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1)
table <- data.frame(id, time, varx, eleg)

在我的真实数据集中,条件是 "in the previous 24 rows",如果符合条件,同一个 ID 可以有 eleg == 1 多次。

谢谢。

library(data.table)
df %>% 
mutate(elegnew = ifelse(Reduce("+", shift(df$varx, 1:3)) == 0 & df$varx == 1, 1, 0))

   id time varx eleg elegnew
1   1    1    0    0       0
2   1    2    0    0       0
3   1    3    0    0       0
4   1    4    0    0       0
5   1    5    1    1       1
6   1    6    1    0       0
7   1    7    0    0       0
8   2    1    0    0       0
9   2    2    1    0       0
10  2    3    1    0       0
11  2    4    1    0       0
12  2    5    1    0       0
13  2    6    1    0       0
14  3    1    0    0       0
15  3    2    0    0       0
16  3    3    0    0       0
17  3    4    1    1       1

其中一种方法可能是

library(dplyr)

m <- 3     #number of times previous rows are looked back

df %>%
  group_by(id) %>%
  mutate(eleg = ifelse(rowSums(sapply(1:m, function(k) lag(varx, n = k, order_by = id, default = 1) == 0)) == m & varx == 1, 
                       1, 
                       0)) %>%
  data.frame()

这给出了

   id time varx eleg
1   1    1    0    0
2   1    2    0    0
3   1    3    0    0
4   1    4    0    0
5   1    5    1    1
6   1    6    1    0
7   1    7    0    0
8   2    1    0    0
9   2    2    1    0
10  2    3    1    0
11  2    4    1    0
12  2    5    1    0
13  2    6    1    0
14  3    1    0    0
15  3    2    0    0
16  3    3    0    0
17  3    4    1    1


示例数据:

df <- structure(list(id = c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 
3, 3, 3, 3), time = c(1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 
1, 2, 3, 4), varx = c(0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 
0, 0, 0, 1)), .Names = c("id", "time", "varx"), row.names = c(NA, 
-17L), class = "data.frame")

这是另一种方法,使用 dplyrzoo

library(dplyr)
library(zoo)

df %>% 
  group_by(id) %>% 
  mutate(elegnew = as.integer(varx == 1 & 
                      rollsum(varx == 1, k = 4, align = "right", fill = 0) == 1))

# # A tibble: 17 x 5
# # Groups:   id [3]
# id  time  varx  eleg elegnew
# <dbl> <dbl> <dbl> <dbl>   <int>
#   1    1.    1.    0.    0.       0
# 2    1.    2.    0.    0.       0
# 3    1.    3.    0.    0.       0
# 4    1.    4.    0.    0.       0
# 5    1.    5.    1.    1.       1
# 6    1.    6.    1.    0.       0
# 7    1.    7.    0.    0.       0
# 8    2.    1.    0.    0.       0
# 9    2.    2.    1.    0.       0
# 10    2.    3.    1.    0.       0
# 11    2.    4.    1.    0.       0
# 12    2.    5.    1.    0.       0
# 13    2.    6.    1.    0.       0
# 14    3.    1.    0.    0.       0
# 15    3.    2.    0.    0.       0
# 16    3.    3.    0.    0.       0
# 17    3.    4.    1.    1.       1

想法是按id分组,然后检查a)varx是否为1和b)前3行加上当前行(k=4)中varx=1事件的总和是否为1(这意味着所有前 3 个必须为 0)。我假设 varx 是 0 或 1。

您已要求 dplyr 解决方案,最好。
下面是一个base R的,有一个函数你可以适配"in the previous 24 rows",把n = 24传给函数就可以了

fun <- function(DF, crit = "varx", new = "eleg", n = 3){
  DF[[new]] <- 0
  for(i in seq_len(nrow(DF))[-seq_len(n)]){
    if(all(DF[[crit]][(i - n):(i - 1)] == 0) && DF[[crit]][i] == 1)
      DF[[new]][i] <- 1
  }
  DF
}


sp <- split(table[-4], table[-4]$id)
new_df <- do.call(rbind, lapply(sp, fun))
row.names(new_df) <- NULL
identical(table, new_df)
#[1] TRUE

请注意,如果您要创建新列 eleg,您可能不需要拆分 table[-4],只需 table,因为第 4 列尚不存在。
你可以做 do.call(rbind, lapply(sp, fun, n = 24)),其余的都是一样的。