【问题标题】:R Multiple condition join using data.tableR使用data.table进行多条件连接
【发布时间】:2014-03-02 03:23:03
【问题描述】:

我有一个大型数据集和一个查找表。我需要为数据集中的每一行返回满足条件的查找行中存在的最小值。

鉴于我的数据集的大小,我不愿意通过交叉连接来破解一个不确定的解决方案,因为这会创建数百万条记录。我希望有人可以提出一个(理想情况下)利用 base r 或 data.table 的解决方案,因为它们已经以有效的方式使用。

示例

A<-seq(1e4,9e4,1e4)
B<-seq(0,1e4,1e3)

dt1<-data.table(expand.grid(A,B),ID=1:nrow(expand.grid(A,B)))
setnames(dt1, c("Var1","Var2"),c("A","B"))

lookup<-data.table(minA=c(1e4,1e4,2e4,2e4,5e4),
                 maxA=c(2e4,3e4,7e4,6e4,9e4),
                 minB=rep(2e3,5),
                 Val=seq(.1,.5,.1))

# Sample  Desired Value
     A     B    ID Val
99: 90000 10000 99 0.5

在 SQL 中,我会写一些类似的东西

SELECT ID, A, B, min(Val) as Val
FROM dt1
LEFT JOIN lookup on dt1.A>=lookup.minA
                 and dt1.A<=lookup.maxA
                 and dt1.B>=lookup.minB
GROUP BY ID, A, B

这将连接从lookupdt1 的所有匹配记录并返回最小的Val

更新

到目前为止,我的解决方案如下:

CJ.table<-function(X,Y) setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL]

dt1.lookup<- CJ.table(dt1,lookup)[A>=minA & A<=maxA & B>=minB,
                                  list(Val=Val[which.min( Val)]),
                                  by=list(ID,A,B)]
dt1.lookup<-rbind.fill(dt1.lookup, dt1[!ID %in% dt1.lookup$ID])

这会检索所有记录并允许在需要时从查找表中返回其他列。它还具有强制选择最小 Val 的好处。

【问题讨论】:

  • 注意:使用CJ(在data.table包中实现)会比expand.grid快。
  • @Arun 这是个好建议!非常感谢
  • 查看我的编辑以对您现有的解决方案进行微调。
  • 根据 A 和 B 的范围,您可能会考虑在交叉加入之前操纵 dt1,就像在这个问题中一样 stackoverflow.com/questions/16423817/…
  • 我的最后一次编辑可能是一个延伸,但你当然可以像 Senor O 那样预先过滤dt1 即。 dt1.prep&lt;-dt1[A&gt;=lookup[,min(minA)] &amp; A&lt;=lookup[,max(maxA)] &amp; B&gt;=lookup[,min(minB)]] 然后只交叉连接 dt1.prep 而不是整个 dt1

标签: r data.table


【解决方案1】:

我发现没有交叉连接的解决方案首先需要通过删除 AB 完全超出范围的行来准备数据:

Prep = dt1[A >= min(lookup$minA) & A <= max(lookup$maxA) & B >= min(lookup$minB)]

然后你制作一个数据表,其中每个条件都满足,对应于最低可能的Val

Indices = Prep[,list(min(which(A >= lookup$minA)), 
                     min(which(A <= lookup$maxA)), 
                     min(which(B >= lookup$minB)), A, B),by=ID]

那么你必须在所有三个条件都满足的最低点得到Val

Indices[,list(Val=lookup$Val[max(V1,V2,V3)], A, B),by=ID]

看看这是否能满足您的需求:

   ID Val     A     B
 1: 19 0.1 10000  2000
 2: 20 0.1 20000  2000
 3: 21 0.2 30000  2000
 4: 22 0.3 40000  2000
 5: 23 0.3 50000  2000
 6: 24 0.3 60000  2000
 7: 25 0.3 70000  2000
 8: 26 0.5 80000  2000
 9: 27 0.5 90000  2000
10: 28 0.1 10000  3000

【讨论】:

  • 好主意!会把我的头绕在它周围,然后回复你
  • 感谢@Senor,不幸的是,这看起来不能保证在多个查找记录匹配时选择最小 Val。在这种情况下,交叉连接对性能不利,但我采用了您的解决方案,并根据另一个 SO 回答重新对其进行了一些更改:交叉连接 stackoverflow.com/questions/10600060/how-to-do-cross-join-in-r/…
【解决方案2】:

我的第一个想法是尝试像 Senor O 那样制作索引。然而,min(Val) 让我更难以思考索引表。我想这样做的方法是遍历查找表。

dt1[,Val:=as.numeric(NA)]
for (row in 1:NROW(lookup)) {
  dt1[A>=lookup[order(Val)][row,minA]&A<=lookup[order(Val)][row,maxA]&B>=lookup[order(Val)][row,minB]&is.na(Val),Val:=lookup[order(Val)][row,Val]]
  }

我认为这应该可行,因为它首先使用 NA 值设置新列。

然后它将查找表按Val 排序,因此您将获得它的最低值。

在每个循环中,如果 NA 中的 Val 中的值仍然是 NA,它只会潜在地更改 dt1 中的值,并且由于我们按照最小的 Val 到最大的顺序循环遍历 lookup,它将确保您得到你想要的min(Val)

将 rbind.fill 行替换为

rbindlist(list(dt1.lookup,dt1[!ID %in% dt1.lookup[,ID]][,list(ID, A, B, Val=as.numeric(NA))]))

它将消除对reshape 包的依赖,我认为它会更快。

【讨论】:

  • 我不确定循环概念对于我的 800k 记录是否可行 - 不过肯定会看看
  • 我做了几个快速的system.time 测试,当我将dt1 增加到 900900 行时,Senor 的速度提高了大约 3 倍,但两者的时间仍然不到 1 秒。当 Senor 的答案出现时,我已经写了一半了,至少在我的屏幕上,所以我觉得我已经完成了。
  • 感谢 Dean,不幸的是,这太耗时了,因为我的查找表有 300 多条记录,而且在我的实际任务中,我需要根据 Val 的派生变量进行选择,这证明了超出我的技能使其按要求工作。我发布了我目前工作的解决方案,所以如果您有任何进一步的想法,我将不胜感激
猜你喜欢
  • 2016-11-12
  • 2015-04-29
  • 2013-10-03
  • 2021-03-11
  • 1970-01-01
  • 2014-03-28
  • 2020-09-16
  • 2021-11-25
  • 2017-02-15
相关资源
最近更新 更多