嵌套的 ifelse 语句

Nested ifelse statement

提问人:balour 提问时间:8/2/2013 最后编辑:Saranjithbalour 更新时间:2/9/2023 访问量:251891

问:

我仍在学习如何将 SAS 代码转换为 R,并收到警告。我需要了解我在哪里犯了错误。我想做的是创建一个变量来总结和区分人口的 3 种状态:大陆、海外、外国人。 我有一个包含 2 个变量的数据库:

  • 身份证国籍: (法国人, 外国人),idnat

如果是法语,那么:idnat

  • ID 出生地: (大陆, 殖民地, 海外)idbp

我想将来自和汇总到一个名为 :idnatidbpidnat2

  • 身份:K(大陆、海外、外国人)

所有这些变量都使用“字符类型”。

idnat2 列中的预期结果:

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

这是我想用 R 翻译的 SAS 代码:

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,因此仅考虑第一个括号之间的内容。对不起,我不明白这个长度与这里有什么关系?有人知道我错在哪里吗?

r if 语句 嵌套 SAS

评论

7赞 Roland 8/2/2013
你不应该混用 和 .ifelseelse
1赞 balour 8/2/2013
@Roland你是对的,谢谢你的建议,我刚刚把结果放了出来。我想要的只是在 idnat2 列中,如果它说得很清楚。@KarlForner谢谢你,这正是我想用简单的例子来做的,但是我真的在为“R”而苦苦挣扎。我尝试在 SPSS 上做同样的事情,它更简单。
0赞 Karl Forner 8/2/2013
我的观点是,SO 不能替代学习语言。有很多书籍、教程......当你遇到困难时,你应该在这里发帖,并且你已经使用了所有其他资源。最好。
7赞 Tomas Greif 8/3/2013
@KarlForner 我完全同意你的看法。然而,在这个特定的情况下(vs.),我对这个问题投了赞成票,因为我在开始使用R时遇到了完全相同的问题。从 R 简介中不清楚,在 R 语言定义中没有关于什么,R 傻瓜书中有几个例子。任何其他描述和 之间差异的来源?ififelseifelseififelse

答:

13赞 Thomas 8/2/2013 #1

请尝试如下操作:

# 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 中,并且没有向量化,这意味着它们检查单个条件是否为真(即有效)并且不能处理多个逻辑(即不起作用),并且 R 会向您发出您收到的警告。ifelseif("french"=="french")if(c("french","foreigner")=="french")

相比之下,它是矢量化的,因此它可以获取您的向量(又名输入变量)并测试其每个元素的逻辑条件,就像您在 SAS 中习惯的那样。另一种方法是使用 and 语句构建一个循环(正如您在这里开始做的那样),但矢量化方法将更有效,并且通常涉及更少的代码。ifelseifelseifelse

评论

0赞 balour 8/2/2013
您好,好吧,R 中的 IF 和 ELSE 没有矢量化,这就是为什么我收到有关长度> 1 的警告,并且只记录了第一个 TRUE 参数。我要试试你对 IFELSE 的暗示,它似乎更有效率,尽管 Tomas greif 也是其中之一。
142赞 Tomas Greif 8/2/2013 #2

如果您使用的是任何电子表格应用程序,则有一个带有语法的基本功能:if()

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

R 中的语法完全相同:ifelse()

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

与电子表格应用程序的唯一区别是 R 是矢量化的(将矢量作为输入并在输出时返回向量)。考虑以下电子表格应用程序和 R 中的公式比较,例如,我们想比较 a 是否> b,如果是则返回 1,如果不是则返回 0。if()ifelse()

在电子表格中:

  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,也可以在package中使用语句CASEsqldf

评论

3赞 EcologyTom 7/27/2018
这个解释真的很好,终于帮我理解了嵌套的方法。谢谢!ifelse()
3赞 W Barker 10/28/2020
嵌套的最佳解释,如果我在任何地方见过。
8赞 Sven Hohenstein 8/3/2013 #3

