【问题标题】:Full outer join with two-sided roll (LOCF)带双面辊的全外连接 (LOCF)
【发布时间】:2013-12-10 13:13:35
【问题描述】:

如何有效地合并两个具有完全外连接的data.tables,同时在左右两侧通过滚动最后观察前向 (LOCF) 处理缺失值?

现实世界应用 - 有两个不一定交错的交易规则信号表,XY,随着时间的推移保持(稀疏)信号值。总体目标是定义复合信号,其中 Signal.z = Signal.x AND Signal.y

X <- data.table(Instrument=rep("SPX",3)
                , Date=as.IDate(c("2013-11-20","2013-11-22","2013-11-24"))
                , Signal=c(TRUE,FALSE,TRUE), key=c("Instrument", "Date"))

Y <- data.table(Instrument=rep("SPX",3)
                , Date=as.IDate(c("2013-11-21","2013-11-23","2013-11-25"))
                , Signal=c(FALSE,TRUE,FALSE), key=c("Instrument", "Date"))

期望的结果

   Instrument       Date Signal.x Signal.y Signal.z
1:        SPX 2013-11-20     TRUE       NA       NA
2:        SPX 2013-11-21     TRUE    FALSE    FALSE
3:        SPX 2013-11-22    FALSE    FALSE    FALSE
4:        SPX 2013-11-23    FALSE     TRUE    FALSE
5:        SPX 2013-11-24     TRUE     TRUE     TRUE
6:        SPX 2013-11-25     TRUE    FALSE    FALSE

