How to avoid that anytime(<numeric>) "updates by reference"?
Asked Answered
Q

5

10

I want to convert a numeric variable to POSIXct using anytime. My issue is that anytime(<numeric>) converts the input variable as well - I want to keep it.

Simple example:

library(anytime)
t_num <- 1529734500
anytime(t_num)
# [1] "2018-06-23 08:15:00 CEST"
t_num
# [1] "2018-06-23 08:15:00 CEST"

This differs from the 'non-update by reference' behaviour of as.POSIXct in base R:

t_num <- 1529734500
as.POSIXct(t_num, origin = "1970-01-01")
# [1] "2018-06-23 08:15:00 CEST"
t_num
# 1529734500

Similarly, anydate(<numeric>) also updates by reference:

d_num <- 17707
anydate(d_num)
# [1] "2018-06-25"
d_num
# [1] "2018-06-25"

I can't find an explicit description of this behaviour in ?anytime. I could use as.POSIXct as above, but does anyone know how to handle this within anytime?

Quintic answered 24/6, 2018 at 13:49 Comment(3)
Just noticing your t_num is not really real (e.g. data.table:::isReallyReal(t_num)), i.e., you can just replace it with 1529734500L :)Hazardous
just that class(1529734500) is numeric but class(1529734500L) is not, so you can eschew the 1*/0+ kludges by starting with an integerHazardous
Enforcing the 'it is a cast so you get a copy'. And of course 1529734500 is numeric, but as you point, data.table reminds us we can express the same value using an integer.Pharmaceutics
H
2

You could hack it like this:

library(anytime)
t_num <- 1529734500
anytime(t_num+0)
# POSIXct[1:1], format: "2018-06-23 08:15:00"
t_num
# [1] 1529734500

Note that an integer input will be treated differently:

t_int <- 1529734500L
anytime(t_int)
# POSIXct[1:1], format: "2018-06-23 08:15:00"
t_int
# [1] 1529734500
Homeland answered 24/6, 2018 at 14:5 Comment(1)
Yes, good answer too. See my (later) answer and comments as to why.Pharmaceutics
P
10

anytime author here: this is standard R and Rcpp and passing-by-SEXP behaviour: you cannot protect a SEXP being passed from being changed.

The view that anytime takes is that you are asking for an input to be converted to a POSIXct as that is what anytime does: from char, from int, from factor, from anything. As a POSIXct really is a numeric value (plus a S3 class attribute) this is what you are getting.

If you do not want this (counter to the design of anytime) you can do what @Moody_Mudskipper and @PKumar showed: used a temporary expression (or variable).

(I also think the data.table example is a little unfair as data.table -- just like Rcpp -- is very explicit about taking references where it can. So of course it refers back to the original variable. There are idioms for deep copy if you need them.)

Lastly, an obvious trick is to use format if you just want different display:

R> d <- data.frame(t_num=1529734500)
R> d[1, "posixct"] <- format(anytime::anytime(d[1, "t_num"]))
R> d
       t_num             posixct
1 1529734500 2018-06-23 01:15:00
R> 

That would work the same way in data.table, of course, as the string representation is a type change. Ditto for IDate / ITime.

Edit: And the development version in the Github repo has had functionality to preserve the incoming argument since June 2017. So the next CRAN version, whenever I will push it, will have it too.

