203 votes

Comment empêcher ifelse() de transformer les objets Date en objets numériques ?

J'utilise la fonction ifelse() pour manipuler un vecteur de date. Je m'attendais à ce que le résultat soit de la classe Date et j'ai été surpris de recevoir un numeric à la place. Voici un exemple :

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)

Ceci est particulièrement surprenant car l'exécution de l'opération sur l'ensemble du vecteur renvoie un Date objet.

dates <- as.Date(c('2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04','2011-01-05'))
dates <- dates - 1
str(dates)

Devrais-je utiliser une autre fonction pour opérer sur Date vecteurs ? Si oui, quelle fonction ? Si non, comment puis-je forcer ifelse pour retourner un vecteur du même type que l'entrée ?

La page d'aide de ifelse indique qu'il s'agit d'une fonctionnalité et non d'un bogue, mais je m'efforce toujours de trouver une explication à ce comportement que j'ai trouvé surprenant.

5voto

Mekki MacAulay Points 1249

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

0voto

sashahafner Points 61

Pourquoi ne pas utiliser l'indexation ici ?

> 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"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X