【问题标题】:How to handle duplicates and favor one version but not always如何处理重复并偏爱一个版本,但并非总是如此
【发布时间】:2021-03-09 17:18:00
【问题描述】:

我有每个 id 重复行的数据,因为它是在两个不同的时间点收集的。根据以下规则,我想删除重复项并保留每个 id 一行:

对于每个id,保留最近时间点的值,除非最近的时间点缺失(NA);在这种情况下,取旧的时间点值。否则,如果两个时间点都是NA,则与NA保持一行。

示例数据

在此数据中,我们有 20 个人的体重被测量了两次。

df <- structure(list(id = c(1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 5L, 5L, 
6L, 6L, 7L, 7L, 8L, 8L, 9L, 9L, 10L, 10L), time_point = c("tp1", 
"tp2", "tp1", "tp2", "tp1", "tp2", "tp1", "tp2", "tp1", "tp2", 
"tp1", "tp2", "tp1", "tp2", "tp1", "tp2", "tp1", "tp2", "tp1", 
"tp2"), weight = c(56L, NA, 95L, 88L, 61L, NA, 55L, 87L, 87L, 
95L, NA, 96L, 88L, 90L, 72L, NA, NA, 67L, 52L, NA)), row.names = c(NA, 
20L), class = "data.frame")

> df

##    id time_point weight
## 1   1        tp1     56
## 2   1        tp2     NA
## 3   2        tp1     95
## 4   2        tp2     88
## 5   3        tp1     61
## 6   3        tp2     NA
## 7   4        tp1     55
## 8   4        tp2     87
## 9   5        tp1     87
## 10  5        tp2     95
## 11  6        tp1     NA
## 12  6        tp2     96
## 13  7        tp1     88
## 14  7        tp2     90
## 15  8        tp1     72
## 16  8        tp2     NA
## 17  9        tp1     NA
## 18  9        tp2     67
## 19 10        tp1     52
## 20 10        tp2     NA

当我们有那个人的两个weight 度量时,我们使用“tp2”版本,除非“tp2”是NA,然后我们会使用“tp1”。如果两者恰好都是NA,那么我们应该只保留一行NA

期望的输出

##    id time_point weight
## 1   1        tp1     56
## 2   2        tp2     88
## 3   3        tp1     61
## 4   4        tp2     87
## 5   5        tp2     95
## 6   6        tp2     96
## 7   7        tp2     90
## 8   8        tp1     72
## 9   9        tp2     67
## 10 10        tp1     52

例如,当我考虑使用dplyr 解决它时,我想它必须包括group_by(id)distinct() 之类的东西。但我真的不知道如何让它发挥作用。


编辑


我意识到上面的df 是我真实数据的过度简化示例。在实际数据中,我可能有更多的列需要根据我指定的去重算法进行处理。

例如,请参阅下面的df_2

df_2 <- structure(list(id = c(1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 5L, 5L, 
6L, 6L, 7L, 7L, 8L, 8L, 9L, 9L, 10L, 10L), time_point = c("tp1", 
"tp2", "tp1", "tp2", "tp1", "tp2", "tp1", "tp2", "tp1", "tp2", 
"tp1", "tp2", "tp1", "tp2", "tp1", "tp2", "tp1", "tp2", "tp1", 
"tp2"), weight = c(NA, 55L, 86L, NA, NA, NA, NA, NA, 92L, NA, 
71L, 90L, NA, NA, 72L, 93L, 60L, 81L, NA, NA), height = c(NA, 
78L, NA, 92L, 98L, NA, 66L, NA, NA, 73L, NA, NA, NA, 91L, NA, 
NA, NA, 58L, 63L, NA)), row.names = c(NA, 20L), class = "data.frame")

> df_2
##    id time_point weight height
## 1   1        tp1     NA     NA
## 2   1        tp2     55     78
## 3   2        tp1     86     NA
## 4   2        tp2     NA     92
## 5   3        tp1     NA     98
## 6   3        tp2     NA     NA
## 7   4        tp1     NA     66
## 8   4        tp2     NA     NA
## 9   5        tp1     92     NA
## 10  5        tp2     NA     73
## 11  6        tp1     71     NA
## 12  6        tp2     90     NA
## 13  7        tp1     NA     NA
## 14  7        tp2     NA     91
## 15  8        tp1     72     NA
## 16  8        tp2     93     NA
## 17  9        tp1     60     NA
## 18  9        tp2     81     58
## 19 10        tp1     NA     63
## 20 10        tp2     NA     NA

