R使用tidyverse用其他值的总和填充缺失值

R fill missing values with the sum of other values using tidyverse

我有一个包含很多列和很多行的数据框。

col_1 | col_2 | ... | col_n
 35   |  NA   | ... |   2
  .   |   .   |  .  |   .
  .   |   .   |  .  |   .
  .   |   .   |  .  |   .
 123  |  90   | ... |   NA

有些行包含 NA 值(可以超过 1 个 NA)。

我希望找到恰好包含 1 个 NA 的所有行,并将其替换为其他列的总和。

如何使用 tidyverse 实现它?

对之前的回答稍作修改,仅检查连续 1 NA -

df<- t(apply(df, 1, function(x) {
  if(sum(is.na(x)) == 1)  replace(x, is.na(x), sum(x, na.rm = TRUE))
  else x
}))

purrr::pmap_df :

df <- purrr::pmap_df(df, ~{
  x <- c(...)
  if(sum(is.na(x)) == 1)  replace(x, is.na(x), sum(x, na.rm = TRUE))
  else x
})

两种tidyverse方法

#toy data
df <- data.frame(
  col_1 = c(35L, 43L, 234L, NA, 23L),
  col_2 = c(421L, 54L, NA, 8L, 45L),
  col_3 = c(NA, 435L, NA, 687L, 78L),
  col_4 = c(767L, 78L, 65L, 89L, NA)
)
df
#>   col_1 col_2 col_3 col_4
#> 1    35   421    NA   767
#> 2    43    54   435    78
#> 3   234    NA    NA    65
#> 4    NA     8   687    89
#> 5    23    45    78    NA

#load libraries
library(tidyverse)

#1st approach
df %>% mutate(across(everything(), ~ ifelse(rowSums(is.na(cur_data())) == 1 & is.na(.), rowSums(cur_data(), na.rm = T), .)))
#>   col_1 col_2 col_3 col_4
#> 1    35   421  1223   767
#> 2    43    54   435    78
#> 3   234    NA    NA    65
#> 4   784     8   687    89
#> 5    23    45    78   146

#2nd approach
df %>% rowwise() %>%
  mutate(replace(cur_data(), is.na(cur_data()) & rowSums(is.na(cur_data())) == 1, rowSums(cur_data(), na.rm = T)))
#> # A tibble: 5 x 4
#> # Rowwise: 
#>   col_1 col_2 col_3 col_4
#>   <int> <int> <int> <int>
#> 1    35   421  1223   767
#> 2    43    54   435    78
#> 3   234    NA    NA    65
#> 4   784     8   687    89
#> 5    23    45    78   146

reprex package (v2.0.0)

于 2021-06-05 创建

我们可以使用 coalescerowSums 来提高效率

library(dplyr)
v1 <- rowSums(df, na.rm = TRUE) * NA^(rowSums(is.na(df)) != 1)
df[] <- coalesce(unlist(df),v1[row(df)])

-输出

 df
  col_1 col_2 col_3 col_4
1    35   421  1223   767
2    43    54   435    78
3   234    NA    NA    65
4   784     8   687    89
5    23    45    78   146

或者上面也可以写成

df %>%
     mutate(rS = rowSums(cur_data(), na.rm = TRUE) * 
        NA^(rowSums(is.na(cur_data())) != 1), 
        across(-rS, ~ coalesce(., rS)), rS = NULL)
#  col_1 col_2 col_3 col_4
#1    35   421  1223   767
#2    43    54   435    78
#3   234    NA    NA    65
#4   784     8   687    89
#5    23    45    78   146

您还可以使用以下解决方案,它是 replace 功能的替代方案:

library(dplyr)
library(purrr)

df %>% 
  pmap_df(., ~ if(sum(is.na(c(...))) == 1) {
    coalesce(c(...), sum(c(...), na.rm = TRUE)) 
  } else {
    c(...)
  }
)

# A tibble: 2 x 4
  col_1 col_2 col_3 col_n
  <dbl> <dbl> <dbl> <dbl>
1    35    42     5     2
2   123    90    NA    NA

数据

structure(list(col_1 = c(35, 123), col_2 = c(NA, 90), col_3 = c(5, 
NA), col_n = c(2, NA)), class = "data.frame", row.names = c(NA, 
-2L))

我使用了 Anil Goyal 的玩具数据(谢谢!)

今天有一个类似的问题请看这里:

这里我们:

  1. 对行求和
  2. 算上 NA 的
  3. and with across with 在 col1-4 上应用所需的条件
  4. 我最喜欢的部分是 .keep = "unused",它删除了“助手”列。
df %>% 
  mutate(rowsum1 = rowSums(., na.rm=TRUE)) %>%
  mutate(count_na = rowSums(is.na(select(.,everything())))) %>% 
  mutate(across(starts_with("col"), ~case_when(count_na ==1 ~coalesce(.,rowsum1),
                                               TRUE ~ as.numeric(.))
                ), .keep ="unused"
         )

输出:

 col_1 col_2 col_3 col_4
1    35   421  1223   767
2    43    54   435    78
3   234    NA    NA    65
4   784     8   687    89
5    23    45    78   146

使用两个中间变量的替代解决方案:

  • q_na 每行 NA 数
  • s_row 不包括 NA 的行值总和。
library(tidyverse)

df <- tribble(
~col_1, ~col_2, ~col_3,
    NA,      1,      3,
    NA,     NA,      2,
     1,      5,      6,
    NA,     NA,     NA,
     3,     NA,      2)

df %>% 
  rowwise() %>% 
  mutate(q_na = sum(is.na(c_across(col_1:col_3))), 
    s_row = sum(c_across(col_1:col_3), na.rm = TRUE)) %>% 
  ungroup() %>% 
  filter(q_na == 1) %>% 
  mutate(across(col_1:col_3, ~if_else(is.na(.x), s_row, .x))) %>% 
  dplyr::select(col_1:col_3)

#> # A tibble: 2 x 3
#>   col_1 col_2 col_3
#>   <dbl> <dbl> <dbl>
#> 1     4     1     3
#> 2     3     5     2

如果要获取所有行,只需删除 filter 并将其包含在 if_else:

df %>% 
  rowwise() %>% 
  mutate(q_na = sum(is.na(c_across(col_1:col_3))), 
    s_row = sum(c_across(col_1:col_3), na.rm = TRUE)) %>% 
  ungroup() %>% 
  filter() %>% 
  mutate(across(col_1:col_3,
    ~if_else(q_na == 1 & is.na(.x), s_row, .x))) %>% 
  dplyr::select(col_1:col_3)

#> # A tibble: 5 x 3
#>   col_1 col_2 col_3
#>   <dbl> <dbl> <dbl>
#> 1     4     1     3
#> 2    NA    NA     2
#> 3     1     5     6
#> 4    NA    NA    NA
#> 5     3     5     2

reprex package (v0.3.0)

于 2021-06-05 创建