How to prevent ifelse() from turning Date objects into numeric objects
You may use data.table::fifelse
(data.table >= 1.12.3
) or dplyr::if_else
.
data.table::fifelse
Unlike
ifelse
,fifelse
preserves the type and class of the inputs.
library(data.table)
dates <- fifelse(dates == '2011-01-01', dates - 1, dates)
str(dates)
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"
dplyr::if_else
From dplyr 0.5.0
release notes:
[
if_else
] have stricter semantics thatifelse()
: thetrue
andfalse
arguments must be the same type. This gives a less surprising return type, and preserves S3 vectors like dates" .
library(dplyr)
dates <- if_else(dates == '2011-01-01', dates - 1, dates)
str(dates)
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"
It relates to the documented Value of ifelse
:
A vector of the same length and attributes (including dimensions and "
class
") astest
and data values from the values ofyes
orno
. The mode of the answer will be coerced from logical to accommodate first any values taken fromyes
and then any values taken fromno
.
Boiled down to its implications, ifelse
makes factors lose their levels and Dates lose their class and only their mode ("numeric") is restored. Try this instead:
dates[dates == '2011-01-01'] <- dates[dates == '2011-01-01'] - 1
str(dates)
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"
You could create a safe.ifelse
:
safe.ifelse <- function(cond, yes, no){ class.y <- class(yes)
X <- ifelse(cond, yes, no)
class(X) <- class.y; return(X)}
safe.ifelse(dates == '2011-01-01', dates - 1, dates)
# [1] "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"
A later note: I see that Hadley has built an if_else
into the the magrittr/dplyr/tidyr complex of data-shaping packages.
DWin's explanation is spot on. I fiddled and fought with this for a while before I realized I could simply force the class after the ifelse statement:
dates <- as.Date(c('2011-01-01','2011-01-02','2011-01-03','2011-01-04','2011-01-05'))
dates <- ifelse(dates=='2011-01-01',dates-1,dates)
str(dates)
class(dates)<- "Date"
str(dates)
At first this felt a little "hackish" to me. But now I just think of it as a small price to pay for the performance returns that I get from ifelse(). Plus it's still a lot more concise than a loop.