L'opérateur ternaire existe-t-il dans R?

175

Comme la question se pose, y a-t-il une séquence de contrôle dans R similaire à l' opérateur ternaire de C ? Si oui, comment l'utilisez-vous? Merci!

Eykanal
la source
1
Voulez-vous quelque chose de plus puissant que ifelse, ou simplement une forme plus compacte?
Carl Witthoft
@CarlWitthoft Forme principalement plus compacte; simplement un moyen d'économiser l'écriture if (x>1) y=2 else y=3. Ecrire y=une fois a un certain attrait.
eykanal

Réponses:

302

De même que iffunction in Ret renvoie la dernière évaluation, if-else est équivalent à ?:.

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

La puissance de R est la vectorisation. La vectorisation de l'opérateur ternaire est 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

Je plaisante, vous pouvez définir le style c ?::

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

ici, vous n'avez pas besoin de vous soucier des parenthèses:

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

mais vous avez besoin de crochets pour l'affectation :(

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

Enfin, vous pouvez faire de manière très similaire avec 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
  }
}       

Vous pouvez vous débarrasser des crochets:

> 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

Ce ne sont pas pour un usage quotidien, mais peut-être bon pour apprendre certains éléments internes du langage R.

kohske
la source
23

Comme tout le monde l'a dit, utilisez ifelse, mais vous pouvez définir des opérateurs de sorte que vous ayez presque la syntaxe d'opérateur ternaire.

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

Cela fonctionne réellement si vous définissez les opérateurs sans les %signes, donc vous pourriez avoir

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

(Cela fonctionne car la priorité de :est inférieure à ?.)

Malheureusement, cela rompt alors les opérateurs d'aide et de séquence existants.

Coton Richie
la source
5

Juste comme une farce, vous pouvez redéfinir l' ?opérateur pour (presque) travailler comme l'opérateur ternaire (CECI EST UNE MAUVAISE IDÉE):

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

... Mais vous devez mettre les expressions entre parenthèses car la priorité par défaut n'est pas comme en C.

N'oubliez pas de restaurer l'ancienne fonction d'aide lorsque vous avez terminé de jouer:

rm(`?`)
Tommy
la source
5

Je voudrais jeter un oeil à la ifelsecommande. Je l'appellerais encore mieux car il est également vectorisé. Un exemple utilisant le jeu de données cars:

> 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
la source
4
Salut Paul - vouliez-vous montrer quelque chose ifelseavec votre exemple? ;)
Josh O'Brien
4

Votre lien pointe vers une ifdéclaration.

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

Si votre variable d'entrée est un vecteur, cela ifelsepourrait être plus approprié:

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

Pour accéder à la page d'aide de if, vous devez intégrer les ifbackticks:

?`if`

La page d'aide pour ifelseest à:

`?ifelse`
Andrie
la source
1
Comme @kohske l'a dit, cela fonctionnera aussi:print(if (x<2) "Less than" else "Greater than")
Ben Bolker
4

Cela n'existe pas explicitement, mais vous pouvez le faire:

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)

La différence entre les deux est que condition1doit être un vecteur logique de longueur 1, tandis que condition2doit être un vecteur logique de la même longueur que x, y, et z. Le premier retournera soit you z(l'objet entier), tandis que le second renverra l'élément correspondant de y( condition2==TRUE) ou z( condition2==FALSE).

Notez également que ifelsesera plus lent que if/ elsesi condition, yet zsont tous les vecteurs de longueur 1.

Joshua Ulrich
la source
merci Joshua, votre réponse a beaucoup aidé, j'ai trouvé la réponse du post que vous avez mentionné stackoverflow.com/a/8792474/3019570
Mahdi Jadaliha
2

if fonctionne comme un ifelse non visualisé s'il est utilisé de la manière suivante:

`if`(condition, doIfTrue, doIfFalse)

L'avantage d'utiliser ceci par rapport à ifelse est lorsque la vectorisation est gênante (c'est-à-dire que j'ai des choses scalaires booléennes et liste / vecteur en conséquence)

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