在第二个数据框的每个元素中查找到第一个数据框的最小距离。

5

我有两个数据框ev1和ev2,描述了在许多测试中收集的两种事件的时间戳。因此,每个数据框都有“test_id”和“timestamp”列。我需要找到每个相同测试中ev1与ev2之间的最小时间距离。

我有一段可行的代码,它合并了这两个数据集,计算了距离,然后使用dplyr过滤最小距离:

ev1 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(1, 2, 3, 2, 3, 4))
ev2 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(6, 1, 8, 4, 5, 11))

data <- merge(ev2, ev1, by=c("test_id"), suffixes=c(".ev2", ".ev1"))

data$distance <- data$time.ev2 - data$time.ev1

min_data <- data %>%
  group_by(test_id, time.ev2) %>%
  filter(abs(distance) == min(abs(distance)))

虽然这样做可以实现功能,但合并部分非常缓慢,效率低下--我会生成一个包含所有ev2->ev1组合的巨大表格,只为了将其过滤到一个。似乎应该有一种方法在合并过程中“实时过滤”。有吗?
更新:当使用akrun提供的data.table方法时,下面有两个“group by”列的情况会失败:
ev1 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(1, 2, 3, 2, 3, 4), group_id=c(0, 0, 0, 1, 1, 1))
ev2 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(5, 6, 7, 1, 2, 8), group_id=c(0, 0, 0, 1, 1, 1))
setkey(setDT(ev1), test_id, group_id)
DT <- ev1[ev2, allow.cartesian=TRUE][,distance:=abs(time-i.time)]

在计算(expr, envir, enclos)时出错:对象“i.time”未找到


2
你尝试过使用left_join(ev2,ev1, by=c("test_id") )而不是merge吗? - Khashaa
@Khashaa 不,我没有。我现在尝试了一下,速度快得多。这只是同样事物的更好实现吗,还是其他原因呢? - Stan
@Stan 你尝试过data.table方法吗?它应该非常快速。 - akrun
@Stan 它是 dplyrmerge(., all.x=TRUE) 的类比。 - Khashaa
2个回答

6

以下是我使用 data.table 的方法:

require(data.table)
setkey(setDT(ev1), test_id)
ev1[ev2, .(ev2.time = i.time, ev1.time = time[which.min(abs(i.time - time))]), by = .EACHI]
#    test_id ev2.time ev1.time
# 1:       0        6        3
# 2:       0        1        1
# 3:       0        8        3
# 4:       1        4        4
# 5:       1        5        4
# 6:       1       11        4

data.table 中的形式为 x[i] 的连接中,当 xi 对于特定列具有相同的名称时,前缀 i. 用于引用 i 中的列。

请参见此SO帖子以了解其工作原理。

语法上更加直观易懂,并且在内存效率上更优(牺牲一点速度1),因为它根本不会将整个连接结果实体化。事实上,这正是您在帖子中所说的 - 边合并边过滤

  1. 关于速度,在大多数情况下并不重要。如果 i 中有很多行,则对每行在 j 表达式中进行评估可能会稍微慢一些。相反,@akrun的答案执行笛卡尔积,然后进行一次过滤。因此,虽然它的内存占用高,但它不会对 i 中的每一行都评估 j。但再次强调,除非您使用的是真正的大型 i,否则甚至不需要考虑这一点。

希望对您有所帮助。


这太完美了--就像你所说的,正是我希望找到的东西,它不会一开始就创建巨大的连接。当你说“以牺牲一点速度为代价”时,你是什么意思?是什么让它比创建完整的连接然后过滤要慢一点?在我的大测试用例中,它似乎甚至更快一些。 - Stan
@Stan,太棒了!很高兴听到它对你有帮助。编辑以澄清你在帖子中的问题。 - Arun

0

也许这可以帮到您:

library(data.table)
setkey(setDT(ev1), test_id)
DT <- ev1[ev2, allow.cartesian=TRUE][,distance:=time-i.time]
DT[DT[,abs(distance)==min(abs(distance)), by=list(test_id, i.time)]$V1]
#    test_id time i.time distance
#1:       0    3      6        3
#2:       0    1      1        0
#3:       0    3      8        5
#4:       1    4      4        0
#5:       1    4      5        1
#6:       1    4     11        7

或者

 ev1[ev2, allow.cartesian=TRUE][,distance:= time-i.time][,
      .SD[abs(distance)==min(abs(distance))], by=list(test_id, i.time)]

更新

使用新的分组方式

setkey(setDT(ev1), test_id, group_id)
setkey(setDT(ev2), test_id, group_id)
DT <- ev1[ev2, allow.cartesian=TRUE][,distance:=i.time-time]
DT[DT[,abs(distance)==min(abs(distance)), by=list(test_id, 
                                group_id,i.time)]$V1]$distance
#[1]  2  3  4 -1  0  4

根据您提供的代码

min_data$distance
#[1]  2  3  4 -1  0  4

我不太熟悉data.table,所以我需要更仔细地看这个问题才能理解它:)。我必须改变的一件事是将abs()移动到第二行——abs(distance) == min(abs(distance)),这样我仍然可以获得负距离(即ev2在ev1之后发生)。 - Stan
@Stan 你可以使用 setkey(setDT(ev2), test_id, group_id),然后尝试使用 by=list(test_id, group_id, i.time)] 步骤。 - akrun
我没有走得那么远,错误发生在“DT <- ...”这一行。经过一些尝试,列的顺序似乎很重要。如果group_id紧跟test_id,那么你的代码就可以工作。如果它在最后(如上面的示例),那么它就不行。 - Stan
@Stan 请展示一个类似于你的数据集的例子。通过使用你展示的代码进行测试,得到的结果是相同的。 - akrun
@Stan 我尝试了两种方式,都可以正常运行setkey(setDT(ev1), group_id, test_id); setkey(setDT(ev2), group_id, test_id); ev1[ev2, allow.cartesian=TRUE][,distance:=i.time-time][] test_id time group_id i.time distance 1: 0 1 0 5 4 2: 0 2 0 5 3` - akrun
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接