【问题标题】:Calculate difference in date between rows计算行之间的日期差异
【发布时间】:2018-01-04 18:32:39
【问题描述】:

我有一个 data.table,我想在其中标记给定组 ID 的先前条目 90 天内的条目。背景是这些是交易的买入信号。所以我不希望在 90 天的窗口内重复,因为我假设我持有该职位 90 天,因此已经购买了一个职位(而且我不想重新开始计时)。

所以我有:

library(data.table)
> dt <- data.table(id = c("A", "A", "A", "B", "B", "B", "C", "C", "C"), date = as.Date(c("2017-01-01", "2017-02-01", "2017-05-01", "2017-01-01", "2017-05-01", "2017-10-01", "2017-01-01", "2017-02-01", "2017-02-15")))
> dt
   id       date
1:  A 2017-01-01
2:  A 2017-02-01
3:  A 2017-05-01
4:  B 2017-01-01
5:  B 2017-05-01
6:  B 2017-10-01
7:  C 2017-01-01
8:  C 2017-02-01
9:  C 2017-02-15

我想得到:

> dt2
   id       date with.90d
1:  A 2017-01-01    FALSE
2:  A 2017-02-01     TRUE
3:  A 2017-05-01    FALSE
4:  B 2017-01-01    FALSE
5:  B 2017-05-01    FALSE
6:  B 2017-10-01    FALSE
7:  C 2017-01-01    FALSE
8:  C 2017-02-01     TRUE
9:  C 2017-02-15     TRUE

我觉得我应该能够使用 .SD 来做到这一点,但我不太明白。感谢您的帮助!

【问题讨论】:

  • 2017-02-012017-05-01 之间的区别是 89 天还是 90 天?

标签: r data.table


【解决方案1】:

你可以使用difftime:

# Data
library(data.table)
dt <- data.table(id = c("A", "A", "A", "B", "B", "B", "C", "C", "C"), date = as.Date(c("2017-01-01", "2017-02-01", "2017-05-01", "2017-01-01", "2017-05-01", "2017-10-01", "2017-01-01", "2017-02-01", "2017-02-15")))

# Difference in days    
dt[, with.90d := as.numeric(difftime(date, shift(date), units = "days")) < 90, id]
dt[is.na(with.90d), with.90d := FALSE]

#    id       date with.90d
# 1:  A 2017-01-01    FALSE
# 2:  A 2017-02-01     TRUE
# 3:  A 2017-05-01     TRUE
# 4:  B 2017-01-01    FALSE
# 5:  B 2017-05-01    FALSE
# 6:  B 2017-10-01    FALSE
# 7:  C 2017-01-01    FALSE
# 8:  C 2017-02-01     TRUE
# 9:  C 2017-02-15     TRUE

解释:

  • 使用difftime() 计算时间差。日期和按组计算的偏移日期之间存在差异 (id)。
  • 检查差异是否小于 90 天。
  • 插入FALSE 表示每组的第一次约会 (is.na())

【讨论】:

  • 谢谢,很好。问题是对于观察 3,我想要一个 FALSE,因为 2017-05-01 距离 2017-01-01 超过 90 天。所以我会在 1 月份买入,一直持有到 3 月底,然后 5 月 1 日将是一个新的买入信号。
  • @richSC for 2017-05-01 你想要2017-02-012017-01-01 的90 天窗口?
  • 从每组中的第一次观察 X 开始,我想标记距离 X 不到 90 天的任何其他观察。然后对于距离 X 超过 90 天的第二天观察,称之为观察Y,我想在 Y 的 90 天内标记任何观察结果。重复。我可以通过一些迭代循环来做到这一点,但希望得到更优雅的东西。
【解决方案2】:

OP 有requested:

从每个组中的第一次观察 X 开始,我想标记距离 X 不到 90 天的任何其他观察。然后对于距离 X 大于 90 天的第二天观察,称之为观察 Y,我想标记 Y 后 90 天内的任何观察结果。重复。

如果我从预期结果中正确理解,with.90d 列中的值FALSE 表示 90 天期间的开始。

很遗憾,下一个 90 天期限的开始时间取决于前一个 90 天期限到期后的下一次观察日期。因此,我们不能使用从每个组的第一个日期开始的固定 90 天间隔。

我曾尝试使用 non-equi joinsrolling joins 找到解决方案,但到目前为止我最终还是采用了递归方法:

dt3[, with.90d := NA]
while (dt3[, any(is.na(with.90d))]) 
  dt3[is.na(with.90d), cd := date - min(date), by = id][
    is.na(with.90d) & cd == 0, with.90d := FALSE][
      is.na(with.90d) & cd <= 90, with.90d := TRUE]
