ChatGPT解决这个技术问题 Extra ChatGPT

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

我正在使用函数 ifelse() 来操作日期向量。我希望结果属于 Date 类,但惊讶地得到了一个 numeric 向量。这是一个例子:

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 的帮助页面表明这是一项功能,而不是错误,但我仍在努力寻找对我发现的令人惊讶的行为的解释。

现在 dplyr 包中有一个函数 if_else() 可以替代 ifelse,同时保留正确的 Date 对象类 - 它是 posted below 作为最近的答案。我在这里引起了人们的注意,因为它通过提供一个经过单元测试并记录在 CRAN 包中的函数来解决这个问题,这与许多其他答案(截至本评论)排在它前面不同。

C
Community

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

data.table::fifelse

与 ifelse 不同,fifelse 保留输入的类型和类别。

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 release notes

[if_else] 具有比 ifelse() 更严格的语义:true 和 false 参数必须是相同的类型。这给出了一个不那么令人惊讶的返回类型,并保留了 S3 向量,如日期"。

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" 

即使它让我失去了复选标记,它也绝对有用。当前版本的帮助页面没有说明对因子参数的期望。我的投票将支持一个因子返回对象,该对象的级别是 truefalse 级别的级别的并集。
有没有办法让 if_else 的参数之一为 NA?我尝试了合乎逻辑的 NA_ 选项,没有任何问题,我不相信有 NA_double_
@Zak 一种可能性是将 NA 包装在 as.Date 中。
NA_real_,@roarkz。和@Henrik,您的评论解决了我的问题。
I
IRTFM

它与 ifelse 的记录价值有关:

与来自 yes 或 no 值的测试和数据值具有相同长度和属性(包括维度和“类”)的向量。答案的模式将被强制从逻辑中获取,以首先容纳取自 yes 的任何值,然后容纳取自 no 的任何值。

归结为它的含义,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_else 构建到数据整形包的 magrittr/dplyr/tidyr 复合体中。


更优雅的版本:safe.ifelse <- function(cond, yes, no) structure(ifelse(cond, yes, no), class = class(yes))
好的。您是否看到有任何理由说明这不是默认行为?
请注意您输入“是”的内容,因为我有 NA 并且它不起作用。将类作为参数传递可能比假设它是“是”条件的类更好。
我不确定最后的评论是什么意思。仅仅因为某物有一个 NA 值并不意味着它不能有一个类。
自此问题出现 8 年以来,ifelse() 仍然不“安全”
J
JD Long

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() 获得的性能回报付出的小代价。另外,它仍然比循环简洁得多。


这种(很好,如果,是的,hackish)技术似乎也有助于解决以下事实:R 的 for 语句将 VECTOR 中项目的 分配给 NAME,而不是它们的
a
ananthapadmanabhan s

这不起作用的原因是,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 之外的任何库。


F
Fabian Werner

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

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 的同一个“错误”,它一直在偷我的时间:-(

固件


这是唯一对我有用的解决方案。
我原以为要返回的级别将是 yesno 级别的并集,并且您将首先检查它们是否都是因子。您可能需要转换为字符,然后使用“unionized”级别重新捆绑。
M
Mekki MacAulay

@fabian-werner 提供的答案很好,但是对象可以有多个类,并且“因子”不一定是 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”,用于“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

inherits(y, "factor") 可能比 "factor" %in% class.y“更正确”
的确。 inherits 可能是最好的。
s
sashahafner

为什么不在这里使用索引?

> dates <- as.Date(c('2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04', '2011-01-05'))
> dates[dates == '2011-01-01'] <- NA
> str(dates)
 Date[1:5], format: NA "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"