在 dplyr 中用 `group_by` 中的随机数替换 NA

Replace NA with random numbers within `group_by` in dplyr

我有一个长格式的数据框,我想用随机数替换缺失值,但我想用不同的设置明智地进行分组...

library(dplyr)

set.seed(1)

imp_df <- 
  data.frame(exp=rep(letters[1:3], each=2),
             rep=1:2,
             mean=1:6,
             sd=seq(0,0.5,0.1))

df <- 
  data.frame(
    exp=rep(letters[1:3], each=20),
    rep=1:2,
    int=rnorm(60,10,5)
  )

df[sample(1:60,25,replace=F), 'int'] <- NA

所以我的数据如上所示,在 imp_df 中,我根据实验 exp 和复制 reprnorm 函数进行了设置。

我的数据框有一些缺失值,我想用随机数替换 NA

如何使用 dplyrtidyr 来实现?

编辑

在@starja 的回答后,我找到了一个快速但可能很慢的解决方案,方法是将 rowwiseleft_join 一起使用。

df %>%
  left_join(imp_df) %>%
  rowwise() %>%
  mutate(imp.int=if_else(
    is.na(int),
    rnorm(1, mean, sd),
    int
  )) %>%
  print(n=60)

还有其他方法吗?

编辑 2

由于 rowwise 方法非常慢,我无法在某些 dplyr 代码中得到它 运行,我使用了一个 for 循环来完成 imp_df 插补设置。

这是一个非常快速的解决方案,但不如我希望的那样可读:

df$imp.int <- df$int


for(line in 1:nrow(imp_df)) {
  imp_settings <- as.list(imp_df[line,])
  rows_missing_values <- which(
    df$exp == imp_settings$exp &
      df$rep == imp_settings$rep &
      is.na(df$imp.int) 
  )
  df$imp.int[rows_missing_values] <- 
    stats::rnorm(length(rows_missing_values), imp_settings$mean, imp_settings$sd)
}

所以我们首先为估算值添加一列 imp.int,现在 运行 通过替换每个组的 NA 逐行添加不同的估算设置。

我想有使用矢量化的更聪明的解决方案,但如果你没有超大数据,我喜欢为此使用 purrr::map 函数和一个小的自定义函数:

library(dplyr)

set.seed(1)

imp_df <- 
  data.frame(exp=rep(letters[1:3], each=2),
             rep=1:2,
             mean=1:6,
             sd=seq(0,0.5,0.1))

df <- 
  data.frame(
    exp=rep(letters[1:3], each=20),
    rep=1:2,
    int=rnorm(60,10,5)
  )

df[sample(1:60,25,replace=F), 'int'] <- NA

replace_fun <- function(x, mean, sd) {
  if (is.na(x)) {
    rnorm(1, mean, sd)
  } else {
    x
  }
}

df %>% 
  left_join(imp_df, by = c("exp", "rep")) %>% 
  mutate(int = purrr::pmap_dbl(list(int, mean, sd), replace_fun)) %>% 
  head()
#>   exp rep       int mean  sd
#> 1   a   1  1.000000    1 0.0
#> 2   a   2 10.918217    2 0.1
#> 3   a   1  5.821857    1 0.0
#> 4   a   2 17.976404    2 0.1
#> 5   a   1 11.647539    1 0.0
#> 6   a   2  5.897658    2 0.1

reprex package (v0.3.0)

于 2021-05-27 创建

(如果需要,可以使用 select(-c(mean, sd)) 删除 mean/sd 列。)

也可以这样做:

library(dplyr)
library(purrr)

df %>%
  left_join(imp_df, by = c("exp", "rep")) %>%
  mutate(int = ifelse(is.na(int), 
                      map2(mean, sd, ~ rnorm(1, .x, .y)), int))

   exp rep       int mean  sd
1    a   1         1    1 0.0
2    a   2  10.91822    2 0.1
3    a   1  5.821857    1 0.0
4    a   2   17.9764    2 0.1
5    a   1  11.64754    1 0.0
6    a   2  5.897658    2 0.1
7    a   1  12.43715    1 0.0
8    a   2  13.69162    2 0.1
9    a   1  12.87891    1 0.0
10   a   2  1.986482    2 0.1