Вложенное ifelse выражение
Я все еще учусь переводить код SAS в R и получаю предупреждения. Мне нужно понять, где я делаю ошибки. Что я хочу сделать, это создать переменную, которая суммирует и дифференцирует 3 статуса населения: материковый, заграничный, иностранный. У меня есть база данных с 2 переменными:
- национальность id:
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, поэтому учитывается только то, что находится в первых скобках. Извините, но я не понимаю, при чем здесь эта длина? Кто-нибудь знает, где я не прав?
Ответы
Ответ 1
Если вы используете приложение электронной таблицы, существует базовая функция if()
с синтаксисом:
if(<condition>, <yes>, <no>)
Синтаксис точно такой же для ifelse()
в R:
ifelse(<condition>, <yes>, <no>)
Единственное отличие от if()
в приложении электронной таблицы состоит в том, что R ifelse()
векторизован (принимает векторы в качестве входного и возвращаемого векторов на выходе). Рассмотрим следующее сравнение формул в приложении для электронных таблиц и в R для примера, где мы хотели бы сравнить, если a > b и return 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 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
statement в sqldf
пакет.
Ответ 2
Попробуйте следующее:
# 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
векторизован, поэтому он может принимать ваши векторы (aka входные переменные) и проверять логическое условие для каждого из своих элементов, например, вы привыкли в SAS. Альтернативным способом обернуть вокруг вас было бы создание цикла с помощью операторов if
и else
(как вы начали здесь делать), но векторизованный подход ifelse
будет более эффективным и, как правило, будет содержать меньше кода.
Ответ 3
Если набор данных содержит много строк, может быть более эффективным соединение с поисковой таблицей с помощью 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
Ответ 4
Вы можете создать вектор idnat2
без if
и ifelse
.
Функция replace
может использоваться для замены всех вхождений "colony"
на "overseas"
:
idnat2 <- replace(idbp, idbp == "colony", "overseas")
Ответ 5
Использование оператора 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
Ответ 6
С 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
Ответ 7
# 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)
Ответ 8
Извините, что присоединился к вечеринке слишком поздно. Здесь простое решение.
#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 foreign*
{
idnat2[i] <- 3 #3 is foreign
}
else if (t[i,2] == 1) #*this says: if not foreign and idbp = mainland then it 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)