Вложенное ifelse заявление

Я все еще учусь переводить код SAS в R и получаю предупреждения. Мне нужно понять, где я делаю ошибки. Что я хочу сделать, это создать переменную, которая суммирует и дифференцирует 3 статуса населения: материковый, заграничный, иностранный. У меня есть база данных с 2 переменными:

  • национальность: idnat (французский, иностранец),

Если idnat Французский тогда:

  • место рождения id: idbp (материк, колония, за рубежом)

Я хочу обобщить информацию от idnat а также idbp в новую переменную под названием idnat2:

  • статус: k (материк, заграница, иностранец)

Все эти переменные используют "тип символа".

Ожидаемые результаты в столбце idnat2:

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

Вот мой код SAS, который я хочу перевести на R:

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

Вот моя попытка в R:

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

Я получаю это предупреждение:

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

Мне посоветовали использовать "вложенный ifelse"вместо этого для его легкости, но получите больше предупреждений:

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

Согласно предупреждающему сообщению, длина больше 1, поэтому учитывается только то, что находится в первых скобках. Извините, но я не понимаю, при чем здесь эта длина? Кто-нибудь знает, где я не прав?

10 ответов

Если вы используете какое-либо приложение для работы с электронными таблицами, есть основная функция if() с синтаксисом:

if(<condition>, <yes>, <no>)

Синтаксис точно такой же для ifelse() в R:

ifelse(<condition>, <yes>, <no>)

Единственная разница if() в приложении электронных таблиц является то, что R ifelse() векторизован (принимает векторы как входной и обратный вектор на выходе). Рассмотрим следующее сравнение формул в приложении для работы с электронными таблицами и в R для примера, где мы хотели бы сравнить, если a > b, и вернуть 1, если да, и 0, если нет.

В таблице:

  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)

В R:

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

ifelse() могут быть вложены разными способами:

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

Рассчитать столбец idnat2 вы можете:

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 Документация

Что такое the condition has length > 1 and only the first element will be used? Посмотрим:

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

Могу ли я все еще использовать if()? Да, можно, но синтаксис не такой крутой:)

test <- function(x) {
  if(x=="french") {
    "french"
  } else{
    "not really french"
  }
}

apply(array(df[["idnat"]]),MARGIN=1, FUN=test)

Если вы знакомы с SQL, вы также можете использовать CASE заявление в sqldf пакет

Попробуйте что-то вроде следующего:

# 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

Ваша путаница возникает из-за того, как SAS и R обрабатывают конструкции if-else. В R, if а также else не векторизованы, то есть они проверяют, является ли единственное условие истинным (т.е. if("french"=="french") работает) и не может обрабатывать несколько логик (т.е. if(c("french","foreigner")=="french") не работает) и R выдает предупреждение, которое вы получаете.

В отличие от ifelse векторизован, поэтому он может взять ваши векторы (или входные переменные) и проверить логическое условие для каждого из их элементов, как вы привыкли в SAS. Альтернативный способ обернуть это вокруг - создать цикл, используя if а также else заявления (как вы начали делать здесь), но векторизация ifelse подход будет более эффективным и, как правило, будет включать меньше кода.

Если набор данных содержит много строк, может быть более эффективно объединить таблицу поиска с помощью data.table вместо вложенных ifelse(),

Прилагается таблица поиска ниже

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

и образец набора данных

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

тогда мы можем сделать обновление, присоединившись:

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

Вы можете создать вектор idnat2 без if а также ifelse,

Функция replace может быть использован для замены всех случаев "colony" с "overseas":

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

Используя оператор SQL CASE с пакетами dplyr и sqldf:

Данные

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

Выход

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

Решением data.table является:

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

ifelse векторизован. if-else не является. Здесь DT это:

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

Это дает:

   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) 

Объяснение с примерами было ключевым, чтобы помочь мне, но проблема, с которой я столкнулся, заключалась в том, что когда я скопировал, он не работал, поэтому мне пришлось возиться с ним несколькими способами, чтобы заставить его работать правильно. (Я супер новичок в R, и у меня были некоторые проблемы с третьим ifelse из-за отсутствия знаний).

так что для тех, кто новичок в R, сталкивается с проблемами...

   ifelse(x < -2,"pretty negative", ifelse(x < 1,"close to zero", ifelse(x < 3,"in [1, 3)","large")##all one line
     )#normal tab
)

(я использовал это в функции, так что "ifelse..." был помещен поверх одного, но последний ")" был полностью слева)

Я собрал функцию для вложения операторов if-else. Не оптимизирован для скорости. Подумал, что это может быть полезно для других.

      ifelse_nested <- function(...) {
  args <- list(...)
  nargs <- length(args)
  
  default_ind <- nargs
  condition_inds <- which(seq_len(nargs) %% 2 == 1)
  condition_inds <- condition_inds[-length(condition_inds)] # remove default_ind
  value_inds <- which(seq_len(nargs) %% 2 == 0)
  
  .init <- args[[default_ind]]
  .x <- mapply(
    function(icond_ind, ivalue_ind) {
      return(list(condition=args[[icond_ind]], value=args[[ivalue_ind]]))
    }
    , icond_ind=condition_inds
    , ivalue_ind=value_inds
    , SIMPLIFY = FALSE
  ) # generate pairs of conditions & resulting-values
  
  out <- Reduce(
    function(x, y) ifelse(x$condition, x$value, y)
    , x = .x
    , init=.init
    , right=TRUE
  )
  
  return(out)
}

Например:

      x <- seq_len(10)
ifelse_nested(x%%2==0, 2,x%%3==0, x^2, 0)

Извините, что присоединился к вечеринке слишком поздно. Вот простое решение.

#building up your initial table
idnat <- c(1,1,1,2) #1 is french, 2 is foreign

idbp <- c(1,2,3,4) #1 is mainland, 2 is colony, 3 is overseas, 4 is foreign

t <- cbind(idnat, idbp)

#the last column will be a vector of row length = row length of your matrix
idnat2 <- vector()

#.. and we will populate that vector with a cursor

for(i in 1:length(idnat))

     #*check that we selected the cursor to for the length of one of the vectors*

{  

  if (t[i,1] == 2) #*this says: if idnat = foreign, then it's foreign*

    {

      idnat2[i] <- 3 #3 is foreign

    }

  else if (t[i,2] == 1) #*this says: if not foreign and idbp = mainland then it's mainland*

    {

      idnat2[i] <- 2 # 2 is mainland  

    }

  else #*this says: anything else will be classified as colony or overseas*

    {

      idnat2[i] <- 1 # 1 is colony or overseas 

    }

}


cbind(t,idnat2)
Другие вопросы по тегам