【问题标题】:Evaluation error of identity in lubridate::interval objectslubridate::interval 对象中身份的评估错误
【发布时间】:2019-03-22 21:03:09
【问题描述】:

假设一个像这样的df:

df <- data.frame(id = c(rep(1:5, each = 2)),
time1 = c("2008-10-12", "2008-08-10", "2006-01-09", "2008-03-13", "2008-09-12", "2007-05-30", "2003-09-29","2003-09-29", "2003-04-01", "2003-04-01"),
time2 = c("2009-03-20", "2009-06-15", "2006-02-13", "2008-04-17", "2008-10-17", "2007-07-04", "2004-01-15", "2004-01-15", "2003-07-04", "2003-07-04"))

   id      time1      time2
1   1 2008-10-12 2009-03-20
2   1 2008-08-10 2009-06-15
3   2 2006-01-09 2006-02-13
4   2 2008-03-13 2008-04-17
5   3 2008-09-12 2008-10-17
6   3 2007-05-30 2007-07-04
7   4 2003-09-29 2004-01-15
8   4 2003-09-29 2004-01-15
9   5 2003-04-01 2003-07-04
10  5 2003-04-01 2003-07-04

我尝试做的是,首先,在变量“time1”和“time2”之间创建一个lubridate 间隔。其次,我想按“id”分组,比较下一行是否与当前行相同,当前行是否与上一行相同。我可以通过以下方式实现:

library(tidyverse)

df %>%
 mutate_at(2:3, funs(as.Date(., format = "%Y-%m-%d"))) %>%
 mutate(overlap = interval(time1, time2)) %>%
 group_by(id) %>%
 mutate(cond1 = ifelse(lead(overlap) == overlap, 1, 0),
        cond2 = ifelse(lag(overlap) == overlap, 1, 0))

      id time1      time2      overlap                        cond1 cond2
   <int> <date>     <date>     <S4: Interval>                 <dbl> <dbl>
 1     1 2008-10-12 2009-03-20 2008-10-12 UTC--2009-03-20 UTC     0    NA
 2     1 2008-08-10 2009-06-15 2008-08-10 UTC--2009-06-15 UTC    NA     0
 3     2 2006-01-09 2006-02-13 2006-01-09 UTC--2006-02-13 UTC     1    NA
 4     2 2008-03-13 2008-04-17 2008-03-13 UTC--2008-04-17 UTC    NA     1
 5     3 2008-09-12 2008-10-17 2008-09-12 UTC--2008-10-17 UTC     1    NA
 6     3 2007-05-30 2007-07-04 2007-05-30 UTC--2007-07-04 UTC    NA     1
 7     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC     1    NA
 8     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC    NA     1
 9     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC     1    NA
10     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC    NA     1

问题是,如您所见,对于 id == 2 和 id == 3,这两个条件都被评估为 TRUE,即使间隔不同。对于 id == 1,它正确地评估为 FALSE,对于 id == 4 和 id == 5,它正确地评估为 TRUE。

现在,当我将区间转换为字符时,它会正确评估它:

df %>%
 mutate_at(2:3, funs(as.Date(., format = "%Y-%m-%d"))) %>%
 mutate(overlap = as.character(interval(time1, time2))) %>%
 group_by(id) %>%
 mutate(cond1 = ifelse(lead(overlap) == overlap, 1, 0),
        cond2 = ifelse(lag(overlap) == overlap, 1, 0)) 

      id time1      time2      overlap                        cond1 cond2
   <int> <date>     <date>     <chr>                          <dbl> <dbl>
 1     1 2008-10-12 2009-03-20 2008-10-12 UTC--2009-03-20 UTC     0    NA
 2     1 2008-08-10 2009-06-15 2008-08-10 UTC--2009-06-15 UTC    NA     0
 3     2 2006-01-09 2006-02-13 2006-01-09 UTC--2006-02-13 UTC     0    NA
 4     2 2008-03-13 2008-04-17 2008-03-13 UTC--2008-04-17 UTC    NA     0
 5     3 2008-09-12 2008-10-17 2008-09-12 UTC--2008-10-17 UTC     0    NA
 6     3 2007-05-30 2007-07-04 2007-05-30 UTC--2007-07-04 UTC    NA     0
 7     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC     1    NA
 8     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC    NA     1
 9     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC     1    NA
