使用函数选择data.table行

时间:2013-10-28 07:43:16

标签: r data.table

我有一个data.table和一个日期列表。我希望使用一个函数来过滤和修改行,该函数检查列表中的日期。

# example data
set.seed(1)
tt <- sample(
             seq(as.POSIXct("2011-10-02"), as.POSIXct("2014-04-06"),
                 by = "day"), 10)
IR1 <- data.table(tstamp = sort(tt), dLoc = 1L:10L)

日期清单:

DLSlist <- lapply(
                  list(dls11t12 = c("2011-10-02", "2012-04-01"), 
                       dls12t13 = c("2012-10-07", "2013-04-07"), 
                       dls13t14 = c("2013-10-06", "2014-04-06"),
                       dls14t15 = c("2014-10-05", "2015-04-05"),
                       dls15t16 = c("2015-10-04", "2016-04-03"),
                       dls16t17 = c("2016-10-02", "2017-04-02")
                       ), 
                  function(X) as.POSIXct(X)
                  )

我想转换dLoc ,如果它属于DLSlist中的任何日期范围。我可以做很多事情如下:

IR1[tstamp > DLSlist[[1]][1] & tstamp < DLSlist[[1]][2], tstamp := tstamp + 60*60]
IR1[tstamp > DLSlist[[2]][1] & tstamp < DLSlist[[2]][2], tstamp := tstamp + 60*60]
IR1[tstamp > DLSlist[[3]][1] & tstamp < DLSlist[[3]][2], tstamp := tstamp + 60*60] 

然而,这似乎容易出错:一个功能适合这个任务...我的工作没有。

DLStest <- function(dd, DLSobj) {
    any(sapply(DLSobj, function(X) dd %between% X))
}

我申请了:

IR1[DLStest(tstamp, DLSlist), tstamp := tstamp + 60*60]

然而它并没有起作用:所有的行都被转换了(不仅是范围内的那些行,就像我丑陋的黑客代码中那样)。

是否有一些使用函数选择行的方法 - 或者根据多个范围检查选择行的其他方法?


更新(感谢发现此问题的Frank)

您确实可以使用返回向量或布尔值的函数进行过滤。错误完全在于我的初始功能。

DLStest_old <- function(dd, DLSobj) {
    any(sapply(DLSobj, function(X) dd %between% X))
}

sapply会返回classmatrix的对象; any会检查整个matrix中是否有任何真值。如果有任何真值,则评估为单个TRUE。如果不是,则评估为单个FALSE

使用测试数据:

(IR1[DLStest_old(tstamp, DLSlist), dLoc := dLoc + 1000L])

                 tstamp dLoc
 1: 2011-11-27 01:00:00 1001
 2: 2012-04-03 00:00:00 1002
 3: 2012-06-01 00:00:00 1003
 4: 2012-09-06 00:00:00 1004
 5: 2013-03-09 01:00:00 1005
 6: 2013-04-25 00:00:00 1006
 7: 2013-05-25 00:00:00 1007
 8: 2013-12-29 01:00:00 1008
 9: 2014-01-09 01:00:00 1009
10: 2014-02-08 01:00:00 1010

修复是使用apply单独测试矩阵的每一行。

DLStest <- function(dd, DLSobj) {
    apply(sapply(DLSobj, function(X) dd %between% X), 1, any)
}

这现在有效:

> (IR1[DLStest(tstamp, DLSlist), dLoc := dLoc + 1000L])
                 tstamp dLoc
 1: 2011-11-27 01:00:00 1001
 2: 2012-04-03 00:00:00    2
 3: 2012-06-01 00:00:00    3
 4: 2012-09-06 00:00:00    4
 5: 2013-03-09 01:00:00 1005
 6: 2013-04-25 00:00:00    6
 7: 2013-05-25 00:00:00    7
 8: 2013-12-29 01:00:00 1008
 9: 2014-01-09 01:00:00 1009
10: 2014-02-08 01:00:00 1010

2 个答案:

答案 0 :(得分:1)

您的数据看起来在DLSlist中没有重叠范围,在这种情况下这应该有用 -

library(data.table)

