循环遍历日期或POSIXct对象会产生数字迭代器。

52

迭代遍历 Date 或者 POSIXct 对象为什么会得到 numeric 类型的结果? 例如:

test = as.Date("2009-01-01")
print( class( test ) )
# [1] "Date"
for ( day in test )
{
    print( class( day ) )
}
# [1] "numeric"
同样的事情也会发生在POSIXct中:
test = as.POSIXct("2009-01-01")
print( class( test ) )
# [1] "POSIXct" "POSIXt"
for ( day in test )
{
    print( class( day ) )
}
# [1] "numeric"

as.numeric(test) 本质上是相同的结果,即从1970年01月01日起的天数。 - Brandon Bertelsen
2
这会起作用:for(d in as.list(test)) print(class(test)) - G. Grothendieck
1
@G.Grothendieck 给出了正确的答案,这本应是最主要的问题 - 即如何让 for 循环在日期(向量?列表?任何形式)上实现几乎所有人真正想要的功能。 - user3673
7个回答

39
< p >?"for"seq(在in后面的部分)是"[A]n expression evaluating to a vector (including a list and an expression) or to a pairlist or 'NULL'",意思是一个表达式评估为一个向量(包括列表和表达式)或一个pairlist或'NULL'。

因此,你的Date向量被强制转换为numeric,因为Date对象不是严格的向量:

is.vector(Sys.Date())
# [1] FALSE
is.vector(as.numeric(Sys.Date()))
# [1] TRUE

对于POSIXct向量也是如此:

is.vector(Sys.time())
# [1] FALSE
is.vector(as.numeric(Sys.time()))
# [1] TRUE

2
非常接近,但恐怕在这里没有强制执行for循环,请参见此答案。 - gagolews
3
@gagolews:这是一个非常微妙的区别,很少有人能够理解或欣赏。是的,for不会剥离属性并在迭代器上调用coerceVector(并可能创建副本)。它只是忽略了属性。无论如何,实际效果是相同的。如果我说“被视为”而不是“强制转换为”,你就没有任何观点可以提出了。 - Joshua Ulrich
1
承认Date对象在大多数人理解的意义上是“向量”会更少令人困惑,但有一个丢失的属性。例如,is.atomic(SysDate())返回TRUE。对我来说真正令人惊讶的是,for循环将遍历列表。 - IRTFM
1
@BondedDust,这可能是因为如果您执行is.vector(as.list(as.Date("2009-01-01"))),它将返回TRUE - David Arenburg
1
这就是为什么有时候研究源代码很有好处的原因 - 手册并不能解释所有细微差别。正如Joushua所提到的,从普通用户的角度来看,这并不是很重要。但是拥有一些洞察力是很好的。 - gagolews
3
不要误解我的评论,这是一个有趣的观点,但我认为大多数人不会在意。;) - Joshua Ulrich

24

循环遍历日期(字符串):

     days <- seq(from=as.Date('2011-02-01'), to=as.Date("2011-03-02"),by='days' )
     for ( i in seq_along(days) )
     {
          print(i)
           print(days[i])
      }

19

在使用for循环时,您没有选择适用于Date向量的正确函数。更好的方法是几乎每个日期或因子都用seq_along进行包装,然后您将做两件事:a)设置它以便您期望从1开始的索引,并且b)防止出现零长度向量的奇怪情况。我认为最好还是与因子一起使用,因为for循环会将其转换为字符向量。

参考Joshua的答案(这当然是正确和有帮助的),我认为is.vector函数有点错误标记或可能只是被误解了。 更准确地说,它可以被称为hasNoAttributesOtherThanName。大多数人认为“向量”特性的测试是使用is.atomic来测试的,而DatePOSIXct对象将从该测试中返回TRUE


3
+1 特别是对于 hasNoAttributesOtherThanName,虽然我认为它应该被命名为 has_no_attributes_other_than_name。;-) - Joshua Ulrich
你的'a)'点是正确的,但关于'b)',for的文档说:“如果seq的长度为零,则跳过循环体。” - Ken Williams
在这种情况下可能没有区别,但是如果使用1:length(x),则会得到不需要的迭代。更安全的做法是使用seq_along()。 - IRTFM

9
似乎实现for循环的C函数没有复制任何向量属性,包括class属性,这应该使i看起来像一个Date对象。
您可以研究do_for(SEXP, SEXP, SEXP, SEXP)函数的源代码(由R的for调用)这里

6

这是一个老问题,但我是R的新手,也遇到了同样的问题。由于我的问题需要并行处理,我使用了foreach,发现与普通的for相比,它的行为不同:

library(foreach)

start_date = as.Date("2013-08-1")
end_date = as.Date("2013-08-13")
days = seq(start_date, end_date, by = "day")

foreach(day = days, .combine='rbind') %dopar% {
  print(class(day))
}

[1] "Date"
[1] "Date"
[1] "Date"
[1] "Date"
...

由于我对大部分R的内部事物不熟悉,因此我不知道为什么foreach会有不同的行为,但这对我的目的很有效,希望对其他人也有用。


1

任何日期对象上的数值操作通常返回天数。在这里,您要求它提供从时期起的天数。14245是1970-01-01到2009-01-01之间的天数。

来自?日期:

日期表示为自1970-01-01以来的天数,早期日期为负值。它们始终按照当前公历规则打印,即使该日历很久以前没有使用过(在大不列颠及其殖民地于1752年采用)。

日期应为整数,但在内部表示中未强制执行此操作。在打印时将忽略分数天。可以通过平均方法或添加或减去(请参见Ops.Date)来生成分数天。

尝试添加print(day)以了解我的意思。

test = as.Date("2009-01-01")
print( class( test ) )
for ( day in test )
{
  print(day)
  print( class( day ) )
}

4
我正在执行哪种数值操作?我只是使用一个for循环。如果我使用从i:length(test)的for循环,然后调用test[i],那么我得到一个日期。对我来说,“for-each”为什么会产生数字并不直观。 - Suraj
1
如果你想循环遍历天数,请使用 for(1:as.numeric(test)) - Brandon Bertelsen
1
你现在的for语句只是返回测试日期和1970-01-01之间的天数。或者说,是day:test,但你真正想要的是类似于1:day:test的东西。 - Brandon Bertelsen
5
如果我理解有误请纠正我,但是我认为 "for (day in test)" 这行代码的意思是 "遍历 test 数组中的每个元素,并将其值赋给变量 day"。不过我可能还没有完全理解,希望您能进一步说明。 - Suraj
2
没错,但这通常不是一个有用的事情。1970年并不是那么伟大的一年。;-) @SFun28对于迭代test的多个条目感兴趣,而不是从某个外部开始日期到test。如果test的长度大于1,则1:as.numeric(test)会发出严厉的警告并丢弃除第一个元素以外的所有元素。 - Ken Williams
显示剩余2条评论

0

虽然不是解决方案,但在循环中检查日期时非常有用的技巧:

for( i.date in as.character(Sys.Date()) ){ cat(paste("日期:", i.date, "\n")) }

只需事先将其转换为字符即可。大多数过滤器都不会介意。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接