10     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC    NA     1

问题是,为什么它会将某些区间评估为相同,而实际上它们并非相同?

【问题讨论】:

  • 在我看来,那些 间隔是相同的,但您并不是要比较 间隔 本身。您是否有理由创建一个间隔而不是例如只是pastetime1time2 一起为您的目的?还有像 duplicateddistinct 这样的函数可以满足你的需要

标签: r tidyverse lubridate


【解决方案1】:

我认为这与 lubridate 实际计算的内容有关。

当我计算date1date2 之间的差异时,会发生这种情况:

df %>%
  mutate_at(2:3, funs(as.Date(., format = "%Y-%m-%d"))) %>%
  mutate(overlap = time2 - time1)

   id      time1      time2  overlap
1   1 2008-10-12 2009-03-20 159 days
2   1 2008-08-10 2009-06-15 309 days
3   2 2006-01-09 2006-02-13  35 days
4   2 2008-03-13 2008-04-17  35 days
5   3 2008-09-12 2008-10-17  35 days
6   3 2007-05-30 2007-07-04  35 days
7   4 2003-09-29 2004-01-15 108 days
8   4 2003-09-29 2004-01-15 108 days
9   5 2003-04-01 2003-07-04  94 days
10  5 2003-04-01 2003-07-04  94 days

所以我们可以知道间隔在白天长度上是相同的。

现在,overlap 实际计算的是什么?为了发现我稍微更改了您的代码以报告领先和滞后而不是 1。

df %>%
  mutate_at(2:3, funs(as.Date(., format = "%Y-%m-%d"))) %>%
  mutate(overlap = interval(time1, time2)) %>%
  group_by(id) %>%
  mutate(cond1 = ifelse(lead(overlap) == overlap, lead(overlap), 0),
         cond2 = ifelse(lag(overlap) == overlap, lag(overlap), 0))

# A tibble: 10 x 6
# Groups:   id [5]
      id time1      time2      overlap                          cond1   cond2
   <int> <date>     <date>     <S4: Interval>                   <dbl>   <dbl>
 1     1 2008-10-12 2009-03-20 2008-10-12 UTC--2009-03-20 UTC       0      NA
 2     1 2008-08-10 2009-06-15 2008-08-10 UTC--2009-06-15 UTC      NA       0
 3     2 2006-01-09 2006-02-13 2006-01-09 UTC--2006-02-13 UTC 3024000      NA
 4     2 2008-03-13 2008-04-17 2008-03-13 UTC--2008-04-17 UTC      NA 3024000
 5     3 2008-09-12 2008-10-17 2008-09-12 UTC--2008-10-17 UTC 3024000      NA
 6     3 2007-05-30 2007-07-04 2007-05-30 UTC--2007-07-04 UTC      NA 3024000
 7     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC 9331200      NA
 8     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC      NA 9331200
 9     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC 8121600      NA
10     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC      NA 8121600

在这里,我们看到leadlag 实际上计算了特定时间间隔内的差异,而不是查看实际间隔的开始日期和结束日期。这就是为什么它认为某些间隔相等而字符串不相等的原因,因为它们应该是。

更多挖掘:

我们来看看interval产生的对象。

a <- interval(df$time1, df$time2)

str(a)
#Formal class 'Interval' [package "lubridate"] with 3 slots
#..@ .Data: num [1:10] 13737600 26697600 3024000 3024000 3024000 ...
#..@ start: POSIXct[1:10], format: "2008-10-12" "2008-08-10" "2006-01-09" ...
#..@ tzone: chr "UTC"

这是一个具有三个插槽的 S4 类:.Datastarttzone

致电a 向我们显示间隔。