dt3
    id       date with.90d      cd
 1:  A 2017-01-01    FALSE  0 days
 2:  A 2017-02-01     TRUE 31 days
 3:  A 2017-05-01    FALSE  0 days
 4:  B 2017-01-01    FALSE  0 days
 5:  B 2017-05-01    FALSE  0 days
 6:  B 2017-10-01    FALSE  0 days
 7:  C 2017-01-01    FALSE  0 days
 8:  C 2017-02-01     TRUE 31 days
 9:  C 2017-02-15     TRUE 45 days
10:  D 2017-03-01    FALSE  0 days
11:  D 2017-04-01     TRUE 31 days
12:  D 2017-05-01     TRUE 61 days
13:  D 2017-06-01    FALSE  0 days
14:  D 2017-07-01     TRUE 30 days
15:  D 2017-08-01     TRUE 61 days
16:  E 2017-01-01    FALSE  0 days
17:  E 2017-02-01     TRUE 31 days
18:  E 2017-03-01     TRUE 59 days
19:  E 2017-04-01     TRUE 90 days
20:  E 2017-05-01    FALSE  0 days
21:  E 2017-06-01     TRUE 31 days
    id       date with.90d      cd

请注意,我在 OP 的示例数据集中添加了另外两个组 DE,以便更好地验证该方法。还要注意从2017-03-01 开始的D 组和从2017-01-01 开始的E 组的结果有何不同。

说明

只要with.90d 中有NA 值,就重复以下顺序仅针对NA(具有TRUEFALSE 值的行已完成) :

  • 计算每组中第一个日期的天差。请注意,使用了min(date),它也适用于无序数据集。或者,可以使用setorder(dt3, date)first(date)(或date[1])。
  • 日差为0 的行表示新周期的开始,并标记为FALSE
  • 日差小于或等于 90 天的行被标记为 TRUE
  • 所有其他行保持不变,即它们保持 NA 值。

为了说明,我保留了帮助器列cd。可以通过dt3[, cd := NULL]删除。

数据

# OP's sample dataset
dt <- data.table(id = c("A", "A", "A", "B", "B", "B", "C", "C", "C"), 
                 date = as.Date(c("2017-01-01", "2017-02-01", "2017-05-01", "2017-01-01", "2017-05-01", "2017-10-01", "2017-01-01", "2017-02-01", "2017-02-15")))
# append group D
dt2 <- dt[, .(id = c(id, rep("D", 6)), 
              date = c(date, seq(as.Date("2017-03-01"), length.out = 6, by = "1 month")))]
# append group E
dt3 <- dt2[, .(id = c(id, rep("E", 6)), 
               date = c(date, seq(as.Date("2017-01-01"), length.out = 6, by = "1 month")))]

【讨论】:

    【解决方案3】:

    听起来您想与之前的所有交易进行比较,以确保当前交易不在其中任何交易的 90 天内。为此,您可以尝试:

    dt[order(id, date), with.90d := sapply(1:(.N), function(i) all(difftime(date[i], date[1:(i-1)], units = "days") < 90) & i != 1L), by = id]
    
    dt
    #   id       date with.90d
    #1:  A 2017-01-01    FALSE
    #2:  A 2017-02-01     TRUE
    #3:  A 2017-05-01    FALSE
    #4:  B 2017-01-01    FALSE
    #5:  B 2017-05-01    FALSE
    #6:  B 2017-10-01    FALSE
    #7:  C 2017-01-01    FALSE
    #8:  C 2017-02-01     TRUE
    #9:  C 2017-02-15     TRUE
    

    这样做是获取当前日期与所有先前日期(在该组内)的差异,并检查所有这些差异是否小于 90 天。如果有 >= 90,它将用FALSE 标记它。请注意,我使用all() 来返回逻辑值,但您可以使用min(),这可能会更快。

    【讨论】:

      【解决方案4】:

      你也可以使用base r函数:

      transform(dt,with.90days=unlist(by(dt$date,dt$id,function(x)c(F,cumsum(as.numeric(diff(x)))<90))))
         id       date with.90days
      1:  A 2017-01-01       FALSE
      2:  A 2017-02-01        TRUE
      3:  A 2017-05-01       FALSE
      4:  B 2017-01-01       FALSE
      5:  B 2017-05-01       FALSE
      6:  B 2017-10-01       FALSE
      7:  C 2017-01-01       FALSE
      8:  C 2017-02-01        TRUE
      9:  C 2017-02-15        TRUE
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-22
        • 2010-11-07
        • 2021-11-25
        相关资源
        最近更新 更多