Instruction ifelse nestede

J’apprends encore comment traduire un code SAS en R et j’obtiens des avertissements. J’ai besoin de comprendre où je fais des erreurs. Ce que je veux faire, c’est créer une variable qui résume et différencie le statut d’une population: continentale, outre-mer, étrangère. J’ai une firebase database avec 2 variables:

  • id nationalité: idnat (français, étranger),

Si idnat est français, alors:

  • id lieu de naissance: idbp (continent, colonie, outre-mer)

Je veux résumer les informations de idnat et idbp dans une nouvelle variable appelée idnat2 :

  • statut: k (continent, outre-mer, étranger)

Toutes ces variables utilisent “type de caractère”.

Résultats attendus dans la colonne idnat2:

  idnat idbp idnat2 1 french mainland mainland 2 french colony overseas 3 french overseas overseas 4 foreign foreign foreign 

Voici mon code SAS que je veux traduire en R:

 if idnat = "french" then do; if idbp in ("overseas","colony") then idnat2 = "overseas"; else idnat2 = "mainland"; end; else idnat2 = "foreigner"; run; 

Voici ma tentative en R:

 if(idnat=="french"){ idnat2 <- "mainland" } else if(idbp=="overseas"|idbp=="colony"){ idnat2 <- "overseas" } else { idnat2 <- "foreigner" } 

Je reçois cet avertissement:

 Warning message: In if (idnat=="french") { : the condition has length > 1 and only the first element will be used 

On m’a conseillé d’utiliser un ” ifelse nested” à la place pour sa facilité, mais j’ai plus d’avertissements:

 idnat2 <- ifelse (idnat=="french", "mainland", ifelse (idbp=="overseas"|idbp=="colony", "overseas") ) else (idnat2 <- "foreigner") 

Selon le message d’avertissement, la longueur est supérieure à 1, de sorte que seuls les éléments entre les premières parenthèses seront pris en compte. Désolé mais je ne comprends pas ce que cette longueur a à voir avec ici? Quelqu’un sait-il où je me trompe?

Si vous utilisez un tableur, il existe une fonction de base if() avec la syntaxe:

 if(, , ) 

La syntaxe est exactement la même pour ifelse() dans R:

 ifelse(, , ) 

La seule différence avec if() dans un tableur est que R ifelse() est vectorisé (prend les vecteurs comme vecteur d’entrée et de retour en sortie). Considérons la comparaison suivante des formules en tableur et en R pour un exemple où nous aimerions comparer si a> b et renvoyer 1 si oui et 0 sinon.

En tableur:

  ABC 1 3 1 =if(A1 > B1, 1, 0) 2 2 2 =if(A2 > B2, 1, 0) 3 1 3 =if(A3 > B3, 1, 0) 

En R:

 > a <- 3:1; b <- 1:3 > ifelse(a > b, 1, 0) [1] 1 0 0 

ifelse() peut être nested de plusieurs manières:

 ifelse(, , ifelse(, , )) ifelse(, ifelse(, , ), ) ifelse(, ifelse(, , ), ifelse(, , ) ) ifelse(, , ifelse(, , ifelse(, , ) ) ) 

Pour calculer la colonne idnat2 vous pouvez:

 df <- read.table(header=TRUE, text=" idnat idbp idnat2 french mainland mainland french colony overseas french overseas overseas foreign foreign foreign" ) with(df, ifelse(idnat=="french", ifelse(idbp %in% c("overseas","colony"),"overseas","mainland"),"foreign") ) 

R Documentation

Quelle est the condition has length > 1 and only the first element will be used ? Voyons voir:

 > # What is first condition really testing? > with(df, idnat=="french") [1] TRUE TRUE TRUE FALSE > # This is result of vectorized function - equality of all elements in idnat and > # ssortingng "french" is tested. > # Vector of logical values is returned (has the same length as idnat) > df$idnat2 <- with(df, + if(idnat=="french"){ + idnat2 <- "xxx" + } + ) Warning message: In if (idnat == "french") { : the condition has length > 1 and only the first element will be used > # Note that the first element of comparison is TRUE and that's whay we get: > df idnat idbp idnat2 1 french mainland xxx 2 french colony xxx 3 french overseas xxx 4 foreign foreign xxx > # There is really logic in it, you have to get used to it 

Puis-je toujours utiliser if() ? Oui, vous pouvez, mais la syntaxe n'est pas si cool 🙂

 test <- function(x) { if(x=="french") { "french" } else{ "not really french" } } apply(array(df[["idnat"]]),MARGIN=1, FUN=test) 

Si vous êtes familier avec SQL, vous pouvez également utiliser l' instruction CASE dans le package sqldf .

Essayez quelque chose comme ceci:

 # some sample data idnat <- sample(c("french","foreigner"),100,TRUE) idbp <- rep(NA,100) idbp[idnat=="french"] <- sample(c("mainland","overseas","colony"),sum(idnat=="french"),TRUE) # recoding out <- ifelse(idnat=="french" & !idbp %in% c("overseas","colony"), "mainland", ifelse(idbp %in% c("overseas","colony"),"overseas", "foreigner")) cbind(idnat,idbp,out) # check result 

Votre confusion vient de la façon dont SAS et R traitent les constructions if-else. Dans R, if et else ne sont pas vectorisés, ce qui signifie qu’ils vérifient si une seule condition est vraie (par exemple, if("french"=="french") fonctionne) et ne peut pas gérer plusieurs logiques (c.-à-d. if(c("french","foreigner")=="french") ne fonctionne pas) et R vous donne l'avertissement que vous recevez.

En revanche, ifelse est vectorisé, de sorte qu'il peut prendre vos vecteurs (aka les variables d'entrée) et tester la condition logique sur chacun de leurs éléments, comme vous en avez l'habitude dans SAS. Une autre solution consiste à créer une boucle à l'aide des instructions if et else (comme vous avez commencé à le faire ici), mais l'approche ifelse vectorisée sera plus efficace et impliquera généralement moins de code.

