2012-02-13 71 views
7

我正在努力高效地执行两个数据帧之间的“关闭”日期匹配。这个问题使用plyr软件包中的idata.frame来探索解决方案,但我也会对其他建议的解决方案感到非常满意。R - 加速近似日期匹配。 idata.frame?

下面是两个数据帧的一个非常简单的版本:

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 25列近30万行。

我想这样做是为了让在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. 

注意,呼吁普通的旧samplereport返回原始数据帧as.data.frame,符合市场预期。

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

马特

UPDATE Data.table是去的正确方法。见下文。

回答

8

由于马修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 
+0

而且,您还可以通过输入负数而不是“Inf”来限制卷的数量。神奇的东西! – Matt 2013-03-06 22:29:59

+0

非常好。感谢您发布此信息。 – 2013-03-06 22:35:18

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 

当然,这听起来像你真正想要做的是合并在一起的两个更广泛的数据.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 
+0

'roll = TRUE'就是为此而设计的。为了得到最近的_after_或者'X [X [Y,roll = TRUE,which = TRUE] +1]',或者反转并且执行'Y [X,roll = TRUE]'。 – 2012-02-14 10:08:35

+0

但是下一个_after_要求非常少见。在实践中,'mult =“last”'或'DT [J(date,23:00),roll = TRUE]'通常更好。 – 2012-02-14 10:34:19

+0

我实际上会很大程度上使用“next after”要求;在事件之后选择第一条记录来衡量对事件的响应并不罕见。 我会玩弄使用'roll = TRUE'来达到正确的效果。我的初步尝试(可能是不正确的)一直让我反其道而行之:'samplereport'在sampleticker之前是最接近的,但是颠倒过程给了我'samplereport'中的所有记录以及一些NAs,这绝对不是什么我想要。我将不得不更多地了解data.table,因为它看起来非常有用。 – Matt 2012-02-14 16:53:26

4

这里的马修Dowle的遵循了一个解决方案观察这是一个自然的地方申请data.tableroll=TRUE的论点。

如果你要应用它,那就要解决一个问题。 roll=TRUE经过专门设计,当密钥的最后一列(此处为日期)未找到完全匹配时,最近的前一个日期的值将被转发转发。然而,你需要相反的(即使有完全匹配,你仍然需要下一个可用日期的值)。

的第一种尝试可能是由"ticker"进行排序,并且通过以相反顺序"rdate",与所得到的重新排序SR合并。这将起作用,除了data.table不想让你按相反顺序排序:键入"rdate"强制该列按升序排列。 (data.table需要这样做才能实现它所设计的快速匹配和连接)。

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

(另外一个细节:因为你不想精确匹配,而是总想在当前操作后的下一个日期,我从sampleticker的rnd,里面有预期的效果减去1要确认它在做什么。它的工作是正确的,我稍微编辑了你的示例数据以包含一个可能的完全匹配("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 
+0

这也是一个很好的解决方案,它的运行速度比公认的解决方案快得多。 – Matt 2012-02-14 21:44:37

+0

非常好!我没有完全检查它,但我明白了。好吧,你们让我确信:'[.data.table'需要一个新的参数来推出下一个观察结果,不是。这是一个简单的开关内部,顺便说一句。选项:'revroll','rollback','rollbacktofirst','next','after'或某种组合?或者,而不是新的参数,'roll = -1 | 0 | 1'将意味着'after | equal | previous',其中'TRUE'和'FALSE'不需要改变,因为它们分别映射到前一个和相等。 – 2012-02-16 10:49:55

+0

对不起,迟到的回应。如果你能添加这个额外的参数,那将是非常棒的!对于我目前的目的,添加一个选项“roll = -1”就足够了。不过,我可以看到你可能需要'rolltofirst'或类似的东西,所以添加任何必要的功能来复制现有的'roll'函数是相反的。感谢您的考虑! – Matt 2012-03-27 15:06:52