R:如何根据另一个变量中的值选择 dplyr::distinct() 保留哪一行?

R: How do I choose which row dplyr::distinct() keeps based on a value in another variable?

现实生活中的问题:我的受试者有 MRI 扫描数据。其中一些已被多次扫描(单独的行)。其中一些每次都根据不同的协议进行扫描。我想按主题 ID 保留所有唯一的行,如果一个主题是在两种不同的协议下扫描的,我希望它更喜欢一个。

玩具示例:

library(dplyr)  
df <- tibble(
        id = c("A", "A", "B", "C", "C", "D"), 
        protocol = c("X", "Y", "X", "X", "X", "Y"),
        date = c(seq(as.Date("2018-01-01"), as.Date("2018-01-06"), 
                 by="days")),
        var = 1:6)

我想 return 一个包含所有唯一主题的数据框(按 ID)。当涉及到重复值时,我希望它保留 "Y" 作为协议的条目,而不是自动保留第一个条目,如果它有这样的选择,但不要删除带有 [=35= 的行] 除此以外。

在示例中,它将保留第 2、3、4 和 6 行。

我更喜欢 dplyr,但我愿意接受其他建议。

我试过的任何东西都不起作用:

df %>% distinct(id, .keep_all = TRUE) #Nope! 

df %>% distinct(id, protocol == "Y", .keep_all = TRUE) #Nope!  

df$protocol <- factor(df$protocol, levels = c("Y", "X"))
df %>% distinct(id, .keep_all = TRUE) #Nope!  

df %>% group_by(id) %>% filter(protocol == "Y") #Nope!

两个好的答案: @RobJensen 建议

df %>% arrange(id, desc(protocol == 'Y')) %>% distinct(id, .keep_all = TRUE)  

如果我有多个协议并希望分配它们的选择顺序,我可以创建一个新变量,在其中我按优先顺序为协议分配一个整数,然后使用@joran

df %>% group_by(id) %>% arrange(desc(protocol),var) %>% slice(1)  

谢谢!

可能有更快的方法(几乎可以肯定是 data.table),但这将是 dplyr 中的天真直接方法,我认为:

df %>% group_by(id) %>% arrange(desc(protocol),var) %>% do(head(.,1))

正如@Gregor 在下面指出的(现已删除),slice(1) 可能是 do(head(.,1)) 的更好成语。

如果您希望输出不是 grouped_df 的小标题,则无需使用 group_by() 即可实现此目的。

df %>% arrange(id, desc(protocol)) %>% distinct(id, .keep_all = TRUE)

您可以将该过程分为两个步骤:抓住必备的东西,抓住其他 ID 的任何东西,然后合并。

distinct_y <- df %>%
  filter(protocol == "Y") %>%
  distinct(id, .keep_all = TRUE)

distinct_other <- df %>%
  anti_join(distinct_y, "id") %>%
  distinct(id, .keep_all = TRUE)

distinct_combined <- rbind(distinct_y, distinct_other)

如果您想将其从 "one above all" 概括为值的排序,我建议将 protocol 作为一个因素。

例如,假设有三个协议:X、Y 和 Z。Y 是最好的,Z 比 X 好,如果没有更好的,你只想要 X。

# Only difference is the best protocol for C will now be Z.
df2 <- tibble(
  id = c("A", "A", "B", "C", "C", "D"),
  protocol = c("X", "Y", "X", "X", "Z", "Y"),
  date = c(seq(as.Date("2018-01-01"), as.Date("2018-01-06"),
               by="days")),
  var = 1:6
)

order_of_importance <- c("Y", "Z", "X")

df2 %>%
  mutate(protocol = factor(protocol, order_of_importance)) %>%
  group_by(id) %>%
  arrange(protocol) %>%
  slice(1)
# # A tibble: 4 x 4
# # Groups: id [4]
#   id    protocol date         var
#   <chr> <fctr>   <date>     <int>
# 1 A     Y        2018-01-02     2
# 2 B     X        2018-01-03     3
# 3 C     Z        2018-01-05     5
# 4 D     Y        2018-01-06     6

按字母顺序排列在所述的简单情况下有效,但如果你愿意,你可以添加一个 protocol_preference 变量来给出你希望 selected 的顺序,如果 [=13] =] 不可用,并且 select "Y" 即使按字母顺序排序时它恰好不是最后一个协议值。

建立@davechilders 的答案和@Nathan Werth 基于"order of importance" 向量

创建因子的想法
order_of_importance <- c("Y", "Z", "X")

    df2 %>%
      mutate(protocol = factor(protocol, order_of_importance)) %>%
      arrange(id, protocol) %>%
      distinct(id, .keep_all = TRUE)

或者,如果您只想 select 'Y' 并且对 select 的内容没有偏好,如果 'Y' 不可用,您可以

df %>% 
    arrange(id, desc(protocol == 'Y')) %>% 
    distinct(id, .keep_all = TRUE)