La réponse fournie par @fabian-werner est excellente, mais les objets peuvent avoir plusieurs classes, et "facteur" n'est pas nécessairement le premier retourné par class(yes)
Je propose donc 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 de R pour ajouter une option documentée pour que base::ifelse() préserve les attributs en fonction de la sélection par l'utilisateur des attributs à préserver. La demande est ici : https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16609 - Il a déjà été signalé comme "WONTFIX" au motif qu'il a toujours été tel qu'il est maintenant, mais j'ai fourni un argument de suivi sur la raison pour laquelle un simple ajout pourrait épargner de nombreux maux de tête aux utilisateurs de R. Peut-être que votre "+1" dans ce fil de discussion sur les bogues encouragera l'équipe R Core à y regarder de plus près.
EDIT : Voici une meilleure version qui permet à l'utilisateur de spécifier les attributs à préserver, soit "cond" (comportement ifelse() par défaut), "yes", le comportement selon le code ci-dessus, ou "no", pour les cas où les attributs de la valeur "no" 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