Вложенное 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")
)
Что такое 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)