r语言 - 如何使用自定义函数合并两个数据集,该函数将规则应用于非公共列?



我正在尝试合并两个不同大小的数据帧,但是由于数据的面板结构,我遇到了困难。

考虑下面的例子,其中'toy. 'Left '是一个包含三个变量的面板:坐标(' cord ')和在特定月份('month')分配给该坐标的名称('name')。接下来,考虑"玩具"。Right,',它由四个变量组成:名称('name'),该名称对该坐标的赋值任期的开始时间('tenure.start'),以及对该坐标的赋值任期的结束时间('tenure.end')。

toy.left <- tribble(~month, ~coord, ~name, 
"2000-01-01", 1301, "Alpha", 
"2000-03-01", 1301, "Beta", 
"2000-06-01", 1302, "Charlie", 
"2000-09-01", 1303, "Delta", 
"2000-12-01", 1303, "Epsilon")
toy.right <- tribble(~name, ~coord, ~tenure.start, ~tenure.end, 
"Alpha", 1301, "2000-02-01", "2000-04-01", 
"Beta", 1301, "1999-11-01", "2000-04-01", 
"Charlie", 1302,  "2000-04-01", "2000-07-01", 
"Delta", 1303, "2000-08-01", "2000-10-01", 
"Epsilon", 1303, "2000-11-01", "2001-01-01", 
"Delta", 1303, "2002-01-01", "2004-01-01")

我想合并这两个数据集,但是有一些规则使得在dplyr中使用merge()变得困难。例如,我不能简单地使用inner_join()并按'name'和'coord'进行合并,因为这违反了数据的面板结构。如果我这样做,个人的任期不会与观察的月份重叠(首先,参见第1行& &;2、应倒置;第二,参见第4行& &;5,其中merge()重复月份观察值,但应该只包括第4行)。

toy.left %>% 
inner_join(toy.right, by = c("name", "coord"))
*Output*
month       coord   name   tenure.start tenure.end 
2000-01-01  1301    Alpha   2000-02-01  2000-04-01
2000-03-01  1301    Beta    1999-11-01  2000-04-01
2000-06-01  1302    Charlie 2000-04-01  2000-07-01
2000-09-01  1303    Delta   2000-08-01  2000-10-01
2000-09-01  1303    Delta   2002-01-01  2004-01-01
2000-12-01  1303    Epsilon 2000-11-01  2001-01-01

为了解决这个问题,我可以按'name ',' coord ','合并数据'month',但我需要根据日期是否落在'tenure '之间来对'month'进行合并。' Start '和'tenure.end.'经过搜索,我找不到一种方法来应用自定义规则来合并()在dplyr。

我知道自定义函数或循环可能是解决这个问题的最佳方法,但我不确定从哪里开始。此外,原始数据集有超过150万个观测值,这可能会产生进一步的问题。

我欢迎你的建议!

(所有这些都是在将monthtenure.*转换为Date之后)

fuzzyjoin

fuzzyjoin::fuzzy_inner_join(
toy.left, toy.right,
by=c("name", "coord", month="tenure.start", month="tenure.end"), 
match_fun=list(`==`, `==`, `>=`, `<=`))
# # A tibble: 4 x 7
#   month      coord.x name.x  name.y  coord.y tenure.start tenure.end
#   <date>       <dbl> <chr>   <chr>     <dbl> <date>       <date>    
# 1 2000-03-01    1301 Beta    Beta       1301 1999-11-01   2000-04-01
# 2 2000-06-01    1302 Charlie Charlie    1302 2000-04-01   2000-07-01
# 3 2000-09-01    1303 Delta   Delta      1303 2000-08-01   2000-10-01
# 4 2000-12-01    1303 Epsilon Epsilon    1303 2000-11-01   2001-01-01

sqldf

sqldf::sqldf(
"select tl.name, tl.coord, tl.month, tr.[tenure.start], tr.[tenure.end]
from [toy.left] tl
inner join [toy.right] tr on tl.name=tr.name and tl.coord=tr.coord
and tl.month between tr.[tenure.start] and tr.[tenure.end]")
#      name coord      month tenure.start tenure.end
# 1    Beta  1301 2000-03-01   1999-11-01 2000-04-01
# 2 Charlie  1302 2000-06-01   2000-04-01 2000-07-01
# 3   Delta  1303 2000-09-01   2000-08-01 2000-10-01
# 4 Epsilon  1303 2000-12-01   2000-11-01 2001-01-01

(我使用带括号符号的[tenure.start]来区分表标识符tl和列名tenure.start,在SQL中,列名中的点通常表示类似schema.tablename.columnname的命名法。)

data.table

这是左连接,而不是其他类型。为了确定由于使用left而不是inner而应该删除哪些内容,我将在toy.left中添加一列:

library(data.table)
setDT(toy.left)
setDT(toy.right)
toy.left[, val := 2]
toy.left[toy.right, on = .(name, coord, month >= tenure.start, month <= tenure.end)][ !is.na(val),]
#         month coord    name   val    month.1
#        <Date> <num>  <char> <num>     <Date>
# 1: 1999-11-01  1301    Beta     2 2000-04-01
# 2: 2000-04-01  1302 Charlie     2 2000-07-01
# 3: 2000-08-01  1303   Delta     2 2000-10-01
# 4: 2000-11-01  1303 Epsilon     2 2001-01-01

data.table有它的重命名列的方式,所以要注意它。当我不确定如何命名时,我经常复制周围的列,以便始终清晰……但我这样做的部分原因是我懒得去学习它是如何确定结果名称的。