La réponse fournie par @ fabian-werner est excellente, mais les objets peuvent avoir plusieurs classes, et "factor" n'est pas forcément le premier retourné par class(yes)
, donc je suggère cette petite modification pour vérifier tous les attributs de classe:
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)
}
J'ai également soumis une demande à l'équipe de développement R pour ajouter une option documentée pour que base :: ifelse () conserve les attributs en fonction de la sélection par l'utilisateur des attributs à préserver. La requête est ici: https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16609 - Elle a déjà été signalée comme "WONTFIX" au motif que cela a toujours été comme il est maintenant, mais j'ai fourni un argument complémentaire sur les raisons pour lesquelles un simple ajout pourrait sauver beaucoup de maux de tête aux utilisateurs de R. Peut-être que votre "+1" dans ce fil de bug encouragera l'équipe R Core à jeter un second regard.
EDIT: Voici une meilleure version qui permet à l'utilisateur de spécifier les attributs à conserver, soit "cond" (comportement ifelse () par défaut), "yes", le comportement selon le code ci-dessus, ou "no", pour les cas où le les attributs de la valeur "non" sont meilleurs:
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
if_else()
dans le package dplyr qui peut remplacerifelse
tout en conservant les classes correctes d'objets Date - elle est publiée ci-dessous en tant que réponse récente. J'attire l'attention sur cela ici car il résout ce problème en fournissant une fonction qui est testée unitaire et documentée dans un package CRAN, contrairement à de nombreuses autres réponses qui (à partir de ce commentaire) ont été classées avant elle.