Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reason for magrittr pipe (%>%) with ifelse behavior?

Tags:

r

magrittr

I am using ifelse with pipe in a scenario where I use the forwarded result in both the condition and ifelse branches. The below is a simplified version: it seems that ifelse + pipe only handles condition different from . if put inside curly braces.

library("magrittr")

FALSE %>%  ifelse(., 'true', 'false')
#> [1] "false"

FALSE %>%  ifelse(. == TRUE, 'true', 'false')
#> Error in ifelse(., . == TRUE, "true", "false"): unused argument ("false")

FALSE %>%  {ifelse(. == TRUE, 'true', 'false')}
#> [1] "false"

My original goal:

library("magrittr")

NULL %>% ifelse(is.null(.), "", as.character(.))
#> Error in ifelse(., is.null(.), "", as.character(.)): unused argument (as.character(.))

NULL %>% {ifelse(is.null(.), "", as.character(.))}
#> [1] ""

Using {} is good enough for me but I would like to understand the reasons for this behavior.

Edit: Although this question discusses a related topic and I originally see the idea of putting ifelse inside curly braces there there is no differentiation between using simply . or using . in a expression / function call which is the main point of my question.

like image 641
Ildi Czeller Avatar asked Feb 05 '23 04:02

Ildi Czeller


2 Answers

I think the error message is quite clear.

  • When you use {}, the . in the {} is substituted to the left-hand-side of %>%.

  • When you call a function without {}, the first argument is the lhs, while the other parameters you coded explicitly will move right, and in addition (see here):

LHS is needed at a position other than the first, one can use the dot,'.', as placeholder.

Therefore, FALSE %>% ifelse(. == TRUE, 'true', 'false') is actually calling:

ifelse(FALSE, FALSE == TRUE, 'true', 'false')

and FALSE %>% {ifelse(. == TRUE, 'true', 'false')} is actually calling:

ifelse(FALSE == TRUE, 'true', 'false')
like image 41
mt1022 Avatar answered Feb 06 '23 16:02

mt1022


The magrittr documentation says that when the dot is used in a nested function calls that it behaves as you saw.

Using the dot for secondary purposes
Often, some attribute or property of lhs is desired in the rhs call in addition to the value of lhs itself, e.g. the number of rows or columns. It is perfectly valid to use the dot placeholder several times in the rhs call, but by design the behavior is slightly different when using it inside nested function calls. In particular, if the placeholder is only used in a nested function call, lhs will also be placed as the first argument! The reason for this is that in most use-cases this produces the most readable code.

For example, iris %>% subset(1:nrow(.) %% 2 == 0) is equivalent to iris %>% subset(., 1:nrow(.) %% 2 == 0) but slightly more compact. It is possible to overrule this behavior by enclosing the rhs in braces. For example, 1:10 %>% {c(min(.), max(.))} is equivalent to c(min(1:10), max(1:10)).

So the solution that is recommened is actually to use the curly braces as you have already found.

The logical evaluation seems to be a seperate function call within the ifelse and therefore the reason that it's behaving as such.

like image 93
zacdav Avatar answered Feb 06 '23 17:02

zacdav