¿El operador ternario existe en R?

175

Como pregunta, ¿hay una secuencia de control en R similar al operador ternario de C ? Si es así, cómo lo usas? ¡Gracias!

eykanal
fuente
1
¿Quieres algo más poderoso que ifelse, o simplemente una forma más compacta?
Carl Witthoft el
@CarlWitthoft Forma más compacta; simplemente una forma de guardar la escritura if (x>1) y=2 else y=3. Escribir y=una vez tiene cierto atractivo.
eykanal

Respuestas:

302

Como ifes función Ry devuelve la última evaluación, if-else es equivalente a ?:.

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

El poder de R es la vectorización. La vectorización del operador ternario es 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

Es broma, puedes definir c-style ?::

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

aquí, no necesita preocuparse por los corchetes:

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

pero necesita paréntesis para la asignación :(

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

Finalmente, puedes hacer una manera muy similar con 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
  }
}       

Puede deshacerse de los corchetes:

> 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

Estos no son para uso diario, pero tal vez sean buenos para aprender algunos aspectos internos del lenguaje R.

kohske
fuente
23

Como todos los demás dijeron, use ifelse, pero puede definir operadores para que casi tenga la sintaxis del operador ternario.

`%?%` <- 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

Realmente funciona si define los operadores sin los %signos, por lo que podría tener

`?` <- 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"

(Esto funciona porque la precedencia de :es menor que ?).

Desafortunadamente, eso rompe la ayuda existente y los operadores de secuencia.

Algodón Richie
fuente
5

Al igual que una broma, puede redefinir el ?operador para (casi) trabajar como el operador ternario (ESTO ES UNA MALA IDEA):

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

... Pero debe poner las expresiones entre paréntesis porque la precedencia predeterminada no es como en C.

Solo recuerda restaurar la antigua función de ayuda cuando hayas terminado de jugar:

rm(`?`)
Tommy
fuente
5

Echaría un vistazo al ifelsecomando. Lo llamaría aún mejor porque también está vectorizado. Un ejemplo con el conjunto de datos de automóviles:

> 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
fuente
44
Hola Paul, ¿querías mostrar algo ifelsecon tu ejemplo? ;)
Josh O'Brien el
4

Su enlace apunta a una ifdeclaración.

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

Si su variable de entrada es un vector, entonces ifelsepodría ser más adecuado:

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

Para acceder a la página de ayuda if, debe incrustar los ifbackticks en:

?`if`

La página de ayuda para ifelseestá en:

`?ifelse`
Andrie
fuente
1
Como dijo @kohske, esto también funcionará:print(if (x<2) "Less than" else "Greater than")
Ben Bolker
4

No existe explícitamente, pero puede hacer:

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

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

o

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

La diferencia entre los dos es que condition1debe haber un vector lógica de longitud 1, mientras que condition2debe ser un vector lógico la misma longitud que x, y, y z. El primero devolverá yo z(el objeto completo), mientras que el segundo devolverá el elemento correspondiente de y( condition2==TRUE) o z( condition2==FALSE).

Nota también que ifelseserá más lento que if/ elsesi condition, yy zson todos los vectores con longitud 1.

Joshua Ulrich
fuente
gracias Joshua, tu respuesta ayudó mucho, encontré la respuesta de la publicación que mencionaste stackoverflow.com/a/8792474/3019570
Mahdi Jadaliha
2

if funciona como ifelse no vectorizado si se usa de la siguiente manera:

`if`(condition, doIfTrue, doIfFalse)

La ventaja de usar esto sobre ifelse es cuando la vectorización está en el camino (es decir, tengo un resultado escalar booleano y de lista / vector como resultado)

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