【问题标题】:Inclusion/Exclusion of rows in a DataFrame, based on specific criteria根据特定标准在 DataFrame 中包含/排除行
【发布时间】:2016-04-21 23:07:58
【问题描述】:

我有大量数据,其中包含许多人的病理学测试数据。我提供了一个按比例缩小的数据集来描述案例的类型。

library(plyr)
library(tidyr)
library(dplyr)
library(lubridate)

options(stringsAsFactors = FALSE)
dat <- structure(list(PersID = c("am1", "am2", "am2", "am3", "am3", "am4", "am4", "am4", "am4", "am4", "am4"), Sex = c("M", "F","F", "M", "M", "F", "F", "F", "F", "F", "F"), DateTested = c("21/10/2015", "9/07/2010", "24/09/2010", "23/10/2013", "25/10/2013", "28/04/2010", "23/06/2010", "21/07/2010", "20/10/2010", "4/03/2011", "2/12/2011"), Res = c("NR", "R", "R", "NR", "R", "R", "R", "R", "R", "R", "R"), Status = c("Yes", "No", "No", "Yes", "Yes", "No", "No", "No", "No", "No", "No"), DateOrder = c(1L, 1L, 2L, 1L, 2L, 1L, 2L, 3L, 4L, 5L, 6L)), .Names = c("PersID", "Sex", "DateTested", "Res", "Status", "DateOrder"), class = "data.frame", row.names = c(NA, -11L))

数据描述了三种类型的人(1)只有一个结果的人(2)有 2 个结果的人,以及(3)有很多结果的人。

我的目标是提出一个脚本,该脚本仅根据一组标准包含个人行。从技术上讲,这是一种仅在其后续结果在指定的再感染期(30 天)内计算个人行数的方法。

我已将我的数据转换为一个列表,并向它传递了一些函数以开始处理数据。

dat$DateTested <- dmy(dat$DateTested)
datList <- dlply(.data=dat, .variables=c('PersID'))

到目前为止我所做的是:

选择每个人只有一个结果的所有行

fnSingleTests <- function(y){
    y <- y[length(y$DateOrder)==1,]
}

singleTests <- ldply(datList, fnSingleTests, .id = NULL)

将数据框转换为列表并传递一个函数 确定 (a) 在 30 天内每人是否有两行 再感染期,则选择第一个,并且(b)如果有 每人超过两行,最后一条记录和第一条记录 记录在 30 天内,只保留第一个。

fnMultiTests <- function(y){
    y <- y[length(y$DateOrder) > 1,]
}

multiTests <- llply(datList, fnMultiTests)

fnMultiTestsSplit <- function(y){

    test <- difftime(y$DateTested[length(y$DateTested)], y$DateTested[1], units='days')


    if (nrow(y) <=2){

        if (test < 31){
            y <- y[y$DateOrder == 1, ]
            y <- y[!is.na(y$PerdID), ]
        } else {
            y <- y[y$DateOrder %in% 1:2, ]
            y <- y[!is.na(y$PersID), ]
        }

    } else  {
        if (test < 31){
            y <- y[y$DateOrder == 1, ]
            y <- y[!is.na(y$PersID), ]
        } else {
            break()
        }

    }
}

finalTests <-  ldply(multiTests, failwith(NULL, fnMultiTestsSplit, quiet = TRUE), .id = NULL)

然后我可以用 rbind 组合数据帧:

allFinalTests &lt;- rbind(singleTests, finalTests)

我被卡住的地方是每人有两排以上的情况,在连续的几排中,可能会有一段时间超过 30 天的再感染期的情况。

谁能建议我如何扩展此代码以仅包含两个以上PersID 的情况,然后仅包含在 30 天再感染期之外发生后续病例的结果。

具体来说,从最早的案例开始,如果下一个案例在 30 天内,则排除第二个案例,或者如果第二个案例距离上一个案例超过 30 天,则包括两个案例。对于相同的PersID

,它应该对所有情况执行此操作

在这个例子中,我正在寻找的最终输出是:

PersID  Sex DateTested  Res Status  DateOrder
am1 M   21/10/2015  NR  Yes 1
am2 F   9/07/2010   R   No  1
am2 F   24/09/2010  R   No  2
am3 M   23/10/2013  NR  Yes 1
am4 F   28/04/2010  R   No  1
am4 F   23/06/2010  R   No  2
am4 F   20/10/2010  R   No  4
am4 F   4/03/2011   R   No  5
am4 F   2/12/2011   R   No  6

