Declaración ifelse anidada

Todavía estoy aprendiendo cómo traducir un código SAS en R y recibo advertencias. Necesito entender dónde estoy cometiendo errores. Lo que quiero hacer es crear una variable que resum y diferencie el estado 3 de una población: continental, extranjero, extranjero. Tengo una base de datos con 2 variables:

  • nacionalidad de id: idnat (francés, extranjero),

Si idnat es francés, entonces:

  • id birthplace: idbp (peninsular, colonia, ulttwigr)

Quiero resumir la información de idnat e idbp en una nueva variable llamada idnat2 :

  • estado: k (continente, extranjero, extranjero)

Todas estas variables usan “tipo de carácter”.

Resultados esperados en la columna idnat2:

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

Aquí está mi código SAS que quiero traducir en R:

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

Aquí está mi bash en R:

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

Recibo esta advertencia:

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

Me aconsejaron usar un ” ifelse nested” en lugar de su facilidad, pero recibo más advertencias:

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

De acuerdo con el mensaje de Advertencia, la longitud es mayor que 1, por lo que solo se tendrá en cuenta lo que esté entre los primeros corchetes. Lo siento pero no entiendo qué tiene que ver esta longitud aquí? ¿Alguien sabe dónde me equivoco?

Si está utilizando cualquier aplicación de hoja de cálculo, hay una función básica if() con syntax:

 if(, , ) 

La syntax es exactamente la misma para ifelse() en R:

 ifelse(, , ) 

La única diferencia con if() en la aplicación de hoja de cálculo es que R ifelse() se vectoriza (toma los vectores como entrada y devuelve el vector en la salida). Considere la siguiente comparación de fórmulas en la aplicación de hoja de cálculo y en R para un ejemplo en el que nos gustaría comparar si a> by devolver 1 si es sí y 0 si no.

En hoja de cálculo:

  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() se puede anidar de muchas maneras:

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

Para calcular la columna idnat2 puedes:

 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 Documentación

¿Cuál es the condition has length > 1 and only the first element will be used ? Veamos:

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

¿Puedo seguir usando if() ? Sí, puedes, pero la syntax no es tan genial 🙂

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

Si está familiarizado con SQL, también puede usar la instrucción CASE en el paquete sqldf .

Pruebe algo como lo siguiente:

 # 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 

Su confusión proviene de cómo SAS y R manejan las construcciones if-else. En R, if y else no están vectorizados, lo que significa que verifican si una sola condición es verdadera (es decir, if("french"=="french") funciona) y no pueden manejar múltiples lógicos (es decir, if(c("french","foreigner")=="french") no funciona) y R le da la advertencia que está recibiendo.

Por el contrario, ifelse está vectorizado, por lo que puede tomar sus vectores (también conocidas como variables de entrada) y probar la condición lógica en cada uno de sus elementos, como lo está acostumbrado en SAS. Una forma alternativa de entender esto sería construir un bucle usando sentencias if y else (como ha comenzado a hacer aquí) pero el enfoque vectorizado de ifelse será más eficiente e involucrará generalmente menos código.

Puede crear el vector idnat2 sin if y ifelse .

La función replace se puede usar para reemplazar todas las ocurrencias de "colony" con "overseas" :

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

Si el conjunto de datos contiene muchas filas, podría ser más eficiente unirse a una tabla de búsqueda utilizando data.table lugar de nested ifelse() .

Siempre que la tabla de búsqueda a continuación

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

y un conjunto de datos de muestra

 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 

entonces podemos hacer una actualización al unirnos :

 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 

Usando la sentencia SQL CASE con los paquetes dplyr y sqldf:

Datos

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

Salida

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

Con data.table, las soluciones son:

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

El ifelse está vectorizado. El if-else no if-else es. Aquí, DT es:

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

Esto da:

  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) 
Intereting Posts