2012-02-13 76 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