a
 [1] 2008-10-12 UTC--2009-03-20 UTC 2008-08-10 UTC--2009-06-15 UTC 2006-01-09 UTC--2006-02-13 UTC
 [4] 2008-03-13 UTC--2008-04-17 UTC 2008-09-12 UTC--2008-10-17 UTC 2007-05-30 UTC--2007-07-04 UTC
 [7] 2003-09-29 UTC--2004-01-15 UTC 2003-09-29 UTC--2004-01-15 UTC 2003-04-01 UTC--2003-07-04 UTC
[10] 2003-04-01 UTC--2003-07-04 UTC

但是当您对a 执行计算时,它在.Data 上执行了计算,这是从指定日期开始的秒数序列(请参阅?interval)。

a@.Data
#[1] 13737600 26697600  3024000  3024000  3024000  3024000  9331200  9331200  8121600  8121600

对于间隔的开始日期,我们需要访问start slot。

a@start
#[1] "2008-10-12 UTC" "2008-08-10 UTC" "2006-01-09 UTC" "2008-03-13 UTC" "2008-09-12 UTC"
#[6] "2007-05-30 UTC" "2003-09-29 UTC" "2003-09-29 UTC" "2003-04-01 UTC" "2003-04-01 UTC"

还有时区...

a@tzone
#[1] "UTC"

我们还可以看看元素之间的关系是什么。最后一个和倒数第二个元素具有相同的间隔。

a[9] == a[10]
#[1] TRUE

它们是相同的对象。

identical(a[9], a[10])
#[1] TRUE

但是当你检查元素是否相等时,它真正检查的是什么?元素 3 和 4 具有相同的时间差,但间隔不同。因此,当您检查他们的滞后/领先是否相等时,它返回TRUE。但由于他们有不同的间隔日期,他们不应该是。因此,当我们检查它们是否相同时,我们才会得到预期的结果。

a[3] == a[4]
#[1] TRUE

a[3]@.Data == a[4]@.Data
#[1] TRUE

identical(a[3], a[4])
#[1] FALSE

那么发生了什么? a[3] == a[4] 真正检查的是a[3]@.Data == a[4]@.Data,因此它正在检查3024000 是否等于3024000。这样做会返回TRUE。但是相同的检查所有的插槽,发现它们不一样,因为每个插槽中的start不同。

然后我考虑使用与领先/滞后相同的相同,以便我们可以将一个逻辑放入代码中,但是看看这个。

a[9]
#[1] 2003-04-01 UTC--2003-07-04 UTC

# now lead
lead(a[9])
#2003-04-01 UTC--NA

输出看起来不像预期的a[10]

#now lag
lag(a[9])
#[1] NA
#attr(,"start")
#[1] "2003-04-01 UTC"
#attr(,"tzone")
#[1] "UTC"
#attr(,"class")
#[1] "Interval"
#attr(,"class")attr(,"package")
#[1] "lubridate"

所以leadlag 对S4 类对象有不同的影响。为了更好地处理您的第一次尝试输出的内容,我这样做了:

df %>%
     mutate_at(2:3, funs(as.Date(., format = "%Y-%m-%d"))) %>%
     mutate(overlap = interval(time1, time2)) %>%
     group_by(id) %>%
     mutate(cond1 = lead(overlap),
            cond2 = lag(overlap))

我收到了很多警告信息说

#In mutate_impl(.data, dots) :
#  Vectorizing 'Interval' elements may not preserve their attributes

我对 R 对象了解得不够多,无法理解 S4 类中的数据是如何存储的,但它看起来确实与典型的 S3 对象不同。

似乎像你一样使用as.character,是要走的路。

