dplyr left_join 小于,大于条件
Posted
技术标签:
【中文标题】dplyr left_join 小于,大于条件【英文标题】:dplyr left_join by less than, greater than condition 【发布时间】:2016-09-14 08:06:13 【问题描述】:这个问题与Efficiently merging two data frames on a non-trivial criteria 和Checking if date is between two dates in r 问题有些相关。我在此处发布的请求是否存在该功能: GitHub issue
我希望使用dplyr::left_join()
加入两个数据框。我用来加入的条件是小于、大于,即<=
和>
。 dplyr::left_join()
支持这个功能吗?或者键只在它们之间使用=
运算符。这很容易从 SQL 运行(假设我在数据库中有数据框)
这是一个 MWE:我有两个数据集,一年一次 (fdata
),而第二个是每五年发生一次的调查数据。因此,对于两个调查年份之间的fdata
中的所有年份,我都会加入相应的调查年份数据。
id <- c(1,1,1,1,
2,2,2,2,2,2,
3,3,3,3,3,3,
5,5,5,5,
8,8,8,8,
13,13,13)
fyear <- c(1998,1999,2000,2001,1998,1999,2000,2001,2002,2003,
1998,1999,2000,2001,2002,2003,1998,1999,2000,2001,
1998,1999,2000,2001,1998,1999,2000)
byear <- c(1990,1995,2000,2005)
eyear <- c(1995,2000,2005,2010)
val <- c(3,1,5,6)
sdata <- tbl_df(data.frame(byear, eyear, val))
fdata <- tbl_df(data.frame(id, fyear))
test1 <- left_join(fdata, sdata, by = c("fyear" >= "byear","fyear" < "eyear"))
我明白了
Error: cannot join on columns 'TRUE' x 'TRUE': index out of bounds
除非left_join
可以处理该条件,但我的语法缺少什么?
【问题讨论】:
您不妨订阅github.com/tidyverse/dplyr/issues/2240 【参考方案1】:一种选择是按行加入列表列,然后取消嵌套该列:
# evaluate each row individually
fdata %>% rowwise() %>%
# insert list column of single row of sdata based on conditions
mutate(s = list(sdata %>% filter(fyear >= byear, fyear < eyear))) %>%
# unnest list column
tidyr::unnest()
# Source: local data frame [27 x 5]
#
# id fyear byear eyear val
# (dbl) (dbl) (dbl) (dbl) (dbl)
# 1 1 1998 1995 2000 1
# 2 1 1999 1995 2000 1
# 3 1 2000 2000 2005 5
# 4 1 2001 2000 2005 5
# 5 2 1998 1995 2000 1
# 6 2 1999 1995 2000 1
# 7 2 2000 2000 2005 5
# 8 2 2001 2000 2005 5
# 9 2 2002 2000 2005 5
# 10 2 2003 2000 2005 5
# .. ... ... ... ... ...
【讨论】:
就像我的回答一样,这不会产生有效的LEFT JOIN
。使用fyear==2011
使用观察结果扩充左侧数据框,然后在fyear==2011
上过滤您的查询结果,那里什么都没有。这适用于 SQL:SELECT * FROM fdata LEFT JOIN sdata ON fyear >= year AND fyear < eyear
。【参考方案2】:
data.table
从 v 1.9.8 开始添加非等连接
library(data.table) #v>=1.9.8
setDT(sdata); setDT(fdata) # converting to data.table in place
fdata[sdata, on = .(fyear >= byear, fyear < eyear), nomatch = 0,
.(id, x.fyear, byear, eyear, val)]
# id x.fyear byear eyear val
# 1: 1 1998 1995 2000 1
# 2: 2 1998 1995 2000 1
# 3: 3 1998 1995 2000 1
# 4: 5 1998 1995 2000 1
# 5: 8 1998 1995 2000 1
# 6: 13 1998 1995 2000 1
# 7: 1 1999 1995 2000 1
# 8: 2 1999 1995 2000 1
# 9: 3 1999 1995 2000 1
#10: 5 1999 1995 2000 1
#11: 8 1999 1995 2000 1
#12: 13 1999 1995 2000 1
#13: 1 2000 2000 2005 5
#14: 2 2000 2000 2005 5
#15: 3 2000 2000 2005 5
#16: 5 2000 2000 2005 5
#17: 8 2000 2000 2005 5
#18: 13 2000 2000 2005 5
#19: 1 2001 2000 2005 5
#20: 2 2001 2000 2005 5
#21: 3 2001 2000 2005 5
#22: 5 2001 2000 2005 5
#23: 8 2001 2000 2005 5
#24: 2 2002 2000 2005 5
#25: 3 2002 2000 2005 5
#26: 2 2003 2000 2005 5
#27: 3 2003 2000 2005 5
# id x.fyear byear eyear val
您还可以在 1.9.6 中将其与 foverlaps
一起工作,但需要付出更多努力。
【讨论】:
setDF
如果有人想将他的数据集返回到普通的data.frame,可以在之后使用
@eddi 加入后,在获取列时是否有一个相当于 .(i.*, x.fear) 的 data.table ,即表 i 中的所有列,但只担心表 x 谢谢。
这个解决方案比tidyr
/dplyr
的解决方案更干净、更快捷,并且在添加更多条件时也能正常工作。
fyear >= byear, fyear
原来我需要在 on 元素之后添加 .(fyear = x. fyear,....不知道为什么,但它现在有效。如果在小插曲中有对此的解释,那就太好了。【参考方案3】:
使用filter
。 (但请注意,此答案不会产生正确的 LEFT JOIN
;但 MWE 会使用 INNER JOIN
给出正确的结果。)
dplyr
包如果被要求合并两个表而没有要合并的东西会不高兴,所以在下面,我为此在两个表中创建一个虚拟变量,然后过滤,然后删除 dummy
:
fdata %>%
mutate(dummy=TRUE) %>%
left_join(sdata %>% mutate(dummy=TRUE)) %>%
filter(fyear >= byear, fyear < eyear) %>%
select(-dummy)
请注意,如果您在 PostgreSQL 中执行此操作(例如),查询优化器会看穿 dummy
变量,如以下两个查询说明所示:
> fdata %>%
+ mutate(dummy=TRUE) %>%
+ left_join(sdata %>% mutate(dummy=TRUE)) %>%
+ filter(fyear >= byear, fyear < eyear) %>%
+ select(-dummy) %>%
+ explain()
Joining by: "dummy"
<SQL>
SELECT "id" AS "id", "fyear" AS "fyear", "byear" AS "byear", "eyear" AS "eyear", "val" AS "val"
FROM (SELECT * FROM (SELECT "id", "fyear", TRUE AS "dummy"
FROM "fdata") AS "zzz136"
LEFT JOIN
(SELECT "byear", "eyear", "val", TRUE AS "dummy"
FROM "sdata") AS "zzz137"
USING ("dummy")) AS "zzz138"
WHERE "fyear" >= "byear" AND "fyear" < "eyear"
<PLAN>
Nested Loop (cost=0.00..50886.88 rows=322722 width=40)
Join Filter: ((fdata.fyear >= sdata.byear) AND (fdata.fyear < sdata.eyear))
-> Seq Scan on fdata (cost=0.00..28.50 rows=1850 width=16)
-> Materialize (cost=0.00..33.55 rows=1570 width=24)
-> Seq Scan on sdata (cost=0.00..25.70 rows=1570 width=24)
使用 SQL 更简洁地执行此操作会得到完全相同相同的结果:
> tbl(pg, sql("
+ SELECT *
+ FROM fdata
+ LEFT JOIN sdata
+ ON fyear >= byear AND fyear < eyear")) %>%
+ explain()
<SQL>
SELECT "id", "fyear", "byear", "eyear", "val"
FROM (
SELECT *
FROM fdata
LEFT JOIN sdata
ON fyear >= byear AND fyear < eyear) AS "zzz140"
<PLAN>
Nested Loop Left Join (cost=0.00..50886.88 rows=322722 width=40)
Join Filter: ((fdata.fyear >= sdata.byear) AND (fdata.fyear < sdata.eyear))
-> Seq Scan on fdata (cost=0.00..28.50 rows=1850 width=16)
-> Materialize (cost=0.00..33.55 rows=1570 width=24)
-> Seq Scan on sdata (cost=0.00..25.70 rows=1570 width=24)
【讨论】:
如前所述,这不会产生正确的 left_join,因为它会从左侧数据集中删除行,而右侧过滤器中没有匹配项。【参考方案4】:这看起来像是 fuzzyjoin 包解决的任务。该包的各种功能的外观和工作方式类似于 dplyr 连接功能。
在这种情况下,fuzzy_*_join
函数之一将为您工作。 dplyr::left_join
和 fuzzyjoin::fuzzy_left_join
之间的主要区别在于,您使用 match.fun
参数提供了在匹配过程中使用的函数列表。请注意,by
参数的写法仍然与left_join
中的写法相同。
下面是一个例子。我用来匹配的函数分别是 >=
和 <
用于 fyear
到 byear
和 fyear
到 eyear
比较。
library(fuzzyjoin)
fuzzy_left_join(fdata, sdata,
by = c("fyear" = "byear", "fyear" = "eyear"),
match_fun = list(`>=`, `<`))
Source: local data frame [27 x 5]
id fyear byear eyear val
(dbl) (dbl) (dbl) (dbl) (dbl)
1 1 1998 1995 2000 1
2 1 1999 1995 2000 1
3 1 2000 2000 2005 5
4 1 2001 2000 2005 5
5 2 1998 1995 2000 1
6 2 1999 1995 2000 1
7 2 2000 2000 2005 5
8 2 2001 2000 2005 5
9 2 2002 2000 2005 5
10 2 2003 2000 2005 5
.. ... ... ... ... ...
【讨论】:
这不能很好地扩展,但它是一个很好且易于理解的解决方案 @aosmith 感谢您的解决方案。可以延长间隔吗?即fyear >= byear-20
和fyear < eyear+5
这似乎不适用于日期 - 与 Excel 对相同数据的 vlookup 相比,它的阻塞速度非常快。以上是关于dplyr left_join 小于,大于条件的主要内容,如果未能解决你的问题,请参考以下文章
使用by = c(x = y)错误在函数内执行dplyr :: left_join
使用 by=c(x=y) 错误在函数内执行 dplyr::left_join
dplyr::left_join 是不是等同于 base::merge(..., all.x=TRUE)?