'Find all tied maximum values in a row and return true or false if column contains max value

I have some data containing numeric columns :

df <- data.frame(v1 = c(0,1,2,3,4,5,6,7,8,9),
                 v2 = c(2,1,4,7,6,7,8,9,0,1),
                 v3 = c(4,1,6,7,8,9,0,1,2,3),
                 v4 = c(0,1,2,7,4,5,6,7,8,9),
                 v5 = c(0,1,6,3,6,9,8,9,0,1))

I can find the first maximum value and return its column name using which.max:

df$max <- colnames(df)[apply(df,1,which.max)]

Instead I would like to add five new columns and insert TRUE if the corresponding column is the max value or tied for the max value, and FALSE if not:

   v1 v2 v3 v4 v5 v1max v2max v3max v4max v5max
1   0  2  4  0  0 FALSE FALSE TRUE  FALSE FALSE
2   1  1  1  1  1 TRUE  TRUE  TRUE  TRUE  TRUE  
3   2  4  6  2  6 FALSE FALSE TRUE  FALSE TRUE     
4   3  7  7  7  3 FALSE TRUE  TRUE  TRUE  FALSE
5   4  6  8  4  6 FALSE FALSE TRUE  FALSE FALSE
6   5  7  9  5  9 FALSE FALSE TRUE  FALSE TRUE
7   6  8  0  6  8 FALSE TRUE  FALSE FALSE TRUE
8   7  9  1  7  9 FALSE TRUE  FALSE FALSE TRUE
9   8  0  2  8  0 TRUE  FALSE FALSE TRUE  FALSE
10  9  1  3  9  1 TRUE  FALSE FALSE TRUE  FALSE

Is there a simple way to achieve this?

r


Solution 1:[1]

Write an auxiliary function is.max and apply it row wise to df.

is.max <- function(x, na.rm = TRUE){
  x == max(x, na.rm = na.rm)
}

res <- t(apply(df, 1, is.max))
colnames(res) <- paste(colnames(res), "max", sep = ".")
res <- cbind(df, res)

res
#   v1 v2 v3 v4 v5 v1.max v2.max v3.max v4.max v5.max
#1   0  2  4  0  0  FALSE  FALSE   TRUE  FALSE  FALSE
#2   1  1  1  1  1   TRUE   TRUE   TRUE   TRUE   TRUE
#3   2  4  6  2  6  FALSE  FALSE   TRUE  FALSE   TRUE
#4   3  7  7  7  3  FALSE   TRUE   TRUE   TRUE  FALSE
#5   4  6  8  4  6  FALSE  FALSE   TRUE  FALSE  FALSE
#6   5  7  9  5  9  FALSE  FALSE   TRUE  FALSE   TRUE
#7   6  8  0  6  8  FALSE   TRUE  FALSE  FALSE   TRUE
#8   7  9  1  7  9  FALSE   TRUE  FALSE  FALSE   TRUE
#9   8  0  2  8  0   TRUE  FALSE  FALSE   TRUE  FALSE
#10  9  1  3  9  1   TRUE  FALSE  FALSE   TRUE  FALSE

Solution 2:[2]

A cbind() evaluating each row to the max() of each row wil do the trick:

df2<-cbind(df,df == apply(df,1,max))
colnames(df2)<-c("v1", "v2" ,"v3", "v4" ,"v5", "v1max", "v2max", "v3max" ,"v4max", "v5max")
df2            
# v1 v2 v3 v4 v5 v1max v2max v3max v4max v5max
# 1   0  2  4  0  0 FALSE FALSE  TRUE FALSE FALSE
# 2   1  1  1  1  1  TRUE  TRUE  TRUE  TRUE  TRUE
# 3   2  4  6  2  6 FALSE FALSE  TRUE FALSE  TRUE
# 4   3  7  7  7  3 FALSE  TRUE  TRUE  TRUE FALSE
# 5   4  6  8  4  6 FALSE FALSE  TRUE FALSE FALSE
# 6   5  7  9  5  9 FALSE FALSE  TRUE FALSE  TRUE
# 7   6  8  0  6  8 FALSE  TRUE FALSE FALSE  TRUE
# 8   7  9  1  7  9 FALSE  TRUE FALSE FALSE  TRUE
# 9   8  0  2  8  0  TRUE FALSE FALSE  TRUE FALSE
# 10  9  1  3  9  1  TRUE FALSE FALSE  TRUE FALSE

Solution 3:[3]

Using max.col:

cbind(df, df==df[cbind( 1:nrow(df), max.col(df) )])

   # v1 v2 v3 v4 v5    v1    v2    v3    v4    v5
# 1   0  2  4  0  0 FALSE FALSE  TRUE FALSE FALSE
# 2   1  1  1  1  1  TRUE  TRUE  TRUE  TRUE  TRUE
# 3   2  4  6  2  6 FALSE FALSE  TRUE FALSE  TRUE
# 4   3  7  7  7  3 FALSE  TRUE  TRUE  TRUE FALSE
# 5   4  6  8  4  6 FALSE FALSE  TRUE FALSE FALSE
# 6   5  7  9  5  9 FALSE FALSE  TRUE FALSE  TRUE
# 7   6  8  0  6  8 FALSE  TRUE FALSE FALSE  TRUE
# 8   7  9  1  7  9 FALSE  TRUE FALSE FALSE  TRUE
# 9   8  0  2  8  0  TRUE FALSE FALSE  TRUE FALSE
# 10  9  1  3  9  1  TRUE FALSE FALSE  TRUE FALSE

Solution 4:[4]

One tidyverse possibility could be:

df %>%
 mutate_all(list(max = ~ . == exec(pmax, !!!.)))

   v1 v2 v3 v4 v5 v1_max v2_max v3_max v4_max v5_max
1   0  2  4  0  0  FALSE  FALSE   TRUE  FALSE  FALSE
2   1  1  1  1  1   TRUE   TRUE   TRUE   TRUE   TRUE
3   2  4  6  2  6  FALSE  FALSE   TRUE  FALSE   TRUE
4   3  7  7  7  3  FALSE   TRUE   TRUE   TRUE  FALSE
5   4  6  8  4  6  FALSE  FALSE   TRUE  FALSE  FALSE
6   5  7  9  5  9  FALSE  FALSE   TRUE  FALSE   TRUE
7   6  8  0  6  8  FALSE   TRUE  FALSE  FALSE   TRUE
8   7  9  1  7  9  FALSE   TRUE  FALSE  FALSE   TRUE
9   8  0  2  8  0   TRUE  FALSE  FALSE   TRUE  FALSE
10  9  1  3  9  1   TRUE  FALSE  FALSE   TRUE  FALSE

Since dplyr 1.0.0

df %>%
    mutate(across(everything(), 
                  ~ . == exec(pmax, !!!.),
                  .names = "{.col}_max"))

Or using dplyr only:

df %>%
    rowwise() %>%
    mutate(across(everything(), 
                  ~ . == max(c_across(everything())), 
                  .names = "{.col}_max"))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rui Barradas
Solution 2 Carles S
Solution 3 989
Solution 4