如何防止ifelse()将Date对象转换为数字对象


161

我正在使用该函数ifelse()来操纵日期向量。我期望结果是class Date,并且很惊讶地得到了numericvector。这是一个例子:

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)

这尤其令人惊讶,因为在整个矢量上执行该操作会返回一个Date对象。

dates <- as.Date(c('2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04','2011-01-05'))
dates <- dates - 1
str(dates)

我是否应该使用其他函数对Date向量进行运算?如果是这样,什么功能?如果没有,如何强制ifelse返回与输入类型相同的向量?

帮助页面的ifelse表示这是一项功能,而不是错误,但是我仍在努力寻找对我发现令人惊讶的行为的解释。


4
if_else()dplyr软件包中现在有一个函数可以替代,ifelse同时保留正确的Date对象类- 作为最新答案在下面发布。我在这里引起注意,因为它通过提供经过单元测试并记录在CRAN程序包中的功能来解决此问题,这与许多其他答案(截至本评论处)均排在其前面不同。
Sam Firke '16

Answers:


132

您可以使用data.table::fifelsedata.table >= 1.12.3)或dplyr::if_else


data.table::fifelse

与不同ifelsefifelse保留输入的类型和类。

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

dplyr 0.5.0发行说明

[ if_else]具有更严格的语义,即ifelse()truefalse参数必须为同一类型。这给出了一个不太令人惊讶的返回类型,并保留了S3向量,例如date

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" 

2
绝对有用,即使它使我没有勾选标记。当前版本的帮助页面未说明期望因子参数的内容。我的投票将是一个因子返回对象,该对象具有的级别是的和的级别true的并集false
IRTFM

3
有没有办法成为if_elsebe NA 的论点之一?我尝试了合理的NA_选择,但没有任何问题,我不相信还有NA_double_
roarkz

11
@Zak一种可能性是包裹NAas.Date
亨里克

NA_real_@roarkz。和@Henrik,您在这里的评论解决了我的问题。
BLT

63

它与以下文件的价值有关ifelse

与或的值具有相同的长度和属性(包括尺寸和“ class”)test以及数据值的向量。答案的模式将从逻辑强制转换为首先容纳from的任何值,然后容纳from的任何值。yesnoyesno

归结为它的含义,ifelse使因子失去其水平,日期失去其类,并且仅恢复其模式(“数字”)。尝试以下方法:

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"

您可以创建一个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"

后面的注释:我看到Hadley已将if_elsemagrittr / dplyr / tidyr复合体内置于数据整形软件包中。


37
更优雅的版本:safe.ifelse <- function(cond, yes, no) structure(ifelse(cond, yes, no), class = class(yes))
hadley

5
真好 您是否看到任何不是默认行为的原因?
IRTFM

请小心输入“是”,因为我有NA,但没有用。将该类作为参数传递可能比假定它是“是”条件的类更好。
丹尼斯,

1
我不确定这意味着最后的评论。仅仅因为某物具有NA值并不意味着它就不能具有类。
IRTFM '19

这个问题已经提出8年了,但仍然ifelse()不是“安全”的
M

16

DWin的解释很明确。我摆弄了一段时间,然后才意识到自己可以简单地在ifelse语句之后强迫上课:

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)

起初,这让我感到有些“骇客”。但是现在我只是认为这是为从ifelse()获得的性能回报付出的很小的代价。而且它比循环还简洁得多。


这种(很好的,如果是的话,是hackhacking)技术似乎也有助于解决以下事实:R的for语句将项目的分配VECTORNAME,而不是它们的
格雷格·明斯霍尔

6

建议的方法不适用于因子列。我想建议这种改进:

safe.ifelse <- function(cond, yes, no) {
  class.y <- class(yes)
  if (class.y == "factor") {
    levels.y = levels(yes)
  }
  X <- ifelse(cond,yes,no)
  if (class.y == "factor") {
    X = as.factor(X)
    levels(X) = levels.y
  } else {
    class(X) <- class.y
  }
  return(X)
}

