7

我正在努力有效地在两个数据框之间执行“关闭”日期匹配。这个问题探讨了使用包中的解决方案idata.frameplyr但我也会对其他建议的解决方案感到非常满意。

这是两个数据框的一个非常简单的版本:

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,但由于日期之间的距离会有所不同,我不确定我是否可以指定一个适用于所有行的间隔。

在此处的另一篇文章之后,我编写了以下代码以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 多个子集操作,因此速度非常慢:我运行了上述查询一天多,最终将其杀死。

在这里看到 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普通的 oldsamplereport会按预期返回原始数据帧。

我知道这idata.frame是实验性的,所以我不一定期望它能够正常工作。但是,如果有人对如何解决此问题有任何想法,我将不胜感激。或者,如果有人能提出一种运行效率更高的完全不同的方法,那就太好了。

马特

UPDATE Data.table 是解决此问题的正确方法。见下文。

4

3 回答 3

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
于 2013-03-06T22:29:01.730 回答
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.frame 合并在一起。为了演示实现这一点的一种方法,在下面的示例中,我向两个 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]), ]

# 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
于 2012-02-13T23:28:45.577 回答
4

这是 Matthew Dowle 观察到的一个解决方案,即这是应用data.tableroll=TRUE论点的自然场所。

如果你要应用它,有一个皱纹需要熨平。roll=TRUE设计为当没有为键的最后一列(此处为日期)找到完全匹配时,将前滚最近的一个日期值。但是,您需要相反的结果(即使存在完全匹配,您仍然需要下一个可用日期的值)。

第一次尝试可能是 sort by"ticker"和 by"rdate" 以相反的顺序,与结果 reordered 合并SR。这会起作用,除了data.table不想让你以相反的顺序排序:通过"rdate"强制该列按升序进行键控。(data.table需要这样做以实现其设计的快速匹配和连接)。

我下面的解决方案是在两个 data.tables 中创建一个新列 - "rnd",用于“反向数字日期”,其值是通过 do 形成的-as.numeric(date)。这会为每个日期分配一个唯一值。此外,由于这些值已乘以-1,因此按升序对它们进行排序具有按降序对日期进行排序的效果

(另一个细节:因为您不想要完全匹配,而是总是想要当前日期之后的下一个日期,所以我1从 sampleticker's中减去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])

使用手头的示例数据,设置并执行所需的合并:

# Create a "reverse numerical date" column, which will uniquely
# identify date, and allow them to be sorted in reverse temporal order
ST$rnd <- -(as.numeric(ST$date) + 1)
SR$rnd <- -(as.numeric(SR$rdate))

# key (and thus sort) both data.tables by ticker and "reverse numerical date"
keycols <- c("ticker", "rnd")
setkeyv(ST, keycols)
setkeyv(SR, keycols)

# The syntax of the merge is now as simple as can be
res <- SR[ST, roll=TRUE]

# Finally, put the results back in temporal order, and pretty up the column order
setkeyv(res, c("ticker", "date"))
setcolorder(res, c("ticker", "date", "rdate", "alpha", "ALPHA", "rnd"))
res
#      ticker       date      rdate alpha ALPHA    rnd
# [1,]      A 2005-01-25 2005-03-15     a     B -12809
# [2,]      A 2005-03-30 2005-04-15     b     C -12873
# [3,]     AA 2005-02-15 2005-03-01     c     D -12830
# [4,]     AA 2005-04-21 2005-05-01     d     F -12895
于 2012-02-14T17:46:39.643 回答