J'apprends toujours comment traduire un code SAS en R et je reçois 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 3 le statut d'une population: continentale, d'outre-mer, étrangère. J'ai une base de données 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 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 "imbriqué ifelse
" à la place pour sa facilité mais pour obtenir plus d'avertissements:
idnat2 <- ifelse (idnat=="french", "mainland",
ifelse (idbp=="overseas"|idbp=="colony", "overseas")
)
else (idnat2 <- "foreigner")
Selon le message d’avertissement, la longueur étant supérieure à 1, seul l’espace entre les premiers crochets sera pris en compte. Désolé mais je ne comprends pas ce que cette longueur a à voir ici? Quelqu'un sait où je me trompe?
Si vous utilisez une application de feuille de calcul, il existe une fonction de base if()
avec la syntaxe:
if(<condition>, <yes>, <no>)
La syntaxe est exactement la même pour ifelse()
dans R:
ifelse(<condition>, <yes>, <no>)
La seule différence par rapport à if()
dans un tableur est que R ifelse()
est vectorisée (prend les vecteurs en entrée et renvoie le vecteur en sortie). Considérons la comparaison suivante des formules dans un tableur et dans R pour un exemple où nous aimerions comparer si a> b et renvoyer 1 si oui et 0 sinon.
Dans le tableur:
A B C
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 imbriqué de plusieurs façons:
ifelse(<condition>, <yes>, ifelse(<condition>, <yes>, <no>))
ifelse(<condition>, ifelse(<condition>, <yes>, <no>), <no>)
ifelse(<condition>,
ifelse(<condition>, <yes>, <no>),
ifelse(<condition>, <yes>, <no>)
)
ifelse(<condition>, <yes>,
ifelse(<condition>, <yes>,
ifelse(<condition>, <yes>, <no>)
)
)
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")
)
Qu'est-ce que 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
> # string "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 quand même utiliser if()
? Oui, vous le pouvez, mais la syntaxe n'est pas très cool :)
test <- function(x) {
if(x=="french") {
"french"
} else{
"not really french"
}
}
apply(array(df[["idnat"]]),MARGIN=1, FUN=test)
Si vous connaissez SQL, vous pouvez également utiliser CASE
statement dans sqldf
package .
Essayez quelque chose comme ce qui suit:
# 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 manière dont SAS et R gèrent les constructions if-else. En R, if
et else
ne sont pas vectorisées, ce qui signifie qu'ils vérifient si une seule condition est vrai (c.-à-d. 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 avertit que vous recevez.
En revanche, ifelse
est vectorisé, il peut donc prendre vos vecteurs (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 de la méthode vectorisée ifelse
. sera plus efficace et impliquera généralement moins de code.
Si le jeu de données contient plusieurs lignes, il serait peut-être plus efficace de rejoindre une table de recherche en utilisant data.table
Au lieu de imbriqué ifelse()
.
Fourni le tableau 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 échantillon 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 un mettre à 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"
avec "overseas"
:
idnat2 <- replace(idbp, idbp == "colony", "overseas")
Utilisation de l'instruction SQL CASE avec les packages dplyr et sqldf:
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, la solution est la suivante:
DT[, idnat2 := ifelse(idbp %in% "foreign", "foreign",
ifelse(idbp %in% c("colony", "overseas"), "overseas", "mainland" ))]
Le ifelse
est vectorisé. Le if-else
n'est pas. Ici, DT c'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)