通过带有两个参数的函数创建新列的 purrr 方法

purrr approach for creating new columns through function with two arguments

我很确定有办法到达那里,但我找不到。

我有一个包含多列的数据框。我现在想在从这些列 (0/1) 采样时向包含信息的数据框添加新列。如果我想从每列中采样相同数量的元素,我有一个 across 的整洁解决方案。当从每一列中采样不同数量的元素时,我也有一个(甚至更丑陋的)解决方案 across,但我希望使用 purrr 有一个更简单的解决方案,我只提供列名作为一个参数和要作为另一个参数采样的元素数量,然后将获得我的新列。

有什么想法吗?

数据

df <- data.frame(x = runif(10),
                 y = runif(10),
                 z = runif(10))

df[1, 1] <- NA
df[2, 2] <- NA
df[3, 3] <- NA

sampling <- c(2, 3, 4)
names(sampling) <- c("random_x", "random_y", "random_z")

采样相同数量元素的解决方案

df %>%
  mutate(across(everything(),
                ~if_else(is.na(.),
                         NA_integer_,
                         as.integer(row_number() %in% sample(which(!is.na(.)), size = 3))),
                .names = "{.col}_random"))

采样不同数量元素的解决方案

df %>%
  mutate(across(everything(),
                ~if_else(is.na(.),
                         NA_integer_,
                         as.integer(row_number() %in% sample(which(!is.na(.)), size = sampling[str_detect(names(sampling), paste0(cur_column(), "$"))]))),
                .names = "{.col}_random"))

想要的咕噜声方式

……也许沿着这些路线?

df %>%
  map2(.x = c("x", "y", "z"),
       .y = sampling,
       .f = ~if_else(is.na(.x),
                     NA_integer_,
                     as.integer(row_number() %in% sample(which(!is.na(.x)), size = .y))))

purrr 方式的问题显然是我没有使用正确的语法,因为我将字符向量作为 .x 而不是 df 中的列传递。

期望的输出

(抛开结果的随机性)

           x          y         z x_random y_random z_random
1         NA 0.06686268 0.7663706       NA        0        0
2  0.7551366         NA 0.5550793        0       NA        1
3  0.7437531 0.61971712        NA        0        0       NA
4  0.5238451 0.57510689 0.7637622        1        0        0
5  0.9593917 0.17481769 0.4443493        0        0        0
6  0.2821633 0.86972254 0.2284449        0        0        0
7  0.3941531 0.61981285 0.8202302        0        0        1
8  0.1473573 0.58482156 0.9078447        0        1        1
9  0.7063327 0.77550907 0.9271699        1        1        1
10 0.6320678 0.06011700 0.2139956        0        1        0
  • 如果将 .x.y 分别传递给 map2,则不应使用 df %>% map2(...)
  • is.na(.x) 不正确,因为 .x 是字符值(如 "x""y""z")。我使用 df[[.x]] 对值进行子集化。
  • 由于我们没有使用 df %>% ...,所以 row_number() 将不起作用,因此将其更改为 seq_along

这是一种使用 map2_dfc 创建新列的方法,我们使用 bind_cols 将其绑定到原始数​​据框。

library(dplyr)
library(purrr)

bind_cols(df, map2_dfc(.x = c("x", "y", "z"),
                       .y = sampling,
                       .f = ~tibble(!!paste0(.x, "_random") := 
           if_else(is.na(df[[.x]]), NA_integer_,
as.integer(seq_along(df[[.x]]) %in% sample(which(!is.na(df[[.x]])), size = .y))))))

#            x          y           z x_random y_random z_random
#1          NA 0.02358698 0.222022714       NA        0        1
#2  0.15099912         NA 0.878007560        0       NA        0
#3  0.20228598 0.92222805          NA        0        0       NA
#4  0.10955137 0.68713928 0.485866574        1        1        1
#5  0.57361508 0.56205208 0.367087414        1        1        0
#6  0.30534642 0.75997029 0.006055428        0        0        1
#7  0.76949447 0.78142772 0.279323093        0        0        0
#8  0.07178739 0.73181961 0.187739444        0        0        1
#9  0.52645525 0.48321814 0.213029355        0        1        0
#10 0.30858707 0.20973381 0.450931534        0        0        0

另一个可能的解决方案,使用 purrr::imap_dfc:

library(tidyverse)

mutate(df, imap_dfc(sampling, ~ +(1:nrow(df) %in% sample(setdiff(1:nrow(df), 
    which(is.na(df[, str_sub(.y, nchar(.y))]))), .x))) * ifelse(is.na(df), NA, 1))

#>              x         y          z random_x random_y random_z
#> 1           NA 0.5784770 0.87429843       NA        0        0
#> 2  0.483728093        NA 0.87502533        0       NA        0
#> 3  0.294748405 0.3057474         NA        0        0       NA
#> 4  0.993350082 0.4282864 0.02936437        0        0        1
#> 5  0.344054454 0.4872465 0.65317911        0        0        1
#> 6  0.465265657 0.6721587 0.77952998        0        1        1
#> 7  0.659649583 0.9923243 0.01262495        1        1        0
#> 8  0.314616988 0.7686583 0.99389609        1        0        0
#> 9  0.009670492 0.1558185 0.73083388        0        0        0
#> 10 0.102769163 0.1543078 0.84348806        0        1        1

仅供参考,我是如何解决的。与@Ronak Shah 的解决方案非常相似,但这里和那里有点不同:

df %>%
  add_column(map2_dfc(.x = c("x", "y", "z"),
                      .y = sampling,
                      .f = ~if_else(is.na(df[.x]),
                                    NA_integer_,
                                    as.integer(row_number(df[.x]) %in% sample(which(!is.na(df[.x])), size = .y)))) %>%
               rename_with(.cols = everything(),
                           .fn   = ~names(sampling)))