【讨论】:

  • 我认为leadlag 的部分问题类似于我在下面提到的==... 直到有人为@ 编写leadlag 方法987654366@ 类对象,这些方法会将间隔转换为它们可以处理的数据类型,即原子向量。我对 R 的 OO 功能也很陌生,但这就是我从您的答案和我自己的搜索中得到的。
  • lead 正在返回 NA 因为它在向量中查找下一个值,并且您给它一个长度为 1 的向量,因此向量中没有下一个值要返回(相同事情发生在lead(mtcars$mpg[1]))。 leadinterval 对象所做的是创建一个新间隔,该间隔等于当前记录的开始日期加上下一条记录的.Data 值。您可以使用lead(a[8:10])[1:2] == c(interval(a[8]@start,a[8]@start + a[9]@.Data),interval(a[9]@start,a[9]@start + a[10]@.Data)) 对此进行测试。 NA 的发生仅仅是因为您已到达输入向量的末尾。
  • 两个答案都很全面,都完全回答了我的问题。因此,我给了你们赏金,并接受了@Wil 的回答,以向你们俩表示敬意。谢谢你们两个:)
【解决方案2】:

更新

如果您查看Interval 类的代码,您会看到创建对象时,它会存储开始日期,然后计算开始和结束之间的差值并将其存储为.Data

interval <- function(start, end = NULL, tzone = tz(start)) {

  if (is.null(tzone)) {
    tzone <- tz(end)
    if (is.null(tzone))
      tzone <- "UTC"
  }

  if (is.character(start) && is.null(end)) {
    return(parse_interval(start, tzone))
  }

  if (is.Date(start)) start <- date_to_posix(start)
  if (is.Date(end)) end <- date_to_posix(end)

  start <- as_POSIXct(start, tzone)
  end <- as_POSIXct(end, tzone)

  span <- as.numeric(end) - as.numeric(start)
  starts <- start + rep(0, length(span))
  if (tzone != tz(starts)) starts <- with_tz(starts, tzone)

  new("Interval", span, start = starts, tzone = tzone)
}

换句话说,返回的对象没有“结束日期”的概念。 end 参数的默认值为NULL,这意味着您甚至可以创建一个没有结束日期的间隔。

interval("2019-03-29")
[1] 2019-03-29 UTC--NA

“结束日期”只是在将Interval 对象格式化为打印时所发生的计算生成的文本。

format.Interval <- function(x, ...) {
  if (length(x@.Data) == 0) return("Interval(0)")
  paste(format(x@start, tz = x@tzone, usetz = TRUE), "--",
        format(x@start + x@.Data, tz = x@tzone, usetz = TRUE), sep = "")
}

int_end <- function(int) int@start + int@.Data

这两个代码 sn-ps 均取自 https://github.com/tidyverse/lubridate/blob/f7a7c2782ba91b821f9af04a40d93fbf9820c388/R/intervals.r

访问overlap 的底层属性允许您在不转换为字符的情况下完成比较。您必须检查 start.Data 是否相等。转换为字符要干净得多,但如果你想避免它,你可以这样做。

ifelse(lead(overlap@start) == overlap@start & lead(overlap@.Data) == overlap@.Data, 1, 0)

一共:

df %>%
  mutate_at(2:3, funs(as.Date(., format = "%Y-%m-%d"))) %>%
  mutate(overlap = interval(time1, time2),
         overlap_char = as.character(interval(time1, time2))) %>%
  group_by(id) %>%
  mutate(cond1_original = ifelse(lead(overlap_char) == overlap_char, 1, 0),
         cond1_new = ifelse(lead(overlap@start) == overlap@start & lead(overlap@.Data) == overlap@.Data, 1, 0),
         cond2_original = ifelse(lag(overlap_char) == overlap_char, 1, 0),
         cond2_new = ifelse(lag(overlap@start) == overlap@start & lag(overlap@.Data) == overlap@.Data, 1, 0)) 

