提问人:Zach 提问时间:7/13/2011 最后编辑:HenrikZach 更新时间:10/20/2023 访问量:55648
如何防止 ifelse() 将 Date 对象变成数值对象
How to prevent ifelse() from turning Date objects into numeric objects
问:
我正在使用该函数来操作日期向量。我以为结果是类的,但很惊讶地得到了一个向量。下面是一个示例: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
答:
它与以下记录的值有关:ifelse
长度和属性(包括维度和 “”)与 或 值的数据值相同。答案的模式将被强制从逻辑中强制化,以首先容纳从中获取的任何值,然后从中获取任何值。
class
test
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 在数据整形包的 magrittr/dplyr/tidyr 复合体中构建了一个,它确实保留了后续的类。if_else
评论
safe.ifelse <- function(cond, yes, no) structure(ifelse(cond, yes, no), class = class(yes))
x <- 1:10; safe.ifelse(x < 5, NA, x)
class(NA)
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
建议的方法不适用于因子列。我想建议这个改进:
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)
}
顺便说一句:如果其他很烂......权力越大,责任越大,即 1x1 矩阵和/或数字的类型转换 [例如,当它们应该添加时] 对我来说是可以的,但 ifelse 中的这种类型转换显然是不需要的。我现在多次遇到同样的“错误”,它只是不断偷走我的时间:-(
固件
评论
yes
no
@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”,对于“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
您可以使用 () 或 .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
发行说明:
[
if_else
] 具有更严格的语义:和 参数必须是相同的类型。这给出了一个不那么令人惊讶的返回类型,并保留了 S3 向量,如日期”。ifelse()
true
false
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"
评论
if_else
NA_
NA_double_
NA
as.Date
NA_real_
这不起作用的原因是,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 之外,这不需要任何库。
为什么不在这里使用索引呢?
> 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"
评论
if_else()
ifelse