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

Cri*_*zar 9 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"))
Run Code Online (Sandbox Code Playgroud)

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

  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"))
Run Code Online (Sandbox Code Playgroud)

现在我想把所有的东西放在一起,所以我使用这样的操作:

> 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
Run Code Online (Sandbox Code Playgroud)

是否存在直接使用连接操作执行此操作的方法?

如果子集只是关键列,而变量需要进行处理 (id,动物权重), 而不是原始数据框的总变量(id,动物,重量,高度),如何组装子集用原来的套装?

tal*_*lat 12

您描述的是一个连接操作,您可以在其中更新原始数据集中的某些值.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]
Run Code Online (Sandbox Code Playgroud)

数据现已更新:

#   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
Run Code Online (Sandbox Code Playgroud)

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

  • @Nettle,好吧,因为它已被OP接受,所以它似乎确实在一定程度上回答了这个问题。请记住,仅因为OP要求dplyr答案并不意味着非dplyr答案是不正确的或没有帮助。在我看来,在这种特定情况下,data.table提供了比dplyr更好的解决方案。 (3认同)
  • @docendo,完全同意你的看法。Dplyr 似乎没有针对这项常见任务的直接解决方案,您的回答非常简洁。原始机智的变化,但同样不令人满意,将是:`df %&gt;% filter(!id %in% sub_df$id) %&gt;% bind_rows(sub_df)` (2认同)

r.u*_*apr 5

首先删除 na,然后简单地堆叠 tibbles:

 bind_rows(filter(df,!is.na(weight)),sub_df)
Run Code Online (Sandbox Code Playgroud)


Max*_*axL 5

对于任何正在寻找在 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"))
Run Code Online (Sandbox Code Playgroud)

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

sub_df <- tibble(id = c(2, 5),
                     animal = c("cat", "snake"),
                     weight = c("2.2", "1.3"))
Run Code Online (Sandbox Code Playgroud)

如果我们单独使用 OP 的方法 ( anti_join %>% 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)

}
Run Code Online (Sandbox Code Playgroud)

现在来看结果:

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  

Run Code Online (Sandbox Code Playgroud)

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

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

Run Code Online (Sandbox Code Playgroud)

希望这有帮助:)


duf*_*fei 5

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

df %>% dplyr::rows_update(sub_df, by = "id")
Run Code Online (Sandbox Code Playgroud)

只要数据集有唯一标识符(一个或多个变量),这种方法就应该有效。