id time1      time2      overlap                        overlap_char                   cond1_original cond1_new cond2_original cond2_new
<int> <date>     <date>     <S4: Interval>                 <chr>                                   <dbl>     <dbl>          <dbl>     <dbl>
1     1 2008-10-12 2009-03-20 2008-10-12 UTC--2009-03-20 UTC 2008-10-12 UTC--2009-03-20 UTC              0         0             NA        NA
2     1 2008-08-10 2009-06-15 2008-08-10 UTC--2009-06-15 UTC 2008-08-10 UTC--2009-06-15 UTC             NA        NA              0         0
3     2 2006-01-09 2006-02-13 2006-01-09 UTC--2006-02-13 UTC 2006-01-09 UTC--2006-02-13 UTC              0         0             NA        NA
4     2 2008-03-13 2008-04-17 2008-03-13 UTC--2008-04-17 UTC 2008-03-13 UTC--2008-04-17 UTC             NA        NA              0         0
5     3 2008-09-12 2008-10-17 2008-09-12 UTC--2008-10-17 UTC 2008-09-12 UTC--2008-10-17 UTC              0         0             NA        NA
6     3 2007-05-30 2007-07-04 2007-05-30 UTC--2007-07-04 UTC 2007-05-30 UTC--2007-07-04 UTC             NA        NA              0         0
7     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC 2003-09-29 UTC--2004-01-15 UTC              1         1             NA        NA
8     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC 2003-09-29 UTC--2004-01-15 UTC             NA        NA              1         1
9     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC 2003-04-01 UTC--2003-07-04 UTC              1         1             NA        NA
10    5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC 2003-04-01 UTC--2003-07-04 UTC             NA        NA              1         1 

您可以在此处阅读有关Intervals 的更多信息:https://lubridate.tidyverse.org/reference/Interval-class.html

我相信您的确切情况与 == 比较有关。正如你在上面看到的,“重叠”是一个列表, 不是向量。来自 ?==,它说:

x 和 y 中至少有一个必须是原子向量,但如果另一个是 列表 R 试图将其强制为原子向量的类型:this 如果列表由长度为 1 的元素组成,则将成功 被强制转换为正确的类型。

如果两个参数是不同类型的原子向量,一个是 强制为另一个类型,(递减)优先顺序 是字符,复杂,数字,整数,逻辑和原始。

我们可以强制“重叠”到 numericcharacter 以查看差异。

df %>%
  mutate_at(2:3, funs(as.Date(., format = "%Y-%m-%d"))) %>%
  mutate(overlap = interval(time1, time2)) %>%
  group_by(id) %>%
  mutate(cond1 = ifelse(lead(overlap) == overlap, 1, 0),
         cond2 = ifelse(lag(overlap) == overlap, 1, 0)) %>%
  mutate(overlap.n = as.numeric(overlap),
         overlap.c = as.character(overlap))

# A tibble: 10 x 8
# Groups:   id [5]
id time1      time2      overlap                        cond1 cond2 overlap.n overlap.c    
<int> <date>     <date>     <S4: Interval>                 <dbl> <dbl>     <dbl> <chr>        
  1     1 2008-10-12 2009-03-20 2008-10-12 UTC--2009-03-20 UTC     0    NA  13737600 2008-10-12 U…
  2     1 2008-08-10 2009-06-15 2008-08-10 UTC--2009-06-15 UTC    NA     0  26697600 2008-08-10 U…
  3     2 2006-01-09 2006-02-13 2006-01-09 UTC--2006-02-13 UTC     1    NA   3024000 2006-01-09 U…
  4     2 2008-03-13 2008-04-17 2008-03-13 UTC--2008-04-17 UTC    NA     1   3024000 2008-03-13 U…
  5     3 2008-09-12 2008-10-17 2008-09-12 UTC--2008-10-17 UTC     1    NA   3024000 2008-09-12 U…
  6     3 2007-05-30 2007-07-04 2007-05-30 UTC--2007-07-04 UTC    NA     1   3024000 2007-05-30 U…
  7     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC     1    NA   9331200 2003-09-29 U…
  8     4 2003-09-29 2004-01-15 2003-09-29 UTC--2004-01-15 UTC    NA     1   9331200 2003-09-29 U…
  9     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC     1    NA   8121600 2003-04-01 U…
  10     5 2003-04-01 2003-07-04 2003-04-01 UTC--2003-07-04 UTC    NA     1   8121600 2003-04-01 U…

根据上面的输出,我相信使用== 将“重叠”间隔强制为numeric 向量,导致上面提到的持续时间比较@hmhensen。当你强制 强制转换为character 而不是numeric,你会得到你想要的结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-22
    • 2018-07-22
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多