在 R 中按组采样,没有替换,但最终结果也不能包含任何重复

Sampling by Group in R with no replacement but the final result cannot contain any repeats as well

我正在尝试构建一个对照组。 ID_1是原始参与者,ID_2是对照。为简单起见,它们按性别和年龄匹配。我收到了一个如下所示的数据框:

ID_1 <- c(1,1,1,2,2,3,3,4,4,4)
Sex <- c("M","M","M","F","F","M","M","F","F","F")
Age <- c(23,23,23,35,35,44,44,35,35,35)
ID_2 <- c(321,322,323,630,631,502,503,630,631,632)

df <- data.frame(ID_1, Sex, Age, ID_2)

所以每个 ID_1 我都有几场比赛,我想在每组中抽样,只得到一个。我得到了:

library(dplyr)

random_ID_2 <- df %>% group_by(ID_1) %>% sample_n(size = 1, replace = F)

问题是我不想重复 ID_2。所以偶然的机会我最终可以将 ID_1 = 2 和 ID_1 = 4 配对到同一个控件 ID_2 = 630

我怎样才能确保这不会发生?

提前致谢。

这是一个可能的采样选项,如果有重复项,它将重新采样:

# handles case where no samples left
my_sample <- function(x, ...){
  if (length(x) == 0L) return(NA) else sample(x, ...)
}

df %>% 
  group_by(ID_1) %>% 
  slice_sample(n = 1) %>% 
  ungroup() %>%
  mutate(resample = duplicated(ID_2)) %>% 
  rowwise() %>%
  mutate(ID_2 = if (resample) my_sample(df[df$ID_1 == ID_1 & df$ID_2 != ID_2, "ID_2"], 1) else ID_2) %>% 
  ungroup() %>% 
  select(-resample)

需要注意的一件事是,在您的数据框下方具有重复 ID_2 的行是有条件的采样。

输出

set.seed(17)是采样相同ID_2的情况:

df %>% 
  group_by(ID_1) %>% 
  slice_sample(n = 1)

  ID_1 Sex     Age  ID_2
  <dbl> <chr> <dbl> <dbl>
1     1 M        23   322
2     2 F        35   631
3     3 M        44   502
4     4 F        35   631

并测试上面的代码:

set.seed(17)
df %>% 
  group_by(ID_1) %>% 
  slice_sample(n = 1) %>% 
  ungroup() %>%
  mutate(resample = duplicated(ID_2)) %>% 
  rowwise() %>%
  mutate(ID_2 = if (resample) my_sample(df[df$ID_1 == ID_1 & df$ID_2 != ID_2, "ID_2"], 1) else ID_2) %>% 
  ungroup() %>% 
  select(-resample)

   ID_1 Sex     Age  ID_2
  <dbl> <chr> <dbl> <dbl>
1     1 M        23   322
2     2 F        35   631
3     3 M        44   502
4     4 F        35   632
> 

再次强调我上面的观点 ID_1 == 4 是有条件的抽样,因为我们允许 ID_1 == 2 保持与 ID_2 == 631 的匹配并更改 ID_1 == 4 的匹配。

工作原理

  1. 像往常一样对数据进行采样。
  2. 然后我们检查 ID_2 中的重复项。注意:duplicated returns TRUE 对于所有 后续 重复的 ID。
  3. 如果一行需要重新采样,那么我们使用行 mutate(ID_2 = if ...)
  4. 从原始数据帧中进行子集和采样

如果可以使用 data.table 解决方案:

dt <- setnames(
        unique(
          setorder(
            setDT(copy(df))[, idx := 1:.N, by = ID_1], # add an index column for each ID_1 group
            idx, ID_1)                                 # sort by idx, ID_1
          # for each Sex/Age group, sample unique values of ID_2 withouth replacement (pad with NA)
          [, ID_3 := c(sample(unique(ID_2)), rep(NA, .N - uniqueN(ID_2))), by = c("Sex", "Age")],
          by = "ID_1") # get the first row for each ID_1 group
        [, c(1:3, 6)], "ID_3", "ID_2") # remove helper columns and rename "ID_3" to "ID_2"