我在 R 中有一个
data.frame
,它由大约 10 万行组成。我正在使用以下示例data.frame
来说明我的问题。想象一下,ID
代表一个病人,date
是检测疾病的日期,A
表示来自测试类型 A 的阳性测试,而 B
是类似的指标,但来自测试类型 B。每行至少有一个阳性测试,数据集按ID
和date
排序。
data <- data.frame(ID=c(1,1,1,1,1,2,2,2,2,2,3,3,3,3,3),
date=as.Date(c("2022-01-01","2022-01-05","2022-02-17","2022-05-13",
"2022-09-20","2021-02-02","2021-02-06","2021-04-18",
"2021-04-19","2022-08-21","2020-01-01","2020-03-29",
"2020-04-03","2020-04-04","2022-01-01")),
A=c(1,1,0,1,0,0,0,1,0,1,0,0,0,0,0),
B=c(0,0,1,0,1,1,1,0,1,0,1,1,1,1,1))
data
ID date A B
1 1 2022-01-01 1 0
2 1 2022-01-05 1 0
3 1 2022-02-17 0 1
4 1 2022-05-13 1 0
5 1 2022-09-20 0 1
6 2 2021-02-02 0 1
7 2 2021-02-06 0 1
8 2 2021-04-18 1 0
9 2 2021-04-19 0 1
10 2 2022-08-21 1 0
11 3 2020-01-01 0 1
12 3 2020-03-29 0 1
13 3 2020-04-03 0 1
14 3 2020-04-04 0 1
15 3 2022-01-01 0 1
我有以下规则来确定患者连续感染的日期/计数(
infec_date
和 n_infec
):
date
值将是他们的初始感染(infec_date
=date
和n_infec=1
)。A==1
和 date
在上次感染后 45 天或更长时间,即为新感染(给 n_infec
加 1 并将 date
指定为新的 infec_date
)。B==1
和 date
是上次感染后 90 天或更长时间,那也是新感染(采取与上述 2. 相同的措施)。infec_date
/n_infec
被结转。我使用此规则的输出数据集需要如下所示:
ID date A B infec_date n_infec
1 1 2022-01-01 1 0 2022-01-01 1
2 1 2022-01-05 1 0 2022-01-01 1
3 1 2022-02-17 0 1 2022-01-01 1
4 1 2022-05-13 1 0 2022-05-13 2
5 1 2022-09-20 0 1 2022-09-20 3
6 2 2021-02-02 0 1 2021-02-02 1
7 2 2021-02-06 0 1 2021-02-02 1
8 2 2021-04-18 1 0 2021-04-18 2
9 2 2021-04-19 0 1 2021-04-18 2
10 2 2022-08-21 1 0 2022-08-21 3
11 3 2020-01-01 0 1 2020-01-01 1
12 3 2020-03-29 0 1 2020-01-01 1
13 3 2020-04-03 0 1 2020-04-03 2
14 3 2020-04-04 0 1 2020-04-03 2
15 3 2022-01-01 0 1 2022-01-01 3
我的目标:找到一种比必须遍历每一行并检查逻辑字符串以手动设置值更快的方法。我想知道使用包
dplyr
、tidyr
、data.table
或 sqldf
. 是否有任何功能可以简化/加快这种类型的编程
我目前在 R 中使用的
for
循环是:
for(i in 1:nrow(data)){
if(i==1){
data[i,"infec_date"]=data[i,"date"]
data[i,"n_infec"]=1
}else if(data[i,"ID"]!=data[i-1,"ID"]){
data[i,"infec_date"]=data[i,"date"]
data[i,"n_infec"]=1
}else{
if(data[i,"A"]==1&data[i,"date"]>=data[i-1,"infec_date"]+45){
data[i,"infec_date"]=data[i,"date"]
data[i,"n_infec"]=data[i-1,"n_infec"]+1
}else if(data[i,"B"]==1&data[i,"date"]>=(data[i-1,"infec_date"]+90)){
data[i,"infec_date"]=data[i,"date"]
data[i,"n_infec"]=data[i-1,"n_infec"]+1
}else{
data[i,"infec_date"]=data[i-1,"infec_date"]
data[i,"n_infec"]=data[i-1,"n_infec"]
}
}
}
处理 10 万行数据时,这会变得很慢并且需要永远运行。我无权访问 SAS,但在 SAS 中进行编程如下所示:
data new_data;
set data;
by id date;
length infec_date n_infec 8.;
format infec_date mmddyy10.;
retain infec_date n_infec;
if first.id then do;
infec_date=date;
n_infec=1;
end;
if A=1 and date>=infec_date+45 then do;
infec_date=date;
n_infec=n_infec+1;
end;
else if B=1 and date>=infec_date+90 then do;
infec_date=date;
n_infec=n_infec+1;
end;
run;
提前致谢!
假设您的数据看起来像这样,您可以
group_by()
ID,创建一个变量来判断分组的 ID 是否重复。它是 SAS 中使用的 if first.ID
的反向等价物。只有在这里,重复是假的是ID第一次出现,这将有助于计算每个ID的感染。
接下来,使用
case_when()
使用您的 A/B 规范/第一个 ID 创建一个变量,这有点类似于 proc sql 的 case when()。第一个变量感染得分为 1,如果没有感染则得分为 0。
接下来,infec_date是感染为1的任何时候,它是日期中的值。如果不是,请使用滞后(日期)转到上一个日期,根据需要调整 n 等等。
然后计算感染累计和,并删除列
data %>%
group_by(ID) %>%
mutate(dup = duplicated(ID)) %>%
mutate(infection = case_when(A == 1 & (date - lag(date)) >=45 ~ 1,
B == 1 & (date - lag(date)) >=90 ~ 1,
dup == FALSE ~ 1,
TRUE ~ 0)) %>%
mutate(infec_date = case_when(infection == 1 ~ date,
infection == 0 & lag(infection) == 1 ~ lag(date),
infection == 0 & lag(infection) == 0 ~ lag(date,n=2),
TRUE ~ NA)) %>%
mutate(n_infec = cumsum(infection)) %>%
select(-dup,-infection) %>%
ungroup()