Triple Exclamation Marks on R

Triple exclamation marks on R

!!! is usually used to evaluate a list of expressions.

library(dplyr)
library(rlang)

VC_preds <- c('mpg', 'cyl')
mtcars %>% select(!!!VC_preds) %>% head

# mpg Cyl
#Mazda RX4 21.0 6
#Mazda RX4 Wag 21.0 6
#Datsun 710 22.8 4
#Hornet 4 Drive 21.4 6
#Hornet Sportabout 18.7 8
#Valiant 18.1 6

If VC_preds is a vector as in your example, !! should work as well.

mtcars %>% select(!!VC_preds) %>% head

Help page of ?"!!!" gives a better example to understand the difference.

vars <- syms(c("height", "mass"))
vars
#[[1]]
#height

#[[2]]
#mass

starwars %>% select(!!!vars)
# A tibble: 87 x 2
# height mass
# <int> <dbl>
# 1 172 77
# 2 167 75
# 3 96 32
# 4 202 136
# 5 150 49
# 6 178 120
# 7 165 75
# 8 97 32
# 9 183 84
#10 182 77
# … with 77 more rows

The use of the triple exclamation mark

There is no difference between !a and !!!a, since !!!a is just !!(!a) and because !a is a boolean, !!(!a) is just its double negation, therefore the same.

R - exclamation mark before variable, but no subsequent =, == or similar

edit: I was confused about the context.

! is the logical-NOT operator in R.

As pointed out in the comments, R often allows users to pass arguments of different types. In this case ?HoltWinters says

gamma: gamma parameter used for the seasonal component. If set to
‘FALSE’, an non-seasonal model is fitted.

So gamma can be either a numeric value or a logical (FALSE) value.

Since this !gamma follows is.logical(gamma) && ..., it will only be evaluated if gamma is a logical (TRUE/FALSE) value. In this case, !gamma is equivalent to gamma==FALSE, but most programmers would shorten this to !gamma (so that FALSE becomes TRUE and TRUE becomes FALSE).

We wouldn't want to test gamma=FALSE without the is.logical() test first, because someone might have specified gamma=0, in which case R would evaluate 0==FALSE, which according to its coercion rules is TRUE.

This test could also have been written if (identical(gamma,FALSE)) - which would correctly evaluate both NULL and 0 as different from FALSE.


In contrast, if gamma were to be numeric, !gamma would be shorthand for gamma != 0.

According to R's rules for coercion from floating-point to logical, 0 gets converted to FALSE and any non-zero, non-NA value gets converted to TRUE (see this question for more detail).
Thus !gamma is equivalent to gamma!=0. Some old-school programmers use this for brevity; I don't think the brevity-clarity tradeoff is worth it, but that's just my opinion.

Is there a more robust rename alternative than select with triple exclamation mark?

An extension to your solution could be

library(tidyverse)

df[,setdiff(mapping, colnames(df))] <- NA

df %>% rename_all(~names(mapping))

# x y z
#1 1 m NA
#2 2 m NA
#3 3 m NA

Or another approach

map_dfc(setdiff(mapping, colnames(df)), ~df %>% mutate(!!.x := NA)) %>%
arrange(mapping) %>%
rename_all(~names(mapping))

Why use double exclamation marks in an if statement?

Short answer: No, there is no reason.

In your code, it's already a boolean type, there is no need to convert it, and convert back again, you will always get the same result. Actually, if you have any boolean (true or false), when you use !! with any of them, it will be converted back to it's initial value:

console.log(!!true); // Will be always "true"
console.log(typeof !!true); // It stills a "boolean" type
console.log(!!false); // Will be always "false"
console.log(typeof !!false); // It stills a "boolean" type

Double exclamation points?

This converts a value to a boolean and ensures a boolean type.

"foo"      // Evaluates to "foo".
!"foo" // Evaluates to false.
!!"foo" // Evaluates to true.

If foo.bar is passed through, then it may not be 0 but some other falsy value. See the following truth table:

Truth Table for javascript

''        ==   '0'           // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r\n" == 0 // true

Source: Doug Crockford

Javascript also gets really weird when it comes to NaN values. And this is the only case I can think of off the top of my head where !! would behave differently to ===.

NaN   ===  NaN     //false
!!NaN === !!NaN //true

// !!NaN is false

How to write a simple for loop that will populate a new column based on values in an old column, using key-value pairs?

I would use dplyr::recode:

df$newcol <- dplyr::recode(df$label, !!!index)

Output:

> df
label newcol
1 a word1
2 b word2
3 c word3
>

How to remove missing values in summarise_all dplyr

An alternative approach based on the coalesce() function from tidyr

In the below code, we remove the type variable since the OP indicated we don't need it in the output. We then group_by() to essentially break up our data into separate data.frames for each ID. The coalesce_by_column() function we define then converts each of these into a list whose elements are each a vector of values for each gene column.

We finally can pass this list to coalesce(). coalesce() takes a set of vectors and finds the first non-NA value across the vectors for each index of the vectors. In practice, this means it can take multiple columns with only one or zero non-NA value across all columns for each index and collapse them into a single column with as many non-NA values as possible.

Usually we would have to pass each vector as its own object to coalesce() but we can use the (splice operator)[https://stackoverflow.com/questions/61180201/triple-exclamation-marks-on-r] !!! to pass each element of our list as its own vector. See the last example in ?"!!!" for a demonstration.

library(dplyr)
library(tidyr)

# Define a function to coalesce by column
coalesce_by_column <- function(df) {
coalesce(!!! as.list(df))
}

# Remove NA rows
df %>%
select(-type) %>%
group_by(ID) %>%
summarise(across(.fns = coalesce_by_column))
#> # A tibble: 2 x 4
#> ID genes1 genes2 genes3
#> <dbl> <dbl> <dbl> <dbl>
#> 1 1 2 0 2
#> 2 2 1 1 1


Related Topics



Leave a reply



Submit