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:
idnat
(français, étranger), Si idnat
est français, alors:
idbp
(continent, colonie, outre-mer) Je veux résumer les informations de idnat
et idbp
dans une nouvelle variable appelée idnat2
:
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)