我正在努力有效地在两个数据帧之间执行“关闭”日期匹配。这个问题探讨了使用idata.frame
包中plyr
的解决方案,但我对其他建议的解决方案也非常满意。
这是两个数据框的非常简单的版本:
sampleticker<-data.frame(cbind(ticker=c("A","A","AA","AA"),
date=c("2005-1-25","2005-03-30","2005-02-15","2005-04-21")))
sampleticker$date<-as.Date(sampleticker$date,format="%Y-%m-%d")
samplereport<-data.frame(cbind(ticker=c("A","A","A","AA","AA","AA"),
rdate=c("2005-2-15","2005-03-15","2005-04-15",
"2005-03-01","2005-04-20","2005-05-01")))
samplereport$rdate<-as.Date(samplereport$rdate,format="%Y-%m-%d")
在实际数据中,sampleticker
超过30,000行,有40列,samplereport
近300,000行,有25列。
我想要做的是合并两个数据框,以便将sampleticker
中的每一行与samplereport
中最接近的日期匹配相结合,该日期匹配发生在sampleticker
中的日期之后。我在过去通过对股票代码字段进行简单合并,对升序进行排序,然后选择股票代码和日期的唯一组合来解决类似的问题。但是,由于此数据集的大小,合并的速度非常快。
尽管我可以说,merge
不允许这种近似匹配。我见过一些使用findInterval
的解决方案,但由于日期之间的距离会有所不同,我不确定是否可以指定适用于所有行的间隔。
在另一篇帖子here之后,我编写了以下代码,以便在每一行上使用adply
并执行联接:
library(plyr)
merge<-adply(sampleticker,1,function(x){
y<-subset(samplereport,ticker %in% x$ticker & rdate > x$date)
y[which.min(y$rdate),]
}))
这非常有效:对于样本数据,我得到了下面的内容,这就是我想要的内容。
date ticker rdate
1 2005-01-25 A 2005-02-15
2 2005-03-30 A 2005-04-15
3 2005-02-15 AA 2005-03-01
4 2005-04-21 AA 2005-05-01
但是,由于代码执行了30,000多个子集操作,因此速度非常慢:我运行上述查询超过一天,最后杀死它。
我看到here plyr 1.0有一个结构idata.frame
,它通过引用调用数据帧,大大加快了子集化操作。但是,我无法使用以下代码:
isamplereport<-idata.frame(samplereport)
adply(sampleticker,1,function(x){
y<-subset(isamplereport,isamplereport$ticker %in% x$ticker &
isamplereport$rdate > x$date)
y[which.min(y$rdate),]
})
我收到错误
Error in list_to_dataframe(res, attr(.data, "split_labels")) :
Results must be all atomic, or all data frames
这对我来说很有意义,因为操作返回idata.frame
(我假设)。但是,将最后一行更改为:
as.data.frame(y[which.min(y$rdate),])
也会抛出错误:
Error in `[.data.frame`(x$`_data`, x$`_rows`, x$`_cols`) :
undefined columns selected.
请注意,在普通旧版as.data.frame
上调用samplereport
会按预期返回原始数据框。
我知道idata.frame
是实验性的,所以我不一定希望它能正常工作。但是,如果有人知道如何解决这个问题,我将不胜感激。或者,如果任何人都可以建议一种运行效率更高的完全不同的方法,那就太棒了。
马特
更新 Data.table是正确的方法。见下文。
答案 0 :(得分:8)
感谢Matthew Dowle以及他在data.table中向前滚动和向前滚动的能力,现在执行此合并变得更加简单。
ST <- data.table(sampleticker)
SR <- data.table(samplereport)
setkey(ST,ticker,date)
SR[,mergerdate:=rdate]
setkey(SR,ticker,mergerdate)
merge<-SR[ST,roll=-Inf]
setnames(merge,"mergerdate","date")
# ticker date rdate
# 1: A 2005-01-25 2005-02-15
# 2: A 2005-03-30 2005-04-15
# 3: AA 2005-02-15 2005-03-01
# 4: AA 2005-04-21 2005-05-01
答案 1 :(得分:6)
这是一个基于data.table
的解决方案,可能比您目前使用的解决方案效果更好:
library(data.table)
ST <- data.table(sampleticker, key="ticker")
SR <- data.table(samplereport, key="ticker")
SR <- SR[with(SR, order(ticker, rdate)),] # rdates need to be in increasing order
SR[ST, list(date = date,
rdate = rdate[match(TRUE, (rdate > date))]), ]
ticker date rdate
[1,] A 2005-01-25 2005-02-15
[2,] A 2005-03-30 2005-04-15
[3,] AA 2005-02-15 2005-03-01
[4,] AA 2005-04-21 2005-05-01
当然,听起来你真正想做的是将两个更广泛的data.frames合并在一起。为了演示实现这一目的的一种方法,在下面的示例中,我向data.tables添加了一些列,然后展示了如何合并相应的行:
# Add some columns to both data.tables
ST$alpha <- letters[seq_len(nrow(ST))]
SR$n <- seq_len(nrow(SR))
SR$ALPHA <- LETTERS[seq_len(nrow(SR))]
# Perform a merge that includes the whole rows from samplereport
# corresponding to the selected rdate
RES <- SR[ST, cbind(date, .SD[match(TRUE,(rdate>date)),-1, with=FALSE]), ]
# Merge res (containing the selected rows from samplereport) back together
# with sampleticker
keycols <- c("ticker", "date")
setkeyv(RES, keycols)
setkeyv(ST, keycols)
ST[RES]
# ticker date alpha rdate n ALPHA
# [1,] A 2005-01-25 a 2005-02-15 1 A
# [2,] A 2005-03-30 b 2005-04-15 3 C
# [3,] AA 2005-02-15 c 2005-03-01 4 D
# [4,] AA 2005-04-21 d 2005-05-01 6 F
答案 2 :(得分:4)
这是一个解决方案,跟随Matthew Dowle的观察,这是一个应用data.table
的{{1}}论证的自然场所。
如果您要使用它,可以解决一个皱纹。 roll=TRUE
的设计使得当找不到密钥的最后一列(此处为日期)的完全匹配时,最近的上一个日期的值将被滚动转发。但是,您想要相反(即使存在完全匹配,您仍然需要下一个可用日期的值)。
第一次尝试可能是按roll=TRUE
排序,按"ticker"
按相反顺序排序,与生成的重新排序的"rdate"
合并。除非SR
不希望让您按相反的顺序排序,否则这会有效:按data.table
键将该列强制升序。 ("rdate"
需要这样做才能实现其设计的快速匹配和连接。
我的解决方案是在两个data.tables中创建一个新列 - data.table
,用于“反向数字日期” - 其值通过执行"rnd"
形成。这为每个日期分配了唯一值。此外,由于值已乘以-as.numeric(date)
,按升序对其进行排序会产生按降序排序日期的效果。
(另一个细节:因为你不想要完全匹配,而是总是希望在当前日期之后的下一个日期,我从样本标签-1
中减去了1
,它具有所需的为了确认它正确地完成了它的工作,我稍微编辑了你的示例数据,以包括一个可能的完全匹配(rnd
),它不应该由合并选择)。
"2005-1-25"
掌握样本数据,设置并执行所需的合并:
# Create sample data.tables
library(data.table)
ST <- data.table(ticker = c("A","A","AA","AA"),
date = as.Date(c("2005-1-25","2005-03-30","2005-02-15",
"2005-04-21"), format="%Y-%m-%d"),
alpha = letters[1:4])
SR <- data.table(ticker = c("A","A","A","AA","AA","AA"),
rdate = as.Date(c("2005-1-25","2005-03-15","2005-04-15",
"2005-03-01","2005-04-20","2005-05-01"),
format="%Y-%m-%d"),
ALPHA = LETTERS[1:6])