dat[cbind(FALSE, t(apply(dat[,-1], 1, function(z) duplicated(z) & z >= max(z))))] <- NA
dat
# id t1 t2 t3 t4 t5
# 1 1 5 10 11 NA NA
# 2 2 6 7 12 13 16
# 3 3 1 2 NA NA NA
# 4 4 3 3 4 NA NA
Breakdown:
Since we need to work row-wise, we'll use apply(dat, 1, .).
On each row, we need those that are at-or-above the max value and duplicated, ergo the anon-func
function(z) duplicated(z) & z >= max(z)
This by itself produces a transposed matrix (because of how R's apply operates), which we then transpose into a correctly-shaped logical matrix:
t(apply(dat[,-1], 1, function(z) duplicated(z) & z >= max(z)))
# t1 t2 t3 t4 t5
# [1,] FALSE FALSE FALSE TRUE TRUE
# [2,] FALSE FALSE FALSE FALSE FALSE
# [3,] FALSE FALSE TRUE TRUE TRUE
# [4,] FALSE FALSE FALSE TRUE TRUE
We omitted the id column with dat[,-1], but for reassigning NA, we need to cbind(FALSE, .) so that the id column is preserved.
Lastly, we reassign to just those fields by using dat[.] <- NA.
PS: the alternate functions used in other answers works just as well here:
# equivalent with this sample data
function(z) duplicated(z) & z >= max(z)
function(z) seq_along(z) > which.max(z)
The largest differences in the answers (so far) is a preference towards R dialects, whether base or dplyr+purrr.
Data
dat <- structure(list(id = 1:4, t1 = c(5L, 6L, 1L, 3L), t2 = c(10L, 7L, 2L, 3L), t3 = c(11L, 12L, NA, 4L), t4 = c(NA, 13L, NA, NA), t5 = c(NA, 16L, NA, NA)), row.names = c(NA, -4L), class = "data.frame")