使用dplyr连接操作替换数据框的子集

时间:2017-07-05 15:16:47

标签: r dplyr

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

  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,动物,体重,身高)如何用原始集合组合子集?

4 个答案:

答案 0 :(得分:11)

您所描述的是一种连接操作,您可以在其中更新原始数据集中的某些值。使用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

答案 1 :(得分:3)

首先删除na,然后简单地堆叠这些元素:

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

答案 2 :(得分: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 %>% bind_rows),则由于df中增加了“ height”列,因此无法使用。需要额外的一两个步骤。

在这种情况下,我们可以使用以下功能:

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

希望这会有所帮助:)

答案 3 :(得分:1)

dplyr::rows_update正是我们这里需要的吗?以下代码应该起作用:

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

只要您的数据集具有唯一标识符(一个或多个变量),此方法就可以工作。