Pharmaceutics answered 24/6, 2018 at 14:21 Comment(16)
See my updated answer showing that it works differently with integer inputsHomeland
Great explanation, especially for understanding why this only affects numeric input to anytime. It would be helpful to add this to ?anytime.Hazardous
@Moody_Mudskipper: That is precisely the gotcha example we have shown a number of times with Rcpp: input of int to numeric function does a cast, hence a copy, hence no change on (now copied) input!Pharmaceutics
@MichaelChirico: Sure. Where? Under 'Details' ?Pharmaceutics
maybe Note? and/or a quick example: t = 0; anytime(t); tHazardous
@Dirk what about a numbyref logical argument to make it 100% explicit ?Homeland
@Henrik: As I tried to explain, that is baked into R and Rcpp: int and char are different types, and when POSIXct comes out at the end they are not affected as they are distinct copies -- numeric gets converted for efficiency reasons. You generally do not want spurious copies.Pharmaceutics
@Moody_Mudskipper: No, not worth it. We very rarely see numeric as input anyway.Pharmaceutics
Given this inconsistency, any reason not to throw is.numeric cases into .POSIXct? From what I can tell the speed is ~ the same.Hazardous
It is not an inconsistency. It is design (of R and its SEXP), and how a typed language works.Pharmaceutics
One small question Dirk: In the light of "It is design (of R and its SEXP) and how a typed language works", do you mind to elaborate on the difference in update-by-reference behavior between anytime(<numeric>) and its base analogue as.POSIXct(<numeric>)? To me, this may seem more like a design choice of anytime (rather than of R and its SEXP)? Which would be perfectly fine! CheersQuintic
POSIXct is not a native SEXP type; it is a numeric with an S3 class attribute. Hence "basically the same" at the C++ level,Pharmaceutics
@DirkEddelbuettel FYI, I removed the data.table stuff from my Q (not needed to illustrate my point), so you may want to do a corresponding edit of your answer. CheersQuintic
The observed behavior for non-numeric vs. numeric input is different. That's the very definition of inconsistency (whether it's by design or not is orthogonal). Anyway as long as it's clearly stated in the documentation the design is up to you.Hazardous
I just tried to look into adding this functionality and ... realized younger me already did a year ago. Just use the development version from github.Pharmaceutics
@DirkEddelbuettel Thanks! I assume you didn't see my 'self-answer' when you made your edit ;)Quintic
H
2

You could hack it like this:

library(anytime)
t_num <- 1529734500
anytime(t_num+0)
# POSIXct[1:1], format: "2018-06-23 08:15:00"
t_num
# [1] 1529734500

Note that an integer input will be treated differently:

t_int <- 1529734500L
anytime(t_int)
# POSIXct[1:1], format: "2018-06-23 08:15:00"
t_int
# [1] 1529734500
Homeland answered 24/6, 2018 at 14:5 Comment(1)
Yes, good answer too. See my (later) answer and comments as to why.Pharmaceutics
T
2

If you do this, it will work :

t_num <- 1529734500
anytime(t_num*1)

#> anytime(t_num*1)
#[1] "2018-06-23 06:15:00 UTC"
#> t_num
#[1] 1529734500
Toughie answered 24/6, 2018 at 14:5 Comment(0)
H
2

Any reason to be married to anytime?

.POSIXct(t_num, tz = 'Europe/Berlin')
# [1] "2018-06-23 08:15:00 CEST"

.POSIXct(x, tz) is a wrapper for structure(x, class = c('POSIXct', 'POSIXt'), tzone = tz) (i.e. you can ignore declaring the origin), and is essentially as.POSIXct.numeric (except the latter is flexible in allowing non-UTC origin dates), look at print(as.POSIXct.numeric).

Hazardous answered 24/6, 2018 at 14:8 Comment(0)
Q
2

When I did my homework before posting the question, I checked the open anytime issues. I have now browsed the closed ones as well, where I found exactly the same issue as mine:

anytime is overwriting inputs

There the package author writes:

I presume because as.POSIXct() leaves its input alone, we should too?

So from anytime version 0.3.1 (unreleased):

Numeric input is now preserved rather than silently cast to the return object type


Thus, one answer to my question is: "wait for 0.3.1"*.

When 0.3.1 is released, the behaviour of anytime(<numeric>) will agree with anytime(<non-numeric>) and as.POSIXct(<numeric>), and work-arounds not needed.


*Didn't have to wait too long: 0.3.1 is now released: "Numeric input is now preserved rather than silently cast to the return object type"

Quintic answered 25/6, 2018 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.