您可以创建不带 和 的向量。idnat2ififelse

该函数可用于将所有出现的 :replace"colony""overseas"

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

评论

1赞 Jaap 10/6/2017
或多或少相同:df$idnat2 <- df$idbp; df$idnat2[df$idnat == 'colony'] <- 'overseas'
2赞 Sun Bee 9/19/2016 #4

使用 data.table,解决方案是:

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

是矢量化的。不是。在这里,DT 是:ifelseif-else

    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

评论

0赞 Jaap 9/19/2016
IMO 更好的方法是:DT[, idnat2 := idbp][idbp %in% c('colony','overseas'), idnat2 := 'overseas']
2赞 Jaap 9/19/2016
甚至更好:DT[, idnat2 := idbp][idbp == 'colony', idnat2 := 'overseas']
0赞 Uwe 9/29/2017
另一种方法是使用查找表进行联接:data.tableDT[lookup, on = .(idnat, idbp), idnat2 := i.idnat2][]
7赞 mpalanco 2/8/2017 #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")

德普利尔

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
9赞 Uwe 9/29/2017 #6

如果数据集包含许多行,则使用 而不是 nested 来联接查找表可能会更有效。data.tableifelse()

提供了下面的查找表

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
1赞 Azul 8/28/2018 #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) 
-1赞 Jorge Lopez 1/3/2019 #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'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)

评论

1赞 Gregor Thomas 1/10/2019
直截了当,是的。但也很冗长和非惯用语......而且说明得不是很好(为什么要使用这些整数而不是问题中提供的数据?并且重复了 Azul 的答案,它使用基本相同的方法,但基于问题的文本数据而不是整数......
0赞 Jorge Lopez 2/3/2019
Porque se me ronco hacerlo de esa manera, Gregor.看到了吗?我们可以用多少种美丽的方式进行交流......阿祖尔的...豪尔赫的...格里高尔的...
0赞 Jorge Lopez 2/3/2019
OP可以选择对他来说更合乎逻辑的东西......就像对你来说一样......对我来说就是这样。萨鲁多斯·格雷戈尔。
1赞 Tiffany T 2/13/2020 #9

示例的解释是帮助我的关键,但是我遇到的问题是当我复制它不起作用时,所以我不得不以多种方式弄乱它才能使其正常工作。(我是 R 的新手,由于缺乏知识,我对第三个 if 有一些问题)。

所以对于那些对 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...”被标记在一个上面,但最后一个“)”完全在左边)

评论

1赞 Gregor Thomas 2/13/2020
仅供参考 - 当做数字箱时会更好。您可以将其重写为 .如果只是一两个嵌套也一样好,但如果你必须更深入,不需要跟踪所有的嵌套和括号可能是一个很好的解脱。cutcut(x, breaks = c(-Inf, -2, 1, 3, Inf), labels = c("pretty negative", "close to zero", "in [1, 3)", "large"))ifelsecut
0赞 Tiffany T 2/14/2020
谢谢,我没有使用过 cut,似乎它把东西分成了 (-inf,-2],(-2,1],(1,3],(3,inf),所以只要区间显示“x <= some Z”,这就可以很好地工作。我测试了,反转中断和标签,仅标签和仅中断......但它没有给我我需要的 [-inf,-2)、[-2,1)、[1,3)、[3,inf) 的结果区间......但考虑到实际应用,切割似乎更好。
0赞 Gregor Thomas 2/14/2020
cut还有一个参数(默认为 TRUE),指示间隔在右侧关闭。设置会给你.这里不用于玩 和 边界,但您也可以用来切换两个极端是否关闭。有关详细信息,请参阅。rightright = FALSE[-inf,-2),[-2,1),[1,3),[3,inf)-InfInfinclude.lowest?cut
0赞 drakethrice 2/9/2023 #10

我整理了一个用于嵌套 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)