27

I want to make an empty vector of POSIXct so that I can put a POSIXct in it:

vec <- vector("POSIXct", 10)
vec
vec[1] <- "2014-10-27 18:11:36 PDT"
vec

That does not work. Any ideas?

Artem Klevtsov
  • 8,376
  • 4
  • 47
  • 52
user3022875
  • 7,420
  • 21
  • 80
  • 145

6 Answers6

24

Since there is no POSIX mode, you cannot initialize a POSIXct vector with vector() alone (see ?mode for a list of all mode types).

But we can use .POSIXct to create the vector from a character vector.

(x <- .POSIXct(character(10))) ## the same as .POSIXct(vector("character", 10))
# [1] NA NA NA NA NA NA NA NA NA NA
class(x)
# [1] "POSIXct" "POSIXt" 

Also note that you can also use .POSIXct(integer(10)) for a length 10 vector of origin date-times.

Rich Scriven
  • 90,041
  • 10
  • 148
  • 213
  • 1
    This is something I have never tried before. Interesting! +1 – jazzurro Nov 21 '14 at 00:55
  • 2
    This is neat, but it relies on a parsing fallthrough in `.POSIXct`. Simpler (and possibly clearer) would be `.POSIXct(rep(NA, 10))` – user295691 Oct 07 '15 at 19:33
  • 1
    Parsing fallthrough? Not sure what that means, `.POSIXct()` just uses `structure()`. There are many ways. You could also do `.POSIXct(NA[1:10])` – Rich Scriven Oct 07 '15 at 19:35
  • 2
    By parsing fallthrough I mean that `character(10)` generates a vector of 10 empty strings, not 10 NA's, so the resulting structure works because "" gets printed as NA. The `rep` solution generates a logical vector, which will be promoted to numeric. This means that operations like `x + 3600` will give strange errors. – user295691 Oct 07 '15 at 19:42
  • It's a good point you make. Mathematical operations would require us to change the mode. I will leave this answer as-is because the question was basic and the `rep(NA, 10)` method was already mentioned in the OP comments and upvoted a few times – Rich Scriven Oct 07 '15 at 19:49
  • 3
    For future viewers, I will note that I **did** include `.POSIXct(integer(10))` which can be used in mathematical operations although not explicitly stated in the answer. – Rich Scriven Oct 07 '15 at 21:32
  • ... and if you wanted to initialize as a double with a missing value that is .POSIXct(rep(NA_real_, length(x))) where length(x) is an integer indicating the length of the desired vector or NA_integer_ so that you initialize with the data-type of your choice but that the vector is filled with NA to start. – russellpierce Feb 24 '16 at 01:40
10

I usually initialize things to NA:

as.POSIXct(rep(NA, 10))

works well in this case. It's explicitly doing what happens under-the-hood in @RichardScriven's answer---see the comments there for a longer discussion.

Gregor Thomas
  • 104,719
  • 16
  • 140
  • 257
1

I would go for Gregor's solution. I first went with Rich Scriven's solution but then got an error when I tried to compute the difference for a Non-NA element later as shown in the example below

t1 <- as.POSIXct("2014-10-27 18:11:36 PDT")
t2 <- as.POSIXct("2014-11-20 18:11:36 PDT")
x <- .POSIXct(character(10))
x[1] <- t1

difftime(t2, t1)
#R Time difference of 24 days

# fails
difftime(t2, x[1])
#R Error in unclass(time1) - unclass(time2) : 
#R   non-numeric argument to binary operator

unclass(x[1]) # character
#R [1] "1414429896"
unclass(t1)
#R [1] 1414429896
#R attr(,"tzone")
#R [1] ""

x <- .POSIXct(rep(NA_real_, 10))
x[1] <- t1
difftime(t2, x[1]) # all good
#R Time difference of 24 days

This can even lead to strange bugs like this one which can take a while to discover

t1 <- as.POSIXct("2001-07-24 CEST")
t2 <- as.POSIXct("2002-08-29 CEST")
x <- .POSIXct(character(10))
x[1] <- t1

t2 < t1
#R [1] FALSE
t2 < x[1] # oh boy 
#R [1] TRUE

# the reason (I think)
unclass(t2)
#R [1] 1030572000
#R attr(,"tzone")
#R [1] ""
unclass(x[1])
#R [1] "995925600"

"995925600" > 1030572000
#R [1] TRUE
0

When creating a POSIXct vector in the following way, the underlying type becomes double:

> times <- as.POSIXct(c("2015-09-18 09:01:05.984 CEST", "2015-09-18 10:01:10.984 CEST", "2015-09-18 10:21:20.584 CEST"))
> typeof(times)
[1] "double"
> values <- c(5,6,7)

Combining the above vector with an empty vector of POSIXct initialized with character as the underlying type, results in a character-POSIXct vector:

> tm1 <- c(.POSIXct(character(0)), times)
> typeof(tm1)
[1] "character"

... which cannot be plotted directly:

> ggplot() + geom_line(aes(x=tm1, y=val), data=data.frame(tm1,val))
geom_path: Each group consist of only one observation. Do you need to adjust the group aesthetic?

I therefore prefer initializing my empty POSIXct vectors with double or integer as the underlying type:

> tm2 <- c(.POSIXct(double(0)), times)
> typeof(tm2)
[1] "double"
> ggplot() + geom_line(aes(x=tm2, y=val), data=data.frame(tm2,val))

Simple POSIXct plot

> tm3 <- c(.POSIXct(integer(0)), times)
> typeof(tm3)
[1] "double"
> ggplot() + geom_line(aes(x=tm3, y=val), data=data.frame(tm3,val))
#Same thing...

When using double, the vector is also initialized with valid dates (which might or might not be preferable):

> .POSIXct(character(10))
 [1] NA NA NA NA NA NA NA NA NA NA
> .POSIXct(double(10))
 [1] "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET"
 [7] "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET" "1970-01-01 01:00:00 CET"
Datoraki
  • 1,173
  • 11
  • 26
0

I use the following function for this. Very similar to the other solutions.

vector_datetime <- function(n = 0L) structure(rep(NA_integer_, n), class = c("POSIXct", "POSIXt"))

So you can do things like this.

> vector_datetime()
POSIXct of length 0

> vector_datetime(10)
 [1] NA NA NA NA NA NA NA NA NA NA

> class(vector_datetime(10))
[1] "POSIXct" "POSIXt" 

This can also be done with lubridate.

library(lubridate)

> as_datetime(integer(0))
POSIXct of length 0
Adam
  • 4,810
  • 2
  • 8
  • 23
0

Perhaps I missed it above, but this is a truly empty POSIXct:

as.POSIXct(integer())

For example, if you want an empty data frame:

empty <- data.frame(date_time = as.POSIXct(integer()),
                    date = as.Date(x = integer(), origin = "1970-01-01"))
empty
[1] date_time date     
<0 rows> (or 0-length row.names)

str(empty)
'data.frame':   0 obs. of  2 variables:
 $ date_time: 'POSIXct' num(0) 
 - attr(*, "tzone")= chr ""
 $ date     : 'Date' num(0) 
user3915170
  • 313
  • 1
  • 7