我正在使用函数 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
的帮助页面表明这是一项功能,而不是错误,但我仍在努力寻找对我发现的令人惊讶的行为的解释。
if_else()
可以替代 ifelse
,同时保留正确的 Date 对象类 - 它是 posted below 作为最近的答案。我在这里引起了人们的注意,因为它通过提供一个经过单元测试并记录在 CRAN 包中的函数来解决这个问题,这与许多其他答案(截至本评论)排在它前面不同。
您可以使用 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
[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"
它与 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))
ifelse()
仍然不“安全”。
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() 获得的性能回报付出的小代价。另外,它仍然比循环简洁得多。
for
语句将 VECTOR
中项目的 值 分配给 NAME
,而不是它们的 类。
这不起作用的原因是,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 之外的任何库。
建议的方法不适用于因子列。我想建议这个改进:
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 的同一个“错误”,它一直在偷我的时间:-(
固件
yes
和 no
级别的并集,并且您将首先检查它们是否都是因子。您可能需要转换为字符,然后使用“unionized”级别重新捆绑。
@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
可能是最好的。
为什么不在这里使用索引?
> 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"
不定期副业成功案例分享
true
和false
级别的级别的并集。if_else
的参数之一为 NA?我尝试了合乎逻辑的NA_
选项,没有任何问题,我不相信有NA_double_
NA
包装在as.Date
中。NA_real_
,@roarkz。和@Henrik,您的评论解决了我的问题。