web-dev-qa-db-fra.com

Déclaration imbriquée if else

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:

  • 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 (continentale, outre-mer, étrangère)

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?

53
balour

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

Documentation R

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 CASEstatement dans sqldfpackage .

97
Tomas Greif

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.

10
Thomas

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
8
Uwe

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")
7
Sven Hohenstein

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
3
mpalanco

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
1
Sun Bee
# 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) 
0
Azul