【问题讨论】:

    标签: r dataframe


    【解决方案1】:

    在 1.9.8 版(CRAN 2016 年 11 月 25 日)中,data.table 包获得了inrange() 函数,该函数利用非等值连接执行范围连接。 p>

    分别使用inrange()%inrange% 运算符,可以达到预期的结果

    library(data.table) # CRAN version 1.10.4-2 used
    data.table(dat)[, DateTested := as.IDate(DateTested, "%d/%m/%Y")][
      , .SD[!DateTested %inrange% list(DateTested + 1L, DateTested + 30L)], by = PersID]
    
       PersID Sex DateTested Res Status DateOrder
    1:    am1   M 2015-10-21  NR    Yes         1
    2:    am2   F 2010-07-09   R     No         1
    3:    am2   F 2010-09-24   R     No         2
    4:    am3   M 2013-10-23  NR    Yes         1
    5:    am4   F 2010-04-28   R     No         1
    6:    am4   F 2010-06-23   R     No         2
    7:    am4   F 2010-10-20   R     No         4
    8:    am4   F 2011-03-04   R     No         5
    9:    am4   F 2011-12-02   R     No         6
    

    对于每个PersID,它会查找在日期范围内的任何其他条目 [第二天,30 天后]。这些被排除在结果之外。

    可以通过以下方式显示排除的行:

    data.table(dat)[, DateTested := as.IDate(DateTested, "%d/%m/%Y")][
      , .SD[DateTested %inrange% list(DateTested + 1L, DateTested + 30L)], by = PersID]
    
       PersID Sex DateTested Res Status DateOrder
    1:    am3   M 2013-10-25   R    Yes         2
    2:    am4   F 2010-07-21   R     No         3
    

    【讨论】:

      【解决方案2】:

      在基础 R 中,我将按如下方式处理它:

      # convert the 'DateTested' column to a date-format
      dat$DateTested <- as.Date(dat$DateTested, format = "%d/%m/%Y")
      # calculate the difference in days with the previous observation in the group
      dat$tdiff <- unlist(tapply(dat$DateTested, INDEX = dat$PersID,
                                 FUN = function(x) c(0, `units<-`(diff(x), "days"))))
      # filter the observations that have either a timedifference of zero or more 
      dat[(dat[,"tdiff"]==0 | dat[,"tdiff"] > 30),]
      

      给出:

         PersID Sex DateTested Res Status DateOrder tdiff
      1     am1   M 2015-10-21  NR    Yes         1     0
      2     am2   F 2010-07-09   R     No         1     0
      3     am2   F 2010-09-24   R     No         2    77
      4     am3   M 2013-10-23  NR    Yes         1     0
      6     am4   F 2010-04-28   R     No         1     0
      7     am4   F 2010-06-23   R     No         2    56
      9     am4   F 2010-10-20   R     No         4    91
      10    am4   F 2011-03-04   R     No         5   135
      11    am4   F 2011-12-02   R     No         6   273
      

      使用 data.table 包:

      library(data.table)
      # convert the 'data.frame' to a 'data.table'
      # and convert the 'DateTested' column to a date-format
      setDT(dat)[, DateTested := as.Date(DateTested, format = "%d/%m/%Y")]
      # calculate the difference in days with the previous observation in the group
      dat[, tdiff := c(0, `units<-`(diff(DateTested), "days")), PersID]
      # filter the observations that have either a timedifference of zero or more than 30 days
      dat[(tdiff==0 | tdiff > 30)]
      

      这会给你同样的结果。您也可以将其链接在一起,如下所示:

      setDT(dat)[, DateTested := as.Date(DateTested, format = "%d/%m/%Y")
                 ][, tdiff := c(0, `units<-`(diff(DateTested), "days")), by = PersID
                   ][(tdiff==0 | tdiff > 30)]
      

      并使用 dplyr

      library(dplyr)
      dat %>% 
        mutate(DateTested = as.Date(DateTested, format = "%d/%m/%Y")) %>%
        group_by(PersID) %>%
        mutate(tdiff = c(0, `units<-`(diff(DateTested), "days"))) %>%
        filter(tdiff == 0 | tdiff > 30)
      

      这也会给你同样的结果。

      【讨论】:

      • 这是一个非常优雅和彻底的解决方案。我已经使用了 dplyr 版本。我真的很感激。
      • SBista 的proposal 我觉得更简洁、更优雅。 OP 显然使用的是dplyr,所以这个答案有相当多的内容。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-17
      • 2016-10-17
      • 1970-01-01
      • 2018-09-30
      • 2019-01-17
      • 1970-01-01
      相关资源
      最近更新 更多