【问题标题】:Merge/combine rows with same ID and Date in R在R中合并/组合具有相同ID和日期的行
【发布时间】:2021-10-31 12:49:54
【问题描述】:

我有一个如下所示的 excel 数据库。 Excel 数据库可以选择仅输入 3 个药物详细信息。凡超过 3 种药物的,都已用 PID 和 Date 输入另一行。

有没有一种方法可以合并 R 中的行,以便每个患者的记录都在一行中?在下面的示例中,我需要合并第 1 & 2 行和第 4 & 6 行。

谢谢。

Row PID Date Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place
1 11A 25/10/2021 RPG 12 NAT 34 QRT 5 45 PMk
2 11A 25/10/2021 BET 10 SET 43 BLT 45
3 12B 20/10/2021 ATY 13 LTP 3 CRT 3 56 GTL
4 13A 22/10/2021 GGS 7 GSF 12 ERE 45 45 RKS
5 13A 26/10/2021 BRT 9 ARR 4 GSF 34 46 GLO
6 13A 22/10/2021 DFS 5
7 14B 04/08/2021 GDS 2 TRE 55 HHS 34 25 MTK

【问题讨论】:

    标签: r


    【解决方案1】:

    首先,以下两种方法完全不同,不是“base R vs dplyr”中的等价物。我确信其中一个可以翻译成另一个。

    dplyr

    这里的前提是首先重塑/旋转数据更长,以便每个药物/剂量都在自己的行上,适当地重新编号,然后将其恢复到宽状态。 p>

    注意:坦率地说,我通常更喜欢处理 long 格式的数据,因此请考虑将其保持在紧邻 pivot_wider 之前的状态。这意味着您需要以某种方式将AgePlace 带回其中。

    为什么?长格式可以很好地处理多种类型的聚合; ggplot2 真的很喜欢长格式的数据;我不喜欢看到并且不得不处理所有NA/empty 值,因为许多PID 没有(例如)Drug6 或更高版本。这似乎是主观的,但它确实可以是对数据处理的客观更改/改进,具体取决于您的工作流程。

    library(dplyr)
    # library(tidyr) # pivot_longer, pivot_wider
    dat0 <- select(dat, PID, Date, Age, Place) %>%
      group_by(PID, Date) %>%
      summarize(across(everything(), ~ .[!is.na(.) & nzchar(trimws(.))][1] ))
    dat %>%
      select(-Age, -Place) %>%
      tidyr::pivot_longer(
        -c(Row, PID, Date),
        names_to = c(".value", "iter"),
        names_pattern = "^([^0-9]+)([123]?)$") %>%
      arrange(Row, iter) %>%
      group_by(PID, Date) %>%
      mutate(iter = row_number()) %>%
      select(-Row) %>%
      tidyr::pivot_wider(
        c("PID", "Date"), names_sep = "",
        names_from = "iter", values_from = c("Drug", "Dose")) %>%
      left_join(dat0, by = c("PID", "Date"))
    # # A tibble: 5 x 16
    # # Groups:   PID, Date [5]
    #   PID   Date       Drug1 Drug2 Drug3 Drug4 Drug5 Drug6 Dose1 Dose2 Dose3 Dose4 Dose5 Dose6   Age Place
    #   <chr> <chr>      <chr> <chr> <chr> <chr> <chr> <chr> <int> <int> <int> <int> <int> <int> <int> <chr>
    # 1 11A   25/10/2021 RPG   NAT   QRT   BET   "SET" "BLT"    12    34     5    10    43    45    45 PMk  
    # 2 12B   20/10/2021 ATY   LTP   CRT   <NA>   <NA>  <NA>    13     3     3    NA    NA    NA    56 GTL  
    # 3 13A   22/10/2021 GGS   GSF   ERE   DFS   ""    ""        7    12    45     5    NA    NA    45 RKS  
    # 4 13A   26/10/2021 BRT   ARR   GSF   <NA>   <NA>  <NA>     9     4    34    NA    NA    NA    46 GLO  
    # 5 14B   04/08/2021 GDS   TRE   HHS   <NA>   <NA>  <NA>     2    55    34    NA    NA    NA    25 MTK  
    

    注意事项:

    • 我很早就爆发了dat0,因为AgePlace 并不真正适合枢轴/重新编号/枢轴心态。

    基础 R

    这是一个基本 R 方法,它拆分(根据您的分组标准:PIDDate),找到需要重新编号的 Drug/Dose 列,重命名它们,以及 merges 所有帧重新组合在一起。

    spl <- split(dat, ave(rep(1L, nrow(dat)), dat[,c("PID", "Date")], FUN = seq_along))
    spl
    # $`1`
    #   Row PID       Date Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place
    # 1   1 11A 25/10/2021   RPG    12   NAT    34   QRT     5  45   PMk
    # 3   3 12B 20/10/2021   ATY    13   LTP     3   CRT     3  56   GTL
    # 4   4 13A 22/10/2021   GGS     7   GSF    12   ERE    45  45   RKS
    # 5   5 13A 26/10/2021   BRT     9   ARR     4   GSF    34  46   GLO
    # 7   7 14B 04/08/2021   GDS     2   TRE    55   HHS    34  25   MTK
    # $`2`
    #   Row PID       Date Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place
    # 2   2 11A 25/10/2021   BET    10   SET    43   BLT    45  NA      
    # 6   6 13A 22/10/2021   DFS     5          NA          NA  NA      
    
    nms <- lapply(spl, function(x) grep("^(Drug|Dose)", colnames(x), value = TRUE))
    nms <- data.frame(i = rep(names(nms), lengths(nms)), oldnm = unlist(nms))
    nms$grp <- gsub("[0-9]+$", "", nms$oldnm)
    nms$newnm <- paste0(nms$grp, ave(nms$grp, nms$grp, FUN = seq_along))
    nms <- split(nms, nms$i)
    
    newspl <- Map(function(x, nm) {
      colnames(x)[ match(nm$oldnm, colnames(x)) ] <- nm$newnm
      x
    }, spl, nms)
    newspl[-1] <- lapply(newspl[-1], function(x) x[, c("PID", "Date", grep("^(Drug|Dose)", colnames(x), value = TRUE)), drop = FALSE ])
    newspl
    # $`1`
    #   Row PID       Date Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place
    # 1   1 11A 25/10/2021   RPG    12   NAT    34   QRT     5  45   PMk
    # 3   3 12B 20/10/2021   ATY    13   LTP     3   CRT     3  56   GTL
    # 4   4 13A 22/10/2021   GGS     7   GSF    12   ERE    45  45   RKS
    # 5   5 13A 26/10/2021   BRT     9   ARR     4   GSF    34  46   GLO
    # 7   7 14B 04/08/2021   GDS     2   TRE    55   HHS    34  25   MTK
    # $`2`
    #   PID       Date Drug4 Dose4 Drug5 Dose5 Drug6 Dose6
    # 2 11A 25/10/2021   BET    10   SET    43   BLT    45
    # 6 13A 22/10/2021   DFS     5          NA          NA
    
    Reduce(function(a, b) merge(a, b, by = c("PID", "Date"), all = TRUE), newspl)
    #   PID       Date Row Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place Drug4 Dose4 Drug5 Dose5 Drug6 Dose6
    # 1 11A 25/10/2021   1   RPG    12   NAT    34   QRT     5  45   PMk   BET    10   SET    43   BLT    45
    # 2 12B 20/10/2021   3   ATY    13   LTP     3   CRT     3  56   GTL  <NA>    NA  <NA>    NA  <NA>    NA
    # 3 13A 22/10/2021   4   GGS     7   GSF    12   ERE    45  45   RKS   DFS     5          NA          NA
    # 4 13A 26/10/2021   5   BRT     9   ARR     4   GSF    34  46   GLO  <NA>    NA  <NA>    NA  <NA>    NA
    # 5 14B 04/08/2021   7   GDS     2   TRE    55   HHS    34  25   MTK  <NA>    NA  <NA>    NA  <NA>    NA
    

    注意事项:

    • 这样做的基本前提是您希望将这些行合并到之前的行中。这意味着(对我而言)使用base::mergedplyr::full_join;了解这些概念的两个很好的链接,以防您不知道:How to join (merge) data frames (inner, outer, left, right)What's the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN?

    • 为此,我们需要确定哪些行与之前的行重复;此外,我们需要知道有多少先前的相同键行。有几种方法可以做到这一点,但我认为最简单的方法是使用base::split。在这种情况下,没有 PID/Date 组合有超过两行,但如果您有一个组合要求第三行,spl 的长度为 3,结果名称将输出到 Drug9/@987654342 @。

    • 第二部分 (nms &lt;- ...) 是我们处理名称的地方。前几个步骤创建了一个nms 数据框,我们将使用它来从旧名称映射到新名称。由于我们关心所有多行组的连续编号,因此我们在药物/剂量名称的基数(删除的编号)上进行聚合,以便我们从Drug1 到有多少列编号所有Drug 列。

      注意:这里假设总是有完美的Drug#/Dose#;如果有任何不匹配,那么编号将是可疑的。

      我们以 nms 结束,这是一个拆分数据框,就像数据的 spl 一样。这是有用且重要的,因为我们会将它们Map(类似zip 的lapply)放在一起。

    • 第三个块用新名称更新splnewspl 中的结果只是重命名了列,这样当我们将它们合并在一起时,就不会发生列重复。

      这里还有一个步骤是从列表中的第二个和后续帧中删除不相关的列。也就是说,我们将AgePlace 保留在第一个这样的帧中,但将其从其余帧中删除。我的假设(基于重复行中这些字段的NA/empty 性质)是我们只想保留第一行的值。

    • 最后一步是迭代地merge 他们在一起。 Reduce 函数很适合这个。

    【讨论】:

      【解决方案2】:

      另一个基于tidyverse 的解决方案,pivot_longer 后跟pivot_wider

      library(tidyverse)
      
      # Note that my dataframe does not contain column Row
      
      df %>% 
        mutate(across(starts_with("Dose"), as.character)) %>% 
        pivot_longer(!c(PID, Date, Age, Place),names_to = "trm") %>% 
        group_by(PID, Date) %>% 
        fill(Age, Place) %>% 
        mutate(trm = paste(trm,1:n(),sep="_")) %>% 
        ungroup %>% 
        pivot_wider(c(PID, Date, Age, Place), names_from = trm) %>% 
        rename_with(~ paste0("Drug",1:length(.x)), starts_with("Drug")) %>% 
        rename_with(~ paste0("Dose",1:length(.x)), starts_with("Dose")) %>% 
        mutate(across(starts_with("Dose"), as.numeric))
      
      #> # A tibble: 5 × 16
      #>   PID   Date     Age Place Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Drug4 Dose4 Drug5
      #>   <chr> <chr>  <int> <chr> <chr> <dbl> <chr> <dbl> <chr> <dbl> <chr> <dbl> <chr>
      #> 1 11A   25/10…    45 PMk   RPG      12 NAT      34 QRT       5 BET      10 SET  
      #> 2 12B   20/10…    56 GTL   ATY      13 LTP       3 CRT       3 <NA>     NA <NA> 
      #> 3 13A   22/10…    45 RKS   GGS       7 GSF      12 ERE      45 DFS       5 <NA> 
      #> 4 13A   26/10…    46 GLO   BRT       9 ARR       4 GSF      34 <NA>     NA <NA> 
      #> 5 14B   04/08…    25 MTK   GDS       2 TRE      55 HHS      34 <NA>     NA <NA> 
      #> # … with 3 more variables: Dose5 <dbl>, Drug6 <chr>, Dose6 <dbl>
      

      【讨论】:

        【解决方案3】:

        data.table 方法

        library(data.table)
        DT <- fread("Row    PID     Date    Drug1   Dose1   Drug2   Dose2   Drug3   Dose3   Age     Place
                    1   11A     25/10/2021  RPG     12  NAT     34  QRT     5   45  PMk
                    2   11A     25/10/2021  BET     10  SET     43  BLT     45      
                    3   12B     20/10/2021  ATY     13  LTP     3   CRT     3   56  GTL
                    4   13A     22/10/2021  GGS     7   GSF     12  ERE     45  45  RKS
                    5   13A     26/10/2021  BRT     9   ARR     4   GSF     34  46  GLO
                    6   13A     22/10/2021  DFS     5                       
                    7   14B     04/08/2021  GDS     2   TRE     55  HHS     34  25  MTK")
        
        dcast(DT)
        
        
        DT
        # Melt to long format
        ans <- melt(DT, id.vars = c("PID", "Date"), 
             measure.vars = patterns(drug = "^Drug", dose = "^Dose"), 
             na.rm = TRUE)
        # Paste and Collapse, use ; as separator
        ans <- ans[, lapply(.SD, paste0, collapse = ";"), by = .(PID, Date)]
        # Split string on ;
        ans[, paste0("Drug", 1:length(tstrsplit(ans$drug, ";"))) := tstrsplit(drug, ";")]
        ans[, paste0("Dose", 1:length(tstrsplit(ans$dose, ";"))) := tstrsplit(dose, ";")]
        #join Age + Place data
        ans[DT[!is.na(Age), ], `:=`(Age = i.Age, Place = i.Place), on = .(PID, Date)]
        ans[, -c("variable", "drug", "dose")]
        #    PID       Date Drug1 Drug2 Drug3 Drug4 Drug5 Drug6 Dose1 Dose2 Dose3 Dose4 Dose5 Dose6 Age Place
        # 1: 11A 25/10/2021   RPG   BET   NAT   SET   QRT   BLT    12    10    34    43     5    45  45   PMk
        # 2: 12B 20/10/2021   ATY   LTP   CRT  <NA>  <NA>  <NA>    13     3     3  <NA>  <NA>  <NA>  56   GTL
        # 3: 13A 22/10/2021   GGS   DFS   GSF   ERE  <NA>  <NA>     7     5    12    45  <NA>  <NA>  45   RKS
        # 4: 13A 26/10/2021   BRT   ARR   GSF  <NA>  <NA>  <NA>     9     4    34  <NA>  <NA>  <NA>  46   GLO
        # 5: 14B 04/08/2021   GDS   TRE   HHS  <NA>  <NA>  <NA>     2    55    34  <NA>  <NA>  <NA>  25   MTK
        

        【讨论】:

          【解决方案4】:

          更新:

          在 akrun 的帮助下,请参见此处:Use ~separate after mutate and across

          我们可以:

          library(dplyr)
          library(stringr)
          library(tidyr)
          df %>% 
            group_by(PID) %>% 
            summarise(across(everything(), ~toString(.))) %>% 
            mutate(across(everything(), ~ list(tibble(col1 = .) %>% 
                                       separate(col1, into = str_c(cur_column(), 1:3), sep = ",\\s+", fill = "left", extra = "drop")))) %>% 
            unnest(c(PID, Row, Date, Drug1, Dose1, Drug2, Dose2, Drug3, Dose3, Age, 
                     Place)) %>% 
            distinct() %>% 
            select(-1, -2)
          
            PID3  Row1  Row2  Row3  Date1      Date2      Date3      Drug11 Drug12 Drug13 Dose11 Dose12 Dose13 Drug21 Drug22 Drug23 Dose21 Dose22 Dose23 Drug31 Drug32 Drug33 Dose31 Dose32 Dose33 Age1  Age2  Age3  Place1 Place2 Place3
            <chr> <chr> <chr> <chr> <chr>      <chr>      <chr>      <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr>  <chr> <chr> <chr> <chr>  <chr>  <chr> 
          1 11A   NA    1     2     NA         25/10/2021 25/10/2021 NA     RPG    BET    NA     12     10     NA     NAT    SET    NA     34     43     NA     QRT    BLT    NA     5      45     NA    45    NA    NA     PMk    NA    
          2 12B   NA    NA    3     NA         NA         20/10/2021 NA     NA     ATY    NA     NA     13     NA     NA     LTP    NA     NA     3      NA     NA     CRT    NA     NA     3      NA    NA    56    NA     NA     GTL   
          3 13A   4     5     6     22/10/2021 26/10/2021 22/10/2021 GGS    BRT    DFS    7      9      5      GSF    ARR    NA     12     4      NA     ERE    GSF    NA     45     34     NA     45    46    NA    RKS    GLO    NA    
          4 14B   NA    NA    7     NA         NA         04/08/2021 NA     NA     GDS    NA     NA     2      NA     NA     TRE    NA     NA     55     NA     NA     HHS    NA     NA     34     NA    NA    25    NA     NA     MTK   
          

          第一个答案: 牢记@r2evans 的出色解释!如果真的需要,我们可以这样做。

          library(dplyr)
          df %>% 
            group_by(PID) %>% 
            summarise(across(everything(), ~toString(.)))
          

          输出:

            PID   Row     Date                               Drug1         Dose1   Drug2        Dose2     Drug3        Dose3      Age        Place       
            <chr> <chr>   <chr>                              <chr>         <chr>   <chr>        <chr>     <chr>        <chr>      <chr>      <chr>       
          1 11A   1, 2    25/10/2021, 25/10/2021             RPG, BET      12, 10  NAT, SET     34, 43    QRT, BLT     5, 45      45, NA     PMk, NA     
          2 12B   3       20/10/2021                         ATY           13      LTP          3         CRT          3          56         GTL         
          3 13A   4, 5, 6 22/10/2021, 26/10/2021, 22/10/2021 GGS, BRT, DFS 7, 9, 5 GSF, ARR, NA 12, 4, NA ERE, GSF, NA 45, 34, NA 45, 46, NA RKS, GLO, NA
          4 14B   7       04/08/2021                         GDS           2       TRE          55        HHS          34         25         MTK  
          

          【讨论】:

            【解决方案5】:

            节日的另一个答案。

            从此页面读取数据

            require(rvest)
            require(tidyverse)
            d = read_html("https://stackoverflow.com/q/69787018/694915") %>%
              html_nodes("table") %>%
              html_table(fill = TRUE) 
            

            每个 PID 和 DATE 的剂量列表

            # primera tabla
            d[[1]]  -> df
            
            df %>% 
              pivot_longer(
                cols = starts_with("Drug"),
                values_to = "Drug"
              ) %>%
              select( !name ) %>% 
              pivot_longer(
                cols = starts_with("Dose"),
                values_to = "Dose"
              ) %>%
              select( !name ) %>%
              drop_na() %>%  
              pivot_wider(
                names_from = Drug,
                values_from = Dose ,
                values_fill = list(0)
              ) -> dose
            

            可变剂量包含此数据

            (https://i.stack.imgur.com/lc3iN.png)

            不像以前的那么优雅,但可以查看每个 PID 的整个处理。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-10-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-10-08
              • 2019-08-02
              相关资源
              最近更新 更多