Si le jeu de données contient de nombreuses lignes, il peut être plus efficace de joindre une table de consultation à l’aide de data.table au lieu de ifelse() nested.

Fourni la table de recherche ci-dessous

 lookup 
  idnat idbp idnat2 1: french mainland mainland 2: french colony overseas 3: french overseas overseas 4: foreign foreign foreign 

et un exemple de jeu de données

 library(data.table) n_row <- 10L set.seed(1L) DT <- data.table(idnat = "french", idbp = sample(c("mainland", "colony", "overseas", "foreign"), n_row, replace = TRUE)) DT[idbp == "foreign", idnat := "foreign"][] 
  idnat idbp 1: french colony 2: french colony 3: french overseas 4: foreign foreign 5: french mainland 6: foreign foreign 7: foreign foreign 8: french overseas 9: french overseas 10: french mainland 

alors nous pouvons faire une mise à jour en rejoignant :

 DT[lookup, on = .(idnat, idbp), idnat2 := i.idnat2][] 
  idnat idbp idnat2 1: french colony overseas 2: french colony overseas 3: french overseas overseas 4: foreign foreign foreign 5: french mainland mainland 6: foreign foreign foreign 7: foreign foreign foreign 8: french overseas overseas 9: french overseas overseas 10: french mainland mainland 

Vous pouvez créer le vecteur idnat2 sans if et ifelse .

La fonction replace peut être utilisée pour remplacer toutes les occurrences de "colony" par "overseas" :

 idnat2 <- replace(idbp, idbp == "colony", "overseas") 

Utilisation de l’instruction SQL CASE avec les packages dplyr et sqldf:

Les données

 df <-structure(list(idnat = structure(c(2L, 2L, 2L, 1L), .Label = c("foreign", "french"), class = "factor"), idbp = structure(c(3L, 1L, 4L, 2L), .Label = c("colony", "foreign", "mainland", "overseas"), class = "factor")), .Names = c("idnat", "idbp"), class = "data.frame", row.names = c(NA, -4L)) 

sqldf

 library(sqldf) sqldf("SELECT idnat, idbp, CASE WHEN idbp IN ('colony', 'overseas') THEN 'overseas' ELSE idbp END AS idnat2 FROM df") 

dplyr

 library(dplyr) df %>% mutate(idnat2 = case_when(.$idbp == 'mainland' ~ "mainland", .$idbp %in% c("colony", "overseas") ~ "overseas", TRUE ~ "foreign")) 

Sortie

  idnat idbp idnat2 1 french mainland mainland 2 french colony overseas 3 french overseas overseas 4 foreign foreign foreign 

Avec data.table, les solutions sont les suivantes:

 DT[, idnat2 := ifelse(idbp %in% "foreign", "foreign", ifelse(idbp %in% c("colony", "overseas"), "overseas", "mainland" ))] 

L’ ifelse est vectorisé. Le if-else ne l’est pas. Ici, DT est:

  idnat idbp 1 french mainland 2 french colony 3 french overseas 4 foreign foreign 

Cela donne:

  idnat idbp idnat2 1: french mainland mainland 2: french colony overseas 3: french overseas overseas 4: foreign foreign foreign 
 # Read in the data. idnat=c("french","french","french","foreign") idbp=c("mainland","colony","overseas","foreign") # Initialize the new variable. idnat2=as.character(vector()) # Logically evaluate "idnat" and "idbp" for each case, assigning the appropriate level to "idnat2". for(i in 1:length(idnat)) { if(idnat[i] == "french" & idbp[i] == "mainland") { idnat2[i] = "mainland" } else if (idnat[i] == "french" & (idbp[i] == "colony" | idbp[i] == "overseas")) { idnat2[i] = "overseas" } else { idnat2[i] = "foreign" } } # Create a data frame with the two old variables and the new variable. data.frame(idnat,idbp,idnat2)