Uwe和GKi的答案都是正确的。Gki收到了这笔奖金,因为Uwe迟到了,但Uwe的解决方案运行速度约为15倍
我有两个数据集,包含不同患者在多个测量时刻的得分,如下所示:
df1 <- data.frame("ID" = c("patient1","patient1","patient1","patient1","patient2","patient3"),
"Days" = c(0,25,235,353,100,538),
"Score" = c(NA,2,3,4,5,6),
stringsAsFactors = FALSE)
df2 <- data.frame("ID" = c("patient1","patient1","patient1","patient1","patient2","patient2","patient3"),
"Days" = c(0,25,248,353,100,150,503),
"Score" = c(1,10,3,4,5,7,6),
stringsAsFactors = FALSE)
> df1
ID Days Score
1 patient1 0 NA
2 patient1 25 2
3 patient1 235 3
4 patient1 353 4
5 patient2 100 5
6 patient3 538 6
> df2
ID Days Score
1 patient1 0 1
2 patient1 25 10
3 patient1 248 3
4 patient1 353 4
5 patient2 100 5
6 patient2 150 7
7 patient3 503 6
列ID
显示患者ID,列Days
显示测量时刻(自患者入选以来的天数),列Score
显示测量的评分。这两个数据集显示了相同的数据,但在不同的时间(df 1是2年前,df 2有相同的数据,但从今年开始更新)。
我必须比较两个数据集之间每个患者和每个时刻的得分。但是,在某些情况下,Days
变量会随着时间的推移发生微小的变化,因此通过简单的连接来比较数据集是不起作用的。示例:
library(dplyr)
> full_join(df1, df2, by=c("ID","Days")) %>%
+ arrange(.[[1]], as.numeric(.[[2]]))
ID Days Score.x Score.y
1 patient1 0 NA 1
2 patient1 25 2 10
3 patient1 235 3 NA
4 patient1 248 NA 3
5 patient1 353 4 4
6 patient2 100 5 5
7 patient2 150 NA 7
8 patient3 503 NA 6
9 patient3 538 6 NA
这里,第3行和第4行包含相同测量值(得分为3)的数据,但没有连接,因为Days
列的值不同(235 vs 248)。
**问题:**我正在寻找一种方法来设置第二列的阈值(比如30天),这将导致以下输出:
> threshold <- 30
> *** insert join code ***
ID Days Score.x Score.y
1 patient1 0 NA 1
2 patient1 25 2 10
3 patient1 248 3 3
4 patient1 353 4 4
5 patient2 100 5 5
6 patient2 150 NA 7
7 patient3 503 NA 6
8 patient3 538 6 NA
此输出显示,上一个输出的第3行和第4行已合并(因为248-235 < 30),并采用了第二个df(248)的Days
的值。
要记住的三个主要条件是:
- 在同一df(第1行和第2行)中,在阈值内的连续天数不会合并。
- 在某些情况下,
Days
变量最多有四个值存在于同一个 Dataframe 中,因此不应合并。可能的情况是,这些值中的一个确实存在于另一个 Dataframe 中的阈值内,并且这些值将必须被合并。请参见下面示例中的第3行。 - 每个评分/天数/患者组合只能使用一次。如果合并满足所有条件,但仍有可能进行双重合并,则应使用第一个合并。
> df1
ID Days Score
1 patient1 0 1
2 patient1 5 2
3 patient1 10 3
4 patient1 15 4
5 patient1 50 5
> df2
ID Days Score
1 patient1 0 1
2 patient1 5 2
3 patient1 12 3
4 patient1 15 4
5 patient1 50 5
> df_combined
ID Days Score.x Score.y
1 patient1 0 1 1
2 patient1 5 2 2
3 patient1 12 3 3
4 patient1 15 4 4
5 patient1 50 5 5
为CHINSOON 12编辑
> df1
ID Days Score
1: patient1 0 1
2: patient1 116 2
3: patient1 225 3
4: patient1 309 4
5: patient1 351 5
6: patient2 0 6
7: patient2 49 7
> df2
ID Days Score
1: patient1 0 11
2: patient1 86 12
3: patient1 195 13
4: patient1 279 14
5: patient1 315 15
6: patient2 0 16
7: patient2 91 17
8: patient2 117 18
我把你的解决方案 Package 在一个函数中,如下所示:
testSO2 <- function(DT1,DT2) {
setDT(DT1);setDT(DT2)
names(DT1) <- c("ID","Days","X")
names(DT2) <- c("ID","Days","Y")
DT1$Days <- as.numeric(DT1$Days)
DT2$Days <- as.numeric(DT2$Days)
DT1[, c("s1", "e1", "s2", "e2") := .(Days - 30L, Days + 30L, Days, Days)]
DT2[, c("s1", "e1", "s2", "e2") := .(Days, Days, Days - 30L, Days + 30L)]
byk <- c("ID", "s1", "e1")
setkeyv(DT1, byk)
setkeyv(DT2, byk)
o1 <- foverlaps(DT1, DT2)
byk <- c("ID", "s2", "e2")
setkeyv(DT1, byk)
setkeyv(DT2, byk)
o2 <- foverlaps(DT2, DT1)
olaps <- funion(o1, setcolorder(o2, names(o1)))[
is.na(Days), Days := i.Days]
outcome <- olaps[, {
if (all(!is.na(Days)) && any(Days == i.Days)) {
s <- .SD[Days == i.Days, .(Days = Days[1L],
X = X[1L],
Y = Y[1L])]
} else {
s <- .SD[, .(Days = max(Days, i.Days), X, Y)]
}
unique(s)
},
keyby = .(ID, md = pmax(Days, i.Days))][, md := NULL][]
return(outcome)
}
其结果是:
> testSO2(df1,df2)
ID Days X Y
1: patient1 0 1 11
2: patient1 116 2 12
3: patient1 225 3 13
4: patient1 309 4 14
5: patient1 315 4 15
6: patient1 351 5 NA
7: patient2 0 6 16
8: patient2 49 7 NA
9: patient2 91 NA 17
10: patient2 117 NA 18
如你所见,第4和第5行是错的。df 1中Score
的值被使用两次(4)。这些行周围的正确输出应该如下所示,因为每个分数(在本例中为X或Y)只能使用一次:
ID Days X Y
4: patient1 309 4 14
5: patient1 315 NA 15
6: patient1 351 5 NA
下面的dataframes代码。
df1 <- data.frame(
ID = rep(c("patient1", "patient2"), c(5L, 2L)),
Days = c("0", "116", "225", "309", "351", "0", "49"),
Score = 1:7
)
df2 <- data.frame(
ID = rep(c("patient1", "patient2"), c(5L, 3L)),
Days = c("0", "86", "195", "279", "315", "0", "91", "117"),
Score = 11:18
)
6条答案
按热度按时间ndasle7k1#
听起来像是一个现实但混乱的数据集的数据清理练习,不幸的是,我们大多数人都有过这样的经验。下面是另一个
data.table
选项:以下数据集的输出:
OP编辑中第二个数据集的输出:
数据(我从其他链接的帖子中添加了更多数据,并简化了数据以便于查看):
说明:
1.依次使用每个表作为左表执行2个重叠连接。
1.将右表中设置NA天前的2个结果与左表中的结果合并。
1.按患者和重叠日期分组。如果存在相同的日期,则保留记录。否则使用最大日期。
1.每个分数只能使用一次,因此删除重复项。
如果您发现这种方法不能给出正确结果的情况,请告诉我。
rxztt3cl2#
lapply
来查找 Days 中的差异低于 threshold 的位置,并进行expand.grid
以获得所有可能的组合。之后,删除那些将挑选相同的两次或正在挑选后面的另一个。从这些计算日差,并选择具有连续最低差异的线。然后rbind
与df2不匹配。数据:
0.. Boris Ruwe的第一个测试用例,1..Boris Ruwe的第二个测试用例,2..Boris Ruwe的第三个测试用例,3..Uwe的测试用例,4..Boris Ruwe的R rolling join two data.tables with error margin on join的测试用例,5..GKi的测试用例。
结果:
格式化结果:
获取
Days
的替代方法:在天数的 * 总 * 差异应最小化的情况下,允许不采用最近的,可能的方法将是:
zaqlnxep3#
作为迟到者,这里有一个解决方案,它使用了一个 * 完全外部连接 *,并根据OP的规则对行进行 * 后续分组和聚合 *。
对于OP的第一个测试用例,我们得到
果然不出所料。
其他用例验证
使用OP的第二个测试用例
我们得到
使用OP的第3个测试用例(用于讨论chinsoon12's answer)
我们得到
如OP所预期(特别参见第5行)
最后,我自己的测试用例在233和248之间有5个“重叠日”,来验证这个用例会被处理
我们得到
说明
完整外部连接
merge(..., all = TRUE)
在同一ID和日期上查找完全匹配,但包括两个数据集中没有匹配的所有其他行。在连接之前,每个数据集都会获得一个额外的列
o
,以指示每个Score
的 origin。结果是有序的,因为后续操作取决于正确的行顺序。
所以,在我自己的测试案例中
现在,使用
rleid()
创建一个分组变量:当满足以下条件之一时,组计数器将提前:
ID
改变ID
内,当连续的Days
之间的间隔超过30天时(因此ID内间隔为30天或更少的行属于一个组或“重叠”)1, 2, 1, 2, ...
或2, 1, 2, 1, ...
df1
开始的一行之后是从df2
开始的一行,或者从df2
开始的一行之后是从df1
开始的一行。最后一个条件没有被OP明确说明,但我的解释是
它确保 * 最多两行,每一行来自不同的数据集 * 正在折叠。
分组后我们得到
大多数组只包含一行,少数包含2行,在最后一步中折叠(按组聚合,返回所需的列并删除分组变量
g
)。改进代码
按组聚合要求每个组的每列只返回一个值(长度为1的向量)。(否则,组结果将由多行组成。)为了简单起见,上面的实现在所有4列上使用
last()
。last(Days)
等效于max(Days)
,因为数据集是有序的。然而,如果我理解正确的话,OP更倾向于从
df2
返回Days
值(尽管OP已经提到max(Days)
也是可以接受的)。为了从
df2
返回Days
值,需要修改聚合步骤:如果组大小.N
大于1,我们从源自df2
的行中选取Days
值,即其中o.y == 2
.这将返回
现在,折叠行5中的
Days
值234已从df2
中选取。对于
Score
列,使用last()
根本不重要,因为在一组2行中应该只有一个非NA值。因此,na.omit()
应该只返回一个值,而last()
可能只是为了保持一致性。toiithl64#
这段代码允许您给予一个阈值,然后将df 1中的分数合并到df 1中作为一个新列。它将仅添加落入df 2 +/-阈值中的分数的单个范围内的分数。请注意,不可能将所有分数连接起来,因为不存在所有分数唯一匹配的阈值。
q3aa05255#
以下是一个可能的
data.table
解决方案eiee3dmh6#
下面的代码适用于您的示例数据。根据您的条件,它应该适用于您的完整数据。对于其他例外情况,可以调整
df31
和df32
。