为组中的每个案例选择一个非重复控件
Posted
技术标签:
【中文标题】为组中的每个案例选择一个非重复控件【英文标题】:Select a non-duplicate control for each case within a group 【发布时间】:2022-01-14 01:08:28 【问题描述】:在给定的数据集中,case_control
表示一行是case
还是control
,id
是case
唯一的标识符,但对于control
和@987654329 可以重复@ 表示集群。我需要在每个group
中为每个案例选择一个控件,但如果之前为一个案例选择了一个控件,则无法根据id
变量为下一个案例选择它。如果没有可用的控件,则必须放弃该案例。
我怎样才能实现这一点,以便在具有约 1000 万行(包含 200 万个案例和 800 万个控件)的非常大的数据集中快速工作?
数据集长这样(https://docs.google.com/spreadsheets/d/1MpjKv9Fm_Hagb11h_dqtDX4hV7G7sZrt/edit#gid=1801722229)
group case_control id
cluster_1 case 11
cluster_1 control 21
cluster_1 control 22
cluster_1 control 23
cluster_2 case 12
cluster_2 control 21
cluster_2 control 22
cluster_2 control 24
cluster_3 case 13
cluster_3 control 21
cluster_3 control 22
cluster_3 control 25
预期的输出必须如下所示
group case_control id
cluster_1 case 11
cluster_1 control 21
cluster_2 case 12
cluster_2 control 22
cluster_3 case 13
cluster_3 control 25
【问题讨论】:
每个病例的对照观察是否总是相同数量? 嗨@DonaldSeinen,不,每个集群中控件的数量可能会有所不同。 既然速度很重要(这是一个重复的任务吗?)你介意改变结构以适应任务吗?示例 - 整数矩阵(组 1:n,对于 case_control 为 1/0 的布尔值)可能会加速此处的任何子集操作。 @DonaldSeinen,是的,我能做到。拥有这些变量类型真的有那么大吗? lapply vs for 当相同的函数应用于更大的数据集时,这是一种避免性能问题的方法,如下面的@wimpel 的回答。特别是如果一种方法复制数据。对于您的样本数据,转换为整数矩阵将占用约 7 倍的内存空间。此外,还有许多针对矩阵操作进行了优化的包,它们可能比其他解决方案更快,因为它们可以避免类型检查,例如Rfast
。
【参考方案1】:
这是一种 data.table 方法。
代码可以缩短(很多),但我选择将每个步骤分开(并注释),这样您就可以看到采取了哪些操作并可以检查中间结果。
library(data.table)
#initialise vector for used ids
id.used <- as.numeric()
#split by group and loop
L <- lapply(split(DT, by = "group"), function(x)
#select first row
caserow <- x[1,]
#select second to last row
controlrow <- x[2:nrow(x), ]
#match against id's already in use
controlrow.new <- controlrow[!id %in% id.used, ]
#sample random row from id's not already used
controlrow.sample <- controlrow.new[controlrow.new[, .I[sample(.N, 1)], ]]
#fill id.used (be carefull with the use of <<- !! google why..)
id.used <<- c(id.used, controlrow.sample$id)
#rowbind the sampled row to the caserow
return(rbind(caserow, controlrow.sample))
)
# rowbind the list back together and cast to wide
dcast(rbindlist(L), group ~ case_control, value.var = "id")
# group case control
# 1: cluster_1 11 21
# 2: cluster_2 12 24
# 3: cluster_3 13 25
使用的样本数据
DT <- fread("group case_control id
cluster_1 case 11
cluster_1 control 21
cluster_1 control 22
cluster_1 control 23
cluster_2 case 12
cluster_2 control 21
cluster_2 control 22
cluster_2 control 24
cluster_3 case 13
cluster_3 control 21
cluster_3 control 22
cluster_3 control 25")
【讨论】:
嗨@Wimpel。该解决方案完美运行。在我的情况下,我真的不能抱怨速度,因为这和 data.table 一样好,对吧?我从库pbapply
中为您的代码添加了一个小功能,我将lapply
替换为pblapply
,它给了我一个进度条。非常感谢您的评论和逐步解释。
嗨@Wimpel,我能做些什么让它比现在运行得更快吗?由于某种原因,我的运行时间随着时间的推移而增加,我不明白。【参考方案2】:
基础R:
Reduce(\(x,y)rbind(x, y[which(!y$id %in% x$id)[1:2], ]), split(df[-(3:4),], ~group))
group case_control id
1 cluster_1 case 11
2 cluster_1 control 21
5 cluster_2 case 12
7 cluster_2 control 22
9 cluster_3 case 13
12 cluster_3 control 25
请注意,我们只需要每个集群的第一个案例和第一个非重复控件,因此切片 1:2
Tidyverse:
df %>%
slice(-(3:4))%>%
group_split(group) %>%
reduce(~rbind(.x, slice(anti_join(.y, .x, by = c("case_control", "id")), 1:2)))
# A tibble: 6 x 3
group case_control id
<chr> <chr> <int>
1 cluster_1 case 11
2 cluster_1 control 21
3 cluster_2 case 12
4 cluster_2 control 22
5 cluster_3 case 13
6 cluster_3 control 25
【讨论】:
我似乎总是忘记Reduce
的力量...很好的答案!!
亲爱的@Onyambu,基本解决方案为我抛出了这个错误。 Error: unexpected input in "Reduce(\"
。知道这可能是什么原因吗?我对这个解决方案更感兴趣,因为我认为这将是我用例的最快解决方案。
@RizwanSA 这是因为您使用的是较旧的 R 版本。将\(X, y)
更改为function(X, y)
以上是关于为组中的每个案例选择一个非重复控件的主要内容,如果未能解决你的问题,请参考以下文章
如何使用没有临时表的 SQL 查询为组中的每个元素添加序列号
如何填充(自动填充)值,例如使用 R 中的 data.table 将 NA 替换为组中的第一个值?
Python Pandas:如何分组并为组中的所有项目分配 id?