因此,预期的输出将是:

##    id weight height
## 1   1     55     78
## 2   2     86     92
## 3   3     NA     98
## 4   4     NA     66
## 5   5     92     73
## 6   6     90     NA
## 7   7     NA     91
## 8   8     93     NA
## 9   9     81     58
## 10 10     NA     63

【问题讨论】:

    标签: r dplyr duplicates


    【解决方案1】:

    您可以在 id 中锻炼测量的排名,并保留排名为 1 的记录

    library(data.table)
    
    setDT(df)
    df[order(weight, time_point), rn := 1:.N, id]
    res <- df[rn == 1]
    

    【讨论】:

    • 谢谢!尽管@akrun 的dplyr 解决方案很棒,但我现在意识到,考虑到大型数据集,这是一项耗时的操作。所以我也想探索data.table 选项。是否可以像df_2 演示的那样将此处的代码调整为超过 1 列的数据?
    【解决方案2】:

    我们可以先做一个arrange,然后在做一个group_by之后再slice

    library(dplyr)
    df %>%
       arrange(id, is.na(weight), desc(time_point)) %>%
       group_by(id) %>%
       slice_head(n = 1) %>%
       ungroup
    

    -输出

    # A tibble: 10 x 3
    #      id time_point weight
    #   <int> <chr>       <int>
    # 1     1 tp1            56
    # 2     2 tp2            88
    # 3     3 tp1            61
    # 4     4 tp2            87
    # 5     5 tp2            95
    # 6     6 tp2            96
    # 7     7 tp2            90
    # 8     8 tp1            72
    # 9     9 tp2            67
    #10    10 tp1            52
    

    或者在arrange之后,使用distinct

    df %>%
       arrange(id, is.na(weight), desc(time_point)) %>%
       distinct(id, .keep_all = TRUE)
    

    -输出

    #    id time_point weight
    #1   1        tp1     56
    #2   2        tp2     88
    #3   3        tp1     61
    #4   4        tp2     87
    #5   5        tp2     95
    #6   6        tp2     96
    #7   7        tp2     90
    #8   8        tp1     72
    #9   9        tp2     67
    #10 10        tp1     52
    

    如果有多个列,那么我们通过对这些列使用 across 汇总进行分组,根据 NA 值和 'time_point' 的降序对它们进行单独排序,得到 first 非NA元素

    df %>% 
        group_by(id) %>% 
        summarise(across(c(weight), ~ .[order(is.na(.),
              -as.integer(factor(time_point)))][1]), .groups = 'drop')
    

    -输出

    # A tibble: 10 x 2
    #      id weight
    # * <int>  <int>
    # 1     1     56
    # 2     2     88
    # 3     3     61
    # 4     4     87
    # 5     5     95
    # 6     6     96
    # 7     7     90
    # 8     8     72
    # 9     9     67
    #10    10     52
    

    对于更新后的数据集“df_2”

    df_2 %>% 
        group_by(id) %>% 
        summarise(across(c(weight, height), ~ .[order(is.na(.),
               -as.integer(factor(time_point)))][1]), .groups = 'drop')
    # A tibble: 10 x 3
    #      id weight height
    # * <int>  <int>  <int>
    # 1     1     55     78
    # 2     2     86     92
    # 3     3     NA     98
    # 4     4     NA     66
    # 5     5     92     73
    # 6     6     90     NA
    # 7     7     NA     91
    # 8     8     93     NA
    # 9     9     81     58
    #10    10     NA     63
    

    【讨论】:

    • 谢谢,我认为我使用了一个过于简单的数据示例。在现实生活中,我有很多专栏,而不仅仅是weight。但是您的解决方案仅适用于一个特定的 col。是否有可能拥有一个全局版本的解决方案,可以在任何列上应用这种重复删除(id 除外)?
    • @Emman 如果有更多列,您可以使用arrange(across
    • @Emman 那不会也造成一些冲突。即假设对于 id 1,权重列有第一个值 NA,第二列有第二个值 NA,那么,您将选择哪个 time_point
    • 刚刚添加了预期的输出。您的最终方法成功了。谢谢你,很抱歉给你带来麻烦。我的原始数据太简单了,引起了整个混乱。再次感谢!
    • @Emman 您可以按照您希望它出现的顺序指定factor(time_point, levels = c("foo", "blah")) 来进行更改
    猜你喜欢
    • 1970-01-01
    • 2019-11-29
    • 2015-07-01
    • 2013-10-20
    • 2019-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多