如何用另一个数据框填充一个数据框,同时保留第一个数据框的 NA

How to fill one data frame with data from another while retaining NAs from the first

我有两个列名相同但行数不同的数据框。第一个数据框 (a) 看起来类似于:

a = data.frame("Site"=c(1,2,3,4,7,9,10,11,13,14), 
               "v1"=c(0,0,0,0,0,0,0,0,0,0), 
               "v2"=c(0,0,0,0,NA,NA,NA,0,0,0), 
               "v3"=c(0,0,0,NA,0,NA,0,0,0,0), 
               "v4"=c(0,0,0,0,0,0,0,0,NA,NA), 
               "v5"=c(0,0,0,0,0,NA,0,NA,0,0)) 

注意:站点 5、6、8 和 12 是故意缺失的。

第二个数据框 (b) 看起来像这样:

b = data.frame("Site"=c(2,3,4,7,10,14),
               "v1"=c(1,NA,2,1,NA,NA),
               "v2"=c(1,1,NA,NA,NA,NA),
               "v3"=c(NA,1,NA,NA,NA,1),
               "v4"=c(1,NA,4,1,NA,NA),
               "v5"=c(1,NA,2,1,1,3))

我想达到的效果是这样的:

desired = data.frame("Site"=c(1,2,3,4,7,9,10,11,13,14), 
                     "v1"=c(0,1,0,2,1,0,0,0,0,0), 
                     "v2"=c(0,1,1,0,NA,NA,NA,0,0,0), 
                     "v3"=c(0,0,1,NA,0,NA,0,0,0,1), 
                     "v4"=c(0,1,0,4,1,0,0,0,NA,NA), 
                     "v5"=c(0,1,0,2,1,NA,1,NA,0,3))

我将数据帧 b 中的数据“注入”(我确定有更好的术语)到数据帧 a 中,但是我想替换任何 NA来自 b 的零,并保持原样来自 a 的 NA。

我发现并尝试过这段代码:

cols <- colnames(a)[colnames(a) %in% colnames(b)]
rows <- rownames(a)[rownames(a) %in% rownames(b)]

a[rows, cols] <- b[rows, cols]

但它带来了 NA。我考虑先用零替换 NA,但即便如此,它也会擦除我当前在数据框 a 中想要保留的 NA。

也许 for 循环或 tidyverse 中的某些东西是可行的方法,但我什至不知道从哪里开始。任何帮助将不胜感激!

我建议您先将 b 中的每个 NA 值替换为 0,然后使用 inner_join 将结果与相应的 Site 值合并在 a。然后,您可以将 a 的非 NA 值替换为它们在 b 中的相应值,而 a 中的 NA 值保持不变。最后,我们将修改后的数据框与 a 的子集绑定,其 Site 值不存在于 b.

library(dplyr)

a %>%
  inner_join(b %>%
               mutate(across(!Site, ~ replace(.x, is.na(.x), 0))), 
             by = "Site") %>%
  mutate(across(ends_with(".x"), ~ ifelse(!is.na(.x), get(gsub("(.*\.)x", "\1y", cur_column())), 
                                          .x))) %>%
  select(!ends_with("y")) %>%
  rename_with(~ gsub("(.*)\.x", "\1", .), ends_with(".x")) %>%
  bind_rows(a %>% 
              filter(!Site %in% unique(b$Site))) %>%
  arrange(Site)


   Site v1 v2 v3 v4 v5
1     1  0  0  0  0  0
2     2  1  1  0  1  1
3     3  0  1  1  0  0
4     4  2  0 NA  4  2
5     7  1 NA  0  1  1
6     9  0 NA NA  0 NA
7    10  0 NA  0  0  1
8    11  0  0  0  0 NA
9    13  0  0  0 NA  0
10   14  0  0  1 NA  3

我的好朋友Onyambu:

推荐的也是一个精明而简洁的解决方案
rbind(a, b) %>% 
  group_by(Site) %>%  
  summarise(across(everything(), ~ 
                     if(any(!is.na(.x))) max(.x, na.rm = TRUE) else NA))
merge(b, a, by = 'Site', all = TRUE) %>%
  split.default(sub('.x|.y', '', names(.))) %>%
  map_df(~coalesce(!!!.x))

# A tibble: 10 x 6
    Site    v1    v2    v3    v4    v5
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1     1     0     0     0     0     0
 2     2     1     1     0     1     1
 3     3     0     1     1     0     0
 4     4     2     0    NA     4     2
 5     7     1    NA     0     1     1
 6     9     0    NA    NA     0    NA
 7    10     0    NA     0     0     1
 8    11     0     0     0     0    NA
 9    13     0     0     0    NA     0
10    14     0     0     1    NA     3
i <- match(b$Site, a$Site)
a_nas <- is.na(a)
for (j in seq(2, ncol(a))) {
  a[i, j] <- ifelse(is.na(b[[j]]), 0, b[[j]])
}
a[a_nas] <- NA

all.equal(desired, a)
# [1] TRUE

我们可以使用{powerjoin}

library(powerjoin)
power_full_join(a, b, by = "Site", conflict = ~ifelse(is.na(.x), NA, coalesce_yx(.x, .y)))
#>    Site v1 v2 v3 v4 v5
#> 1     1  0  0  0  0  0
#> 2     2  1  1  0  1  1
#> 3     3  0  1  1  0  0
#> 4     4  2  0 NA  4  2
#> 5     7  1 NA  0  1  1
#> 6     9  0 NA NA  0 NA
#> 7    10  0 NA  0  0  1
#> 8    11  0  0  0  0 NA
#> 9    13  0  0  0 NA  0
#> 10   14  0  0  1 NA  3