search multiple columns for values above '60', if true return the value from another column

This is simple enough not to absolutely require cut-and-paste reprex (see the FAQ, but it's a good idea to cut friction as much as possible.

This is a concise way to do this, with little syntax to master. I'll unpack it below

# fake data created by random sampling 
# without a seed, so they are likely to
# be all different and different each
# time data frame is created with this
# snippet
m <- matrix(
  c(plate = 1:50,
  sample(20:100,50, replace = TRUE),
  sample(20:100,50, replace = TRUE),
  sample(20:100,50, replace = TRUE)),
  nrow = 50,
  ncol = 4

colnames(m) <- c("plate","m1","m2","m3")
#>      plate m1 m2  m3
#> [1,]     1 32 72  51
#> [2,]     2 47 60  89
#> [3,]     3 53 84  23
#> [4,]     4 94 98  78
#> [5,]     5 50 72 100
#> [6,]     6 98 61  47

mark_na <- function(x) ifelse(x < 60,NA,x)

m[,2:4] <- apply(m[,2:4],2,mark_na)

#>      plate m1 m2  m3
#> [1,]     1 NA 72  NA
#> [2,]     2 NA 60  89
#> [3,]     3 NA 84  NA
#> [4,]     4 94 98  78
#> [5,]     5 NA 72 100
#> [6,]     6 98 61  NA

Created on 2023-04-05 with reprex v2.0.2

  1. My paradigm of using R is school algebra—f(x)=y. x is an object that needs some transformation, y is the object containing the transformation and f is the function object that does the transformation. Each of these may be, and usually is, composite.

  2. The object chosen for x has a big influence on f. I've used a matrix because all the contents to be subject to f is numeric. A matrix must be either all character or all numeric. Internally, both columns and rows are vectors. A data frame, which is where incoming data usually lands, can mix character and numeric types. *However, both columns and rows are lists. This is an important difference because a matrix can be treated as a single object and transformed more simply.

  3. The function isolates the logical condition to be tested—whether a value is less than 61, because those are the values to be replaced with NA.

  4. The matrix object, m, has objects and rows. Here m[,2:4] means all rows of m (because the row position in the brackets is empty and columns 2:4 (if we wanted only the second and fourth column, it would be m[,c(2,4)]). Think row/column, row/column. If only dealing with columns, it can be shorthanded m[2:3] which we usually do. When we want to change only some rows, it would be `m[1:7,2:3]. I find it helpful to always have the comma—one less thing to keep track of.

  5. At this point, we know that we are changing every thing in m except the first, plate column with a value of less than 61 to NA and we know how. Now, we do that in a single pass by applying our function to the target columns by columns (we could also do it row-wise). That's what apply does.

  6. As far as variable dimensions, dim() works like the subset operator, row/column. m is

> dim(m)
[1] 50  4

The script will work for any numbers of rows. Some wand waving is required for a variable number of columns. Come back with a reprex if you need help with that case.