#creating the data
DLSlist <- data.table(read.csv(textConnection('
                  "2011-10-02", "2012-04-01" 
                  "2012-10-07", "2013-04-07" 
                  "2013-10-06", "2014-04-06"
                  "2014-10-05", "2015-04-05"
                  "2015-10-04", "2016-04-03"
                  "2016-10-02", "2017-04-02"'), header = FALSE))

IR1 <- data.table(
   tstamp = c("2011-10-01", "2012-10-06", "2014-10-07","2016-10-03")
)

#fixing data type       
IR1[,tstamp := as.Date(tstamp,"%Y-%m-%d")]
DLSlist[,V1 := as.Date(V1,"%Y-%m-%d")]
DLSlist[,V2 := as.Date(V2,"%Y-%m-%d")]
DLSlist[,tstamp := V1]

#setting a key for data.table to find the closest match
setkey(IR1,tstamp)
setkey(DLSlist,tstamp)

#roll = Inf finds the closest match for the key
IR2 <- DLSlist[IR1, roll = Inf]

#Doing the operation where condition is satisfied
IR2[tstamp > V1 & tstamp < V2 , tstamp2 := tstamp + 60*60]

输出

> IR2
       tstamp         V1         V2    tstamp2
1: 2011-10-01       <NA>       <NA>       <NA>
2: 2012-10-06 2011-10-02 2012-04-01       <NA>
3: 2014-10-07 2014-10-05 2015-04-05 2024-08-15
4: 2016-10-03 2016-10-02 2017-04-02 2026-08-12

如果确实有重叠范围,那么您可以创建类似于执行此操作的所有日期的集合,并将其合并回IR1以查看此集合中的日期。您可以获得执行此操作的所有日期的列表,如下所示 -

DLSlist2 <- unique(DLSlist[,list(DatesToFix = seq.Date(V1, V2, by = "day")), by = "V1"][,V1 := NULL])

我相信你可以将这个逻辑作为一个函数。

答案 1 :(得分:1)

您希望使用逻辑向量进行子集化。在您的初始公式中,该函数仅返回单个值(而不是向量),从而导致您的赋值影响所有行或不影响任何行。

IR <- copy(IR1)
DLStest_old <- function(dd, DLSobj) {
    any(sapply(DLSobj, function(X) dd %between% X))
}

# on the whole tstamp vector at once
  IR[,DLStest_old(tstamp, DLSlist)]
  # TRUE

一种解决方案是使用您的功能,但“按行”应用它:

# by row
  IR[,DLStest_old(tstamp, DLSlist),by=1:nrow(IR)]$V1
  # TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE

请注意,我将它放在data.table的j位置以返回结果。通常,要通过表达式进行子集,可将其置于i位置(在第一个逗号之前),但“by”不适用于i表达式,因此对于此方法,最好保存逻辑向量,然后是它的子集:

 # by row, for use in i
    change_em <- IR[,DLStest_old(tstamp, DLSlist),by=1:nrow(IR)]$V1
    IR[change_em,tstamp:=tstamp+1e15][]

我破坏了您的日期以使更改更加清晰,从而导致:

                 tstamp dLoc
 1: ))0'-06-03 15:45:52    1
 2: 2012-04-03 00:00:00    2
 3: 2012-06-01 00:00:00    3
 4: 2012-09-07 00:00:00    4
 5: ))0'-06-03 15:45:52    5
 6: 2013-04-26 00:00:00    6
 7: 2013-05-25 00:00:00    7
 8: ))0'-06-03 15:45:52    8
 9: ))0'-06-03 15:45:52    9
10: ))0'-06-03 15:45:52   10

您找到的另一种解决方案是使用*apply系列中的内容:

DLStest_apply <- function(dd, DLSobj) {
    apply(sapply(DLSobj, function(X) dd %between% X), 1, any)
}

# apply "any" on the margin of the sapply result
  IR[,DLStest_apply(tstamp, DLSlist)]
  # TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE

apply用于矩阵和数组,此sapply的结果是矩阵,

class(sapply(DLSlist, function(X) IR$tstamp %between% X))
# "matrix"

所以这应该很快。通常,sapply可以返回不同类型的结果。


P.S。我认为日期很难一目了然,如果你能提前知道你不需要它们,最好不要在你的例子中使用它们。