【问题标题】:Replace a subset of a data frame with dplyr join operations用 dplyr 连接操作替换数据帧的子集
【发布时间】:2017-07-05 15:16:46
【问题描述】:

假设我对数据框的某些列值进行了如下处理:

  id animal weight   height ...
  1    dog     23.0
  2    cat     NA
  3   duck     1.2
  4  fairy     0.2
  5  snake     BAD


df <- data.frame(id = seq(1:5),
             animal = c("dog", "cat", "duck", "fairy", "snake"),
             weight = c("23", NA, "1.2", "0.2",  "BAD"))

假设处理需要在单独的表中工作,并作为结果给出以下数据框,它是原始数据框的子集:

  id animal weight
  2    cat    2.2
  5  snake    1.3

sub_df <- data.frame(id = c(2, 5),
             animal = c("cat", "snake"),
             weight = c("2.2", "1.3"))

现在我想再次将所有内容放在一起,所以我使用这样的操作:

> df %>%
   anti_join(sub_df, by = c("id", "animal")) %>%
   bind_rows(sub_df)

 id animal weight
 4  fairy    0.2
 1    dog   23.0
 3   duck    1.2
 2    cat    2.2
 5  snake    1.3

是否有某种方法可以直接通过连接操作来执行此操作?

如果子集只是关键列和要进行治疗的变量 (id,动物体重) 而不是总变量原始数据框(id, animal, weight, height)如何将子集与原始集合组装起来?

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    您描述的是一个连接操作,您可以在其中更新原始数据集中的一些值。这很容易使用data.table 实现出色的性能,因为它具有快速连接和按引用更新的概念 (:=)。

    这是您的玩具数据的示例:

    library(data.table)
    setDT(df)             # convert to data.table without copy
    setDT(sub_df)         # convert to data.table without copy
    
    # join and update "df" by reference, i.e. without copy 
    df[sub_df, on = c("id", "animal"), weight := i.weight]
    

    数据现已更新:

    #   id animal weight
    #1:  1    dog   23.0
    #2:  2    cat    2.2
    #3:  3   duck    1.2
    #4:  4  fairy    0.2
    #5:  5  snake    1.3
    

    你可以使用setDF切换回普通的data.frame

    【讨论】:

    • docendo discimus 完美运行。现在,如果您了解 dplyr 方法,我的具体观点是我使用了两个中间步骤再次连接这两个表。首先,添加处理表中未涉及的其余变量(同质列数),第二步从主数据框中删除处理过的观察值(使用 anti_join),最后逐行添加第一步的表。
    • 可爱的答案,但没有回答发布的问题
    • @Nettle,好吧,既然它已被 OP 接受,它似乎确实在一定程度上回答了这个问题。请记住,仅仅因为 OP 要求提供 dplyr 答案并不意味着非 dplyr 答案不正确或没有帮助。在我看来,在这种特定情况下,data.table 提供了比 dplyr 更好的解决方案。
    • @docendo,完全同意你的看法。 Dplyr 似乎没有针对此常见任务的直接解决方案,您的回答非常简洁。原始机智的变化,但同样不令人满意,将是:df %&gt;% filter(!id %in% sub_df$id) %&gt;% bind_rows(sub_df)
    【解决方案2】:

    先去掉 na,然后简单地堆叠 tibbles:

     bind_rows(filter(df,!is.na(weight)),sub_df)
    

    【讨论】:

      【解决方案3】:

      对于正在寻找在 tidyverse 管道中使用的解决方案的任何人:

      我经常遇到这个问题,并编写了一个简短的函数,主要使用 tidyverse 动词来解决这个问题。它将考虑原始 df 中有其他列的情况。

      例如,如果 OP 的 df 有一个额外的“高度”列:

      library(dplyr)
      
      df <- tibble(id = seq(1:5),
                       animal = c("dog", "cat", "duck", "fairy", "snake"),
                       weight = c("23", NA, "1.2", "0.2",  "BAD"),
                       height = c("54", "45", "21", "50", "42"))
      

      我们想要加入的数据子集是相同的:

      sub_df <- tibble(id = c(2, 5),
                           animal = c("cat", "snake"),
                           weight = c("2.2", "1.3"))
      

      如果我们单独使用 OP 的方法 (anti_join %&gt;% bind_rows),由于 df 中的附加“高度”列,这将不起作用。需要额外的一两个步骤。

      在这种情况下,我们可以使用以下函数:

      replace_subset <- function(df, df_subset, id_col_names = c()) {
      
        # work out which of the columns contain "new" data
        new_data_col_names <- colnames(df_subset)[which(!colnames(df_subset) %in% id_col_names)]
      
        # complete the df_subset with the extra columns from df
        df_sub_to_join <- df_subset %>%
          left_join(select(df, -new_data_col_names), by = c(id_col_names))
      
        # join and bind rows
        df_out <- df %>%
          anti_join(df_sub_to_join, by = c(id_col_names)) %>%
          bind_rows(df_sub_to_join)
      
        return(df_out)
      
      }
      

      现在来看结果:

      replace_subset(df = df , df_subset = sub_df, id_col_names = c("id"))
      
      ## A tibble: 5 x 4
      #     id animal weight height
      #  <dbl> <chr>  <chr>  <chr> 
      #1     1 dog    23     54    
      #2     3 duck   1.2    21    
      #3     4 fairy  0.2    50    
      #4     2 cat    2.2    45    
      #5     5 snake  1.3    42  
      
      

      这是一个在管道中使用该函数的示例:

      df %>%
        replace_subset(df_subset = sub_df, id_col_names = c("id")) %>%
        mutate_at(.vars = vars(c('weight', 'height')), .funs = ~as.numeric(.)) %>%
        mutate(bmi = weight / (height^2))
      
      ## A tibble: 5 x 5
      #     id animal weight height      bmi
      #  <dbl> <chr>   <dbl>  <dbl>    <dbl>
      #1     1 dog      23       54 0.00789 
      #2     3 duck      1.2     21 0.00272 
      #3     4 fairy     0.2     50 0.00008 
      #4     2 cat       2.2     45 0.00109 
      #5     5 snake     1.3     42 0.000737
      
      

      希望这有帮助:)

      【讨论】:

      • 不错的解决方案!您知道是否进行了一些更新以简化此过程?
      【解决方案4】:

      dplyr::rows_update 不正是我们需要的吗?以下代码应该可以工作:

      df %>% dplyr::rows_update(sub_df, by = "id")
      

      只要您的数据集有唯一标识符(一个或多个变量),这应该可以工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-02-16
        • 2019-11-17
        • 2015-08-22
        • 2020-01-21
        • 2011-07-28
        • 1970-01-01
        相关资源
        最近更新 更多