O operador ternário existe em R?

175

Como a pergunta é, existe uma sequência de controle em R semelhante ao operador ternário de C ? Se sim, como você o usa? Obrigado!

eykanal
fonte
1
Você quer algo mais poderoso do que ifelse, ou apenas uma forma mais compacta?
Carl Witthoft
@CarlWitthoft Forma principalmente mais compacta; simplesmente uma maneira de economizar na escrita if (x>1) y=2 else y=3. Escrever y=uma vez tem um certo apelo.
#

Respostas:

302

Como ifis function in Re retorna a avaliação mais recente, if-else é equivalente a ?:.

> a <- 1
> x <- if(a==1) 1 else 2
> x
[1] 1
> x <- if(a==2) 1 else 2
> x
[1] 2

O poder de R é a vetorização. A vetorização do operador ternário é ifelse:

> a <- c(1, 2, 1)
> x <- ifelse(a==1, 1, 2)
> x
[1] 1 2 1
> x <- ifelse(a==2, 1, 2)
> x
[1] 2 1 2

Brincadeirinha, você pode definir o estilo c ?::

`?` <- function(x, y)
    eval(
      sapply(
        strsplit(
          deparse(substitute(y)), 
          ":"
      ), 
      function(e) parse(text = e)
    )[[2 - as.logical(x)]])

aqui, você não precisa se preocupar com colchetes:

> 1 ? 2*3 : 4
[1] 6
> 0 ? 2*3 : 4
[1] 4
> TRUE ? x*2 : 0
[1] 2
> FALSE ? x*2 : 0
[1] 0

mas você precisa de colchetes para atribuição :(

> y <- 1 ? 2*3 : 4
[1] 6
> y
[1] 1
> y <- (1 ? 2*3 : 4)
> y
[1] 6

Finalmente, você pode fazer uma maneira muito semelhante com c:

`?` <- function(x, y) {
  xs <- as.list(substitute(x))
  if (xs[[1]] == as.name("<-")) x <- eval(xs[[3]])
  r <- eval(sapply(strsplit(deparse(substitute(y)), ":"), function(e) parse(text = e))[[2 - as.logical(x)]])
  if (xs[[1]] == as.name("<-")) {
    xs[[3]] <- r
        eval.parent(as.call(xs))
  } else {
    r
  }
}       

Você pode se livrar de colchetes:

> y <- 1 ? 2*3 : 4
> y
[1] 6
> y <- 0 ? 2*3 : 4
> y
[1] 4
> 1 ? 2*3 : 4
[1] 6
> 0 ? 2*3 : 4
[1] 4

Estes não são para uso diário, mas talvez sejam bons para aprender alguns internos da língua R.

kohske
fonte
23

Como todo mundo disse, use ifelse, mas você pode definir operadores para ter quase a sintaxe do operador ternário.

`%?%` <- function(x, y) list(x = x, y = y)
`%:%` <- function(xy, z) if(xy$x) xy$y else z

TRUE %?% rnorm(5) %:% month.abb
## [1]  0.05363141 -0.42434567 -0.20000319  1.31049766 -0.31761248
FALSE %?% rnorm(5) %:% month.abb
## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
# or, more generally
condition %?% value1 %:% value2

Na verdade, funciona se você definir os operadores sem os %sinais, para que você possa ter

`?` <- function(x, y) if(x) y[[1]] else y[[2]]
`:` <- function(y, z) list(y, z)

TRUE ? rnorm(5) : month.abb
## [1]  1.4584104143  0.0007500051 -0.7629123322  0.2433415442  0.0052823403
FALSE ? rnorm(5) : month.abb
## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"

(Isso funciona porque a precedência de :é menor que ?.)

Infelizmente, isso interrompe a ajuda existente e os operadores de sequência.

Richie Cotton
fonte
5

Assim como uma brincadeira, você pode redefinir o ?operador para (quase) funcionar como o operador ternário (ESTA É UMA IDÉIA RUIM):

`?` <- function(x, y) { y <-substitute(y); if(x) eval(y[[2]], parent.frame()) else eval(y[[3]], parent.frame()) }

x <- 1:3
length(x) ? (x*2) : 0
x <- numeric(0)
length(x) ? (x*2) : 0

for(i in 1:5) cat(i, (i %% 2) ? "Odd\n" : "Even\n")

... Mas você precisa colocar as expressões entre parênteses, porque a precedência padrão não é como em C.

Lembre-se de restaurar a função de ajuda antiga quando terminar de jogar:

rm(`?`)
Tommy
fonte
5

Eu daria uma olhada no ifelsecomando. Eu o chamaria ainda melhor porque também é vetorizado. Um exemplo usando o conjunto de dados de carros:

> cars$speed > 20
 [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE
[49]  TRUE  TRUE

> ifelse(cars$speed > 20, 'fast', 'slow')
 [1] "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow"
[11] "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow"
[21] "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow"
[31] "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow" "slow"
[41] "slow" "slow" "slow" "fast" "fast" "fast" "fast" "fast" "fast" "fast"
Paul Hiemstra
fonte
4
Oi Paul - você quis mostrar algo ifelsecom o seu exemplo? ;)
Josh O'Brien
4

Seu link aponta para uma ifdeclaração.

> x <- 1
> if(x < 2) print("Less than") else print("Greater than")
[1] "Less than"

Se sua variável de entrada for um vetor, ifelsepoderá ser mais adequado:

> x <- 1:3
> ifelse(x<=2, "Less than or equal", "Greater than")
[1] "Less than or equal" "Less than or equal" "Greater than"   

Para acessar a página de ajuda if, é necessário incorporar os ifbackticks:

?`if`

A página de ajuda para ifelseestá em:

`?ifelse`
Andrie
fonte
1
Como @kohske disse, isso também funcionará:print(if (x<2) "Less than" else "Greater than")
Ben Bolker
4

Não existe explicitamente, mas você pode:

set.seed(21)
y <- 1:10
z <- rnorm(10)

condition1 <- TRUE
x1 <- if(condition1) y else z

ou

condition2 <- sample(c(TRUE,FALSE),10,TRUE)
x2 <- ifelse(condition2, y, z)

A diferença entre os dois é que condition1deve ser um vector de comprimento lógico 1, ao passo que condition2deve ser um vector lógico do mesmo comprimento que x, ye z. O primeiro retornará um you z(o objeto inteiro), enquanto o segundo retornará o elemento correspondente de y( condition2==TRUE) ou z( condition2==FALSE).

Também nota que ifelseserá mais lenta do que if/ elsese condition, ye zsão todos os vectores com um comprimento.

Joshua Ulrich
fonte
graças Joshua, sua resposta ajudou muito, eu encontrei resposta de pós você mencionou stackoverflow.com/a/8792474/3019570
Mahdi Jadaliha
2

if funciona como ifelse não-vetorizado se usado da seguinte maneira:

`if`(condition, doIfTrue, doIfFalse)

A vantagem de usar isso sobre ifelse é quando a vetorização está no caminho (ou seja, eu tenho como resultado booleano escalar e coisas de lista / vetor)

ifelse(TRUE, c(1,2), c(3,4))
[1] 1
`if`(TRUE, c(1,2), c(3,4))
[1] 1 2
UpsideDownRide
fonte