【问题讨论】:

    标签: r data.table full-outer-join


    【解决方案1】:

    可能是这样的:

    dates = sort(c(X$Date, Y$Date))
    
    setkey(X, Date)
    setkey(Y, Date)
    
    Z = X[J(dates), roll = T][,
          Signal.y := Y[J(dates), roll = T]$Signal][,
          Signal.z := as.logical(Signal * Signal.y)]
    

    在这个想法的基础上,这里有一种处理大型示例数据的方法:

    # assuming keys are set to Instrument, Date in both data.tables
    
    Z = unique(setkey(rbind(setnames(X[Y, roll = T],
                                     c("Instrument", "Date", "Signal.x", "Signal.y")),
                            setnames(Y[X, roll = T],
                                     c("Instrument", "Date", "Signal.y", "Signal.x")),
                            use.names = TRUE),
                      Instrument, Date))[,
               Signal.z := as.logical(Signal.x * Signal.y)]
    

    【讨论】:

    • 感谢 Eddi,我认为您的意思是:Signal * Signal.y 而不是 Signal + Signal.y(请参阅第一个问题中的 AND 子句)
    • 即使在进行了上述编辑之后,在较大的基准数据上运行时,我也会收到 data.table 错误:... Check for duplicate key values in i...(将很快发布基准数据)。你是说dates = unique(sort(c(X$Date, Y$Date))) 吗?它缓解了错误消息,但仍然产生与我的版本和 Blue Magister 的版本不同的结果(比较发布在单独的答案中)
    • 是的,关于* 的好点 - 应该更仔细地阅读 OP;您会得到不同的结果,因为上述(有或没有unique)仅适用于单个“仪器” - 我必须考虑是否有可能在您发布的更大数据集上比na.locf做得更好
    • @DanielKrizian 查看编辑 - 在我的测试中,它比您的 na.locf 版本快 1.5-2 倍
    • 我相应地编辑了测试,你是对的。出色的解决方案,更简洁,更快捷!感谢你们俩的贡献,我想我可以将您的答案设置为已接受,除非@BlueMagister 发现任何错误。
    【解决方案2】:

    来自mnelLinked here is an excellent answer 解释如何在data.table 包中进行完全外连接。

    这里的应用程序很简单,增加了将最后一个观察结果向前滚动的皱纹(通过 roll = TRUE 加入)。

    创建一个data.table,其中包含XY 中的所有(唯一)键。

    ## one way to do the outer join
    keys <- unique(rbind(X[,key(X),with = FALSE], Y[,key(Y), with = FALSE]))
    ## alternate way if you have multiple data.tables to outer join
    keys <- lapply(list(X,Y), function(z) z[,key(z), with = FALSE])
    keys <- rbindlist(keys)
    
    ## this setkey is mostly cosmetic - 
    ## determines whether the final output is sorted or not
    setkeyv(keys, names(keys))
    
    ##cosmetic changing of column names to minimize confusion
    setnames(X,"Signal","Signal.X")
    setnames(Y,"Signal","Signal.Y")
    
    ## two joins, followed by the definition of the new column
    X[Y[keys, roll = TRUE], roll = TRUE][,
        Signal.Z := as.logical(Signal.X * Signal.Y)]
    ## this output is returned invisibly. either assign it or force print
    .Last.value
    #    Instrument       Date Signal.X Signal.Y Signal.Z
    # 1:        SPX 2013-11-20     TRUE       NA       NA
    # 2:        SPX 2013-11-21     TRUE    FALSE    FALSE
    # 3:        SPX 2013-11-22    FALSE    FALSE    FALSE
    # 4:        SPX 2013-11-23    FALSE     TRUE    FALSE
    # 5:        SPX 2013-11-24     TRUE     TRUE     TRUE
    # 6:        SPX 2013-11-25     TRUE    FALSE    FALSE
    

    as.logical(. * .) 复制 &amp; 的习语 NA 传播的地方受到 Eddi's answer 的启发。

    【讨论】:

      【解决方案3】:

      我将测量三种可用解决方案(Daniel.Krizian、Blue.Magister、eddi)的时间。

      为此,我创建了更大的基准数据 - 大型信号表 XY

      基准数据:XY

      nobs <- 5000 # number of observations for each instrument
      nopps <- nobs * 3 # opportunities to trade in the time window studied
      ninstr <- 200 # number of instruments
      
      set.seed(2)  # set.seed(1) generates "MPM" instrument twice :)
      universe <-  replicate( ninstr , paste( sample( LETTERS , 3 , repl = TRUE ), collapse = "" ) )
      window <- as.Date("2013-11-26") - 1:nopps + 1
      frame <- CJ(Instrument=universe, Date=rep(1:nobs))
      
      gen.sig.tbl <- function() {
        frame[, Date:= as.IDate(sample(window, size=nobs, replace=F)), by="Instrument"]
        setkey(frame,Instrument,Date)
      
        rnd.sig.sparse <- function(nobs) {
          frst <- sample(c(FALSE,TRUE), 1)
          rep(c(frst,!frst), nobs/2)
        }
      
        frame[, Signal:=rnd.sig.sparse(nobs), by="Instrument"]
        return(copy(frame))
      }
      set.seed(1)
      X <- gen.sig.tbl()
      set.seed(2)
      Y <- gen.sig.tbl()
      
      X
                   Instrument       Date Signal
            1:        AAS 1972-11-02  FALSE
            2:        AAS 1972-11-04   TRUE
            3:        AAS 1972-11-07  FALSE
            4:        AAS 1972-11-08   TRUE
            5:        AAS 1972-11-10  FALSE
           ---                             
       999996:        ZVH 2013-11-14  FALSE
       999997:        ZVH 2013-11-15   TRUE
       999998:        ZVH 2013-11-18  FALSE
       999999:        ZVH 2013-11-25   TRUE
      1000000:        ZVH 2013-11-26  FALSE
      
      Y
               Instrument       Date Signal
            1:        AAS 1972-11-13   TRUE
            2:        AAS 1972-11-17  FALSE
            3:        AAS 1972-11-20   TRUE
            4:        AAS 1972-11-21  FALSE
            5:        AAS 1972-11-23   TRUE
           ---                             
       999996:        ZVH 2013-11-16   TRUE
       999997:        ZVH 2013-11-19  FALSE
       999998:        ZVH 2013-11-23   TRUE
       999999:        ZVH 2013-11-24  FALSE
      1000000:        ZVH 2013-11-25   TRUE
      

      三种解决方案:

      Daniel.Krizian <- function () {
        Z <- merge(X, Y, all=TRUE)[, c("Signal.x","Signal.y"):=list( na.locf(Signal.x, na.rm = F)
                                                                     , na.locf(Signal.y, na.rm = F))
                                   , by=Instrument]
      
        Z[, Signal.z := Signal.x & Signal.y]
      
        # and the last line because (FALSE & NA) == FALSE, whereas NA result is desired
        Z[, Signal.z := ifelse(is.na(Signal.x) | is.na(Signal.y), NA, Signal.z)]
        return(Z)
      }
      
      
      
      Blue.Magister <- function() {
        keys <- unique(rbind(X[,key(X),with = FALSE], Y[,key(Y), with = FALSE]))
      
        ## this setkey is mostly cosmetic - 
        ## determines whether the final output is sorted or not
        setkeyv(keys, names(keys))
      
        ##cosmetic changing of column names to minimize confusion
        setnames(X,"Signal","Signal.X")
        setnames(Y,"Signal","Signal.Y")
      
        ## two joins, followed by the definition of the new column
        Z <- X[Y[keys, roll = TRUE], roll = TRUE][,
                                             Signal.Z := as.logical(Signal.X * Signal.Y)]
        Z <- unique(Z)
        return(Z)
      }
      
      eddi <- function (){
      
        # assuming keys are set to Instrument, Date in both data.tables
        Z = unique(setkey(rbind(setnames(X[Y, roll = T],
                                         c("Instrument", "Date", "Signal.x", "Signal.y")),
                                setnames(Y[X, roll = T],
                                         c("Instrument", "Date", "Signal.y", "Signal.x")),
                                use.names = TRUE),
                          Instrument, Date))[,
                                             Signal.z := as.logical(Signal.x * Signal.y)]
        return(Z)
      }
      

      基准测试:

      system.time(Z.DK <- Daniel.Krizian())
      
      user  system elapsed 
      2.70    0.07    3.01 
      
      system.time(Z.eddi <- eddi())
      
      user  system elapsed 
      1.14    0.03    1.84 
      
      system.time(Z.BM <- Blue.Magister())
      
      user  system elapsed 
      3.35    0.14    3.52
      
      setnames(X,"Signal.X", "Signal") # reset original data back after Blue.Magister() call
      setnames(Y,"Signal.Y", "Signal") # reset original data back after Blue.Magister() call
      setnames(Z.BM
               , c("Signal.X", "Signal.Y", "Signal.Z")
               , c("Signal.x", "Signal.y", "Signal.z"))
      identical(Z.DK, Z.BM)
      
      TRUE
      
      identical(Z.DK, Z.eddi)
      
      TRUE
      

      【讨论】:

      • 您为什么要在@BlueMagister 的解决方案中保留两个 替代方案来找出keys
      【解决方案4】:

      我的解决方案如下;如果您知道更有效的方法,请告诉我!

      Z <- merge(X, Y, all=TRUE)[, c("Signal.x","Signal.y"):=list( na.locf(Signal.x, na.rm = F)
                                                                 , na.locf(Signal.y, na.rm = F))
                                 , by=Instrument]
      
      Z[, Signal.z := Signal.x & Signal.y]
      
      # and the last line because (FALSE & NA) == FALSE, whereas NA result is desired
      Z[, Signal.z := ifelse(is.na(Signal.x) | is.na(Signal.y), NA, Signal.z)]
      

      【讨论】:

      • @MattDowle 等人:我可以,也许是边缘案例建议merge.data.table(x, y, roll.x=all.x, roll.y=all.y) 吗? :)
      猜你喜欢
      • 2017-05-24
      • 2014-12-04
      • 1970-01-01
      • 2012-07-27
      • 2010-09-05
      • 2010-12-06
      • 2020-03-17
      • 2013-03-13
      • 1970-01-01
      相关资源
      最近更新 更多