Looping over a Date or POSIXct object results in a numeric iterator
Asked Answered
H

7

53

Why does iterating through a Date or POSIXct object result in numeric? For example:

test = as.Date("2009-01-01")
print( class( test ) )
# [1] "Date"
for ( day in test )
{
    print( class( day ) )
}
# [1] "numeric"

The same thing happens with POSIXct:

test = as.POSIXct("2009-01-01")
print( class( test ) )
# [1] "POSIXct" "POSIXt"
for ( day in test )
{
    print( class( day ) )
}
# [1] "numeric"
Homebody answered 22/6, 2011 at 3:43 Comment(3)
as.numeric(test) is essentially the same result. ie, the number of days from epoch 1970-01-01.Dentoid
This would work: for(d in as.list(test)) print(class(test))Delwin
@G.Grothendieck has the right answer to what should have been the primary question -- which is how to make for do what virtually everyone really wants it to do with a (vector? list? whatever) of dates.Ebullition
N
39

?"for" says that seq (the part after in) is "[A]n expression evaluating to a vector (including a list and an expression) or to a pairlist or 'NULL'".

So your Date vector is being coerced to numeric because Date objects aren't strictly vectors:

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

The same is true for POSIXct vectors:

is.vector(Sys.time())
# [1] FALSE
is.vector(as.numeric(Sys.time()))
# [1] TRUE
Nocturne answered 22/6, 2011 at 4:15 Comment(6)
Pretty close, but I'm afraid that but there is no coercion in a for loop here, see this answer.....Johannajohannah
@gagolews: That's a very subtle distinction that few people will understand or appreciate. Yes, for doesn't strip attributes and call coerceVector on the iterator (and potentially create a copy). It simply ignores the attributes. Regardless, the practical effect is the same. Had I said "treated as" instead of "coerced to", you wouldn't have had a point to make.Nocturne
It would be less confusing to admit that Date objects are "vectors" in the sense that most people understand the term, but with an attribute which is lost. For instance, is.atomic(SysDate()) returns TRUE. The really surprising notion for me anyway was that for-loops will traverse lists.Selmaselman
@BondedDust, that is probably because if you do is.vector(as.list(as.Date("2009-01-01"))) it will return TRUECassette
So that's the reason why it's sometimes good to study the source code - the manual does not explain all the nuances. Indeed, as Joushua mentioned, from a plain user's point of view this is not so important. But it's good to have some insight.Johannajohannah
@gagolews: And don't misunderstand my comment, it's an interesting point, but I just don't think most people will care. ;)Nocturne
D
24

loop through days (strings):

     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])
      }
Dimitry answered 24/7, 2012 at 1:49 Comment(0)
S
19

You are not choosing the right function to apply to Date vectors when using for-loops. Better would be wrapping seq_along for pretty much every date or factor that is being looped across. Then you will do two thing: a) set it up so you are expecting an index that starts at 1, and b) protect against strange things that occur with zero length vectors. I also think it would be better to use it with factors, which the for-loops will turn into character vectors.

With reference to Joshua's answer (which is certainly correct and helpful), I think the is.vector function is a bit mislabeled or maybe just misunderstood. It could be more accurately be called hasNoAttributesOtherThanName. The property that most people consider "vectoric" is tested with is.atomic and Date and POSIXct objects will return TRUE from that test.

Selmaselman answered 22/8, 2011 at 4:11 Comment(3)
+1 especially for hasNoAttributesOtherThanName, though I think it should be named has_no_attributes_other_than_name. ;-)Nocturne
Your 'a)' point is valid, but regarding 'b)', the docs for for say "If seq has length zero the body of the loop is skipped."Mihalco
In this case it might not make a difference but in the case what the person was using 1:length(x) they get iterations that they should not want to get. Safer to use seq_along().Selmaselman
J
9

It seems that the C function that implements the for loop does not copy any of the vector's attributes. This also includes the class attribute, which should make i appear to be a Date object.

You may study the source code of the do_for(SEXP, SEXP, SEXP, SEXP) function (the one called by R's for) here.

Johannajohannah answered 24/4, 2014 at 19:49 Comment(0)
A
6

It is an old question, but I am a novice on R and faced the same problem. Since my problem would be processed in parallel, I used foreach and saw the behaviour is different when compared with the normal 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"
...

As I am not experienced with the inner things of most of R, thus I do not know why foreach ends up having a different behaviour but that worked for my purpose, and hopefully might be useful for someone else.

Apterygial answered 31/7, 2014 at 14:24 Comment(0)
D
1

Any numerical operation on date objects generally returns the number of days. In this, you are asking it to provide you with the number of days from the epoch. 14245 which is the number of days between 1970-01-01 - 2009-01-01

From ?Dates:

Dates are represented as the number of days since 1970-01-01, with negative values for earlier dates. They are always printed following the rules of the current Gregorian calendar, even though that calendar was not in use long ago (it was adopted in 1752 in Great Britain and its colonies).

It is intended that the date should be an integer, but this is not enforced in the internal representation. Fractional days will be ignored when printing. It is possible to produce fractional days via the mean method or by adding or subtracting (see Ops.Date).

Try adding print(day) to see what I mean.

test = as.Date("2009-01-01")
print( class( test ) )
for ( day in test )
{
  print(day)
  print( class( day ) )
}
Dentoid answered 22/6, 2011 at 4:2 Comment(7)
hmm...which numeric operation am I performing? I'm simply using a for loop. If I use a for loop from i:length(test) and then call test[i] then I get a Date. Its not intuitive to me why the "for-each" would result in numericHomebody
If you want to loop through the number of days, use for(1:as.numeric(test))Dentoid
Your present for statement is just returning the number of days between test and 1970-01-01. Or, day:test but what you really want is something like 1:day:testDentoid
I guess I'm just not seeing it. Correct me if I'm wrong, but I read "for ( day in test )" as "iterate through each element in test and assign the value to variable day"Homebody
@BrandonBertelsen - for(1:as.numeric(test)) doesn't iterate through the number of days in test, it starts at Jan 1, 1970, and counts forward the number of days elapsed until the day test.Mihalco
@Ken, by setting 1:as.numeric(test) you're telling it to start at 1, hence Jan 1, 1970.Dentoid
Right, but that's not often a useful thing to do. 1970 wasn't that great a year. ;-) @SFun28 was interested in iterating through the multiple entries of test, not from some external start date to test. And if test has length more than 1, 1:as.numeric(test) issues a stern warning and discards all but the first element.Mihalco
J
0

Not a solution per-se, but a useful trick when you want to check the dates in a loop:

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

just transform it as character beforehand. Most filters won't bother.

Jesicajeske answered 19/7, 2022 at 20:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.