顺便说一句:ifelse很烂……功能强大,责任重大,例如,我可以接受1x1矩阵和/或数字的类型转换(例如,应在应添加它们的时候),但是ifelse中的这种类型转换显然是不需要的。我现在多次碰到ifelse的同一个“ bug”,它只是继续浪费我的时间:-(

固件


这是唯一适合我的因素解决方案。
bshor

我本以为要返回的级别将是和的级别的yes并集no,并且您首先要检查它们是否都是因素。您可能需要转换为角色,然后使用“未离子化”级别进行捆绑。
IRTFM

6

之所以不起作用,是因为ifelse()函数将值转换为因子。一个不错的解决方法是在评估之前将其转换为字符。

dates <- as.Date(c('2011-01-01','2011-01-02','2011-01-03','2011-01-04','2011-01-05'))
dates_new <- dates - 1
dates <- as.Date(ifelse(dates =='2011-01-01',as.character(dates_new),as.character(dates)))

除了基数R之外,不需要任何库。


5

@ fabian-werner提供的答案很好,但是对象可以具有多个类,并且“ factor”不一定是所返回的第一个类class(yes),因此,我建议进行此小的修改以检查所有类的属性:

safe.ifelse <- function(cond, yes, no) {
      class.y <- class(yes)
      if ("factor" %in% class.y) {  # Note the small condition change here
        levels.y = levels(yes)
      }
      X <- ifelse(cond,yes,no)
      if ("factor" %in% class.y) {  # Note the small condition change here
        X = as.factor(X)
        levels(X) = levels.y
      } else {
        class(X) <- class.y
      }
      return(X)
    }

我还向R开发团队提交了一个请求,要求添加一个文档化的选项,以使base :: ifelse()根据用户选择要保留的属性来保留属性。该请求位于此处:https : //bugs.r-project.org/bugzilla/show_bug.cgi?id=16609-由于它一直是现在的样子,因此已被标记为“ WONTFIX”,但是我提供了一个后续论据,说明为什么简单的添加可能会节省很多R用户的头痛。也许您在该错误线程中的“ +1”会鼓励R Core团队重新审视。

编辑:这是一个更好的版本,允许用户指定要保留的属性,即“ cond”(默认ifelse()行为),“ yes”,上述代码的行为或“ no”(对于以下情况) “否”值的属性更好:

safe_ifelse <- function(cond, yes, no, preserved_attributes = "yes") {
    # Capture the user's choice for which attributes to preserve in return value
    preserved           <- switch(EXPR = preserved_attributes, "cond" = cond,
                                                               "yes"  = yes,
                                                               "no"   = no);
    # Preserve the desired values and check if object is a factor
    preserved_class     <- class(preserved);
    preserved_levels    <- levels(preserved);
    preserved_is_factor <- "factor" %in% preserved_class;

    # We have to use base::ifelse() for its vectorized properties
    # If we do our own if() {} else {}, then it will only work on first variable in a list
    return_obj <- ifelse(cond, yes, no);

    # If the object whose attributes we want to retain is a factor
    # Typecast the return object as.factor()
    # Set its levels()
    # Then check to see if it's also one or more classes in addition to "factor"
    # If so, set the classes, which will preserve "factor" too
    if (preserved_is_factor) {
        return_obj          <- as.factor(return_obj);
        levels(return_obj)  <- preserved_levels;
        if (length(preserved_class) > 1) {
          class(return_obj) <- preserved_class;
        }
    }
    # In all cases we want to preserve the class of the chosen object, so set it here
    else {
        class(return_obj)   <- preserved_class;
    }
    return(return_obj);

} # End safe_ifelse function

1
inherits(y, "factor")可能比“ "factor" %in% class.y
IRFTM

确实。 inherits可能是最好的。
Mekki MacAulay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.