如何在另一列上有条件地按组执行列的连续计数

How to Perform Consecutive Counts of Column by Group Conditionally Upon Another Column

我正在尝试从按 PatientID 列分组的 Noshow 列中获取连续计数。我使用的以下代码非常接近我希望获得的结果。但是,使用求和函数 returns 求整个组的总和。我希望 sum 函数只对当前行和上面有“1”的行求和。基本上,我试图计算患者在每一行中没有显示他们的预约的连续次数,然后在他们显示时重置为 0。似乎只需要对我的以下代码进行一些调整。但是,我似乎无法在本网站的任何地方找到答案。

transform(df, ConsecNoshows = ifelse(Noshow == 0, 0, ave(Noshow, PatientID, FUN = sum)))

以上代码产生以下输出:

#Source: local data frame [12 x 3]
#Groups: ID [2]
#
#   PatientID Noshow ConsecNoshows
#       <int>  <int>         <int>   
#1          1      0             0
#2          1      1             4
#3          1      0             0
#4          1      1             4
#5          1      1             4
#6          1      1             4
#7          2      0             0
#8          2      0             0
#9          2      1             3
#10         2      1             3
#11         2      0             0
#12         2      1             3

这就是我想要的:

#Source: local data frame [12 x 3]
#Groups: ID [2]
#
#   PatientID Noshow ConsecNoshows
#       <int>  <int>         <int>   
#1          1      0             0
#2          1      1             0
#3          1      0             1
#4          1      1             0
#5          1      1             1
#6          1      1             2
#7          2      0             0
#8          2      0             0
#9          2      1             0
#10         2      1             1
#11         2      0             2
#12         2      1             0

[更新]我希望连续计数向下偏移一行。

感谢您提前提供的任何帮助!

对连续值进行分组的最直接的方法是使用 data.table 中的 rleid,这是 data.table 包中的一个选项,您可以通过 [=14] 对数据进行分组=] 以及 rleidNoshow 变量。而且您还需要 cumsum 函数来获取 Noshow 变量的累计和而不是 sum:

library(data.table)
setDT(df)[, ConsecNoshows := ifelse(Noshow == 0, 0, cumsum(Noshow)), .(PatientID, rleid(Noshow))]
df
#    PatientID Noshow ConsecNoshows
# 1:         1      0             0
# 2:         1      1             1
# 3:         1      0             0
# 4:         1      1             1
# 5:         1      1             2
# 6:         1      1             3
# 7:         2      0             0
# 8:         2      0             0
# 9:         2      1             1
#10:         2      1             2
#11:         2      0             0
#12:         2      1             1

这是另一种(类似的)data.table 方法

library(data.table)
setDT(df)[, ConsecNoshows := seq(.N) * Noshow, by = .(PatientID, rleid(Noshow))]
df
#     PatientID Noshow ConsecNoshows
#  1:         1      0             0
#  2:         1      1             1
#  3:         1      0             0
#  4:         1      1             1
#  5:         1      1             2
#  6:         1      1             3
#  7:         2      0             0
#  8:         2      0             0
#  9:         2      1             1
# 10:         2      1             2
# 11:         2      0             0
# 12:         2      1             1

这基本上是按 NoshowPatientID 和 "run-length-encoding" 分组,并使用组大小创建序列,同时乘以 Noshow 以便仅保留以下值Noshow == 1

我们可以使用 base R 中的 rle(未使用包)。使用 ave,我们按 'PatientID' 分组,得到 'Noshow' 的 rle,将 'lengths' 的 sequence 乘以 'values'由 'lengths' 复制以获得预期的输出。

helperfn <- function(x) with(rle(x), sequence(lengths) * rep(values, lengths))
df$ConsecNoshows <- with(df, ave(Noshow, PatientID, FUN = helperfn))
df$ConsecNoshows 
#[1] 0 1 0 1 2 3 0 0 1 2 0 1

由于 OP 似乎正在使用 'tbl_df',dplyr 中的解决方案是

library(dplyr)
df %>%
   group_by(PatientID) %>%
   mutate(ConsecNoshows = helperfn(Noshow))
#     PatientID Noshow ConsecNoshows
#       <int>  <int>         <int>
#1          1      0             0
#2          1      1             1
#3          1      0             0
#4          1      1             1
#5          1      1             2
#6          1      1             3
#7          2      0             0
#8          2      0             0
#9          2      1             1
#10         2      1             2
#11         2      0             0
#12         2      1             1

我会创建一个辅助函数,然后使用您最熟悉的任何实现:

sum0 <- function(x) {x[x == 1]=sequence(with(rle(x), lengths[values == 1]));x}

#base R
transform(df1, Consec = ave(Noshow, PatientID, FUN=sum0))

#dplyr
library(dplyr)
df1 %>% group_by(PatientID) %>% mutate(Consec=sum0(Noshow))

#data.table
library(data.table)
setDT(df1)[, Consec := sum0(Noshow), by = PatientID]
  #    PatientID Noshow Consec
  #        <int>  <int>  <int>
  # 1          1      0      0
  # 2          1      1      1
  # 3          1      0      0
  # 4          1      1      1
  # 5          1      1      2
  # 6          1      1      3
  # 7          2      0      0
  # 8          2      0      0
  # 9          2      1      1
  # 10         2      1      2
  # 11         2      0      0
  # 12         2      1      1