【问题标题】:Find the minimum distance between two data frames, for each element in the second data frame对于第二个数据帧中的每个元素,找到两个数据帧之间的最小距离
【发布时间】:2015-02-12 12:00:56
【问题描述】:

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

我有一个工作代码可以合并两个数据集,计算距离,然后使用 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)))

虽然这可行,但合并部分非常慢并且感觉效率低下 - 我正在生成一个巨大的表,其中包含相同 test_id 的所有 ev2->ev1 组合,只是将其过滤为一个。在合并期间,似乎应该有一种“动态过滤”的方法。有没有?

更新:当使用 akrun 概述的 data.table 方法时,以下带有两个“分组依据”列的情况会失败:

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)]

eval 中的错误(expr、envir、enclos):找不到对象“i.time”

【问题讨论】:

  • 你试过left_join(ev2,ev1, by=c("test_id") )而不是merge吗?
  • @Khashaa 不,我没有。我现在试了一下,它的速度无限快。它只是对同一件事的更好实现,还是发生了其他事情?
  • @Stan 您是否尝试过data.table 方法。它应该很快。
  • @Stan 它是merge(., all.x=TRUE)dplyr 类似物。

标签: r plyr dplyr


【解决方案1】:

这是我使用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] 形式的连接中,前缀i. 用于引用i 中的列,此时xi 共享特定列的相同名称。

请参阅this SO post 了解其工作原理。

这在语法上更容易理解正在发生的事情,并且内存效率高(以低速度为代价1),因为它根本不会实现整个连接结果。事实上,这正是您在帖子中所说的 - 在合并时动态过滤

  1. 在大多数情况下,速度并不重要。如果i 中有很多 行,它可能会慢一点,因为必须为@987654332 中的每一行评估j-表达式@。相比之下,@akrun 的答案是进行笛卡尔连接,然后进行一次过滤。因此,虽然它的内存很高,但它不会为i 中的每一行评估j。但同样,除非您使用 非常大 i,否则这甚至都无关紧要,这种情况并不常见。

HTH

【讨论】:

  • 这是完美的——就像你说的那样,正是我希望找到的,一开始就不会产生巨大连接的东西。当您说“以牺牲速度为代价”时,您是什么意思?是什么让这比创建完整连接然后过滤慢一点?在我的大测试用例上,它似乎还要快一点。
  • @Stan,太棒了!很高兴听到它有帮助。编辑以在帖子中澄清您的 Q。
【解决方案2】:

这可能会有所帮助:

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 之后)。
  • 一个问题——我的真实示例有两列(我们称它们为 test_id 和 group_id),它们共同构成了分组键。我将如何更改上面的代码以实现这一点?我尝试了setkey(setDT(ev1), test_id, group_id),它通过了,但是我在下一行得到一个错误,即找不到对象'i.time',这让我感到困惑。
  • @Stan 是的,您可以将其移至第二行。我只是将min_data 作为参考。您能否展示一个给出错误的示例(使其更易于查看)?
  • 我添加了一个更新,其中包含一个给出错误的示例。
  • @Stan 您可以使用setkey(setDT(ev2), test_id, group_id),然后尝试使用by=list(test_id, group_id, i.time)] 的步骤
猜你喜欢
  • 2020-02-06
  • 2013-10-02
  • 2014-06-18
  • 1970-01-01
  • 1970-01-01
  • 2018-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多