How to change matrix entries using conditional if in R
Asked Answered
T

5

16

I have this example matrix and I want to change the entries of the matrix with "YES" or "NO" based on a conditional if statement.

a<-c(5,1,0,3,2,0.6,1.6,7,9,0)
b<-c(11,0,1,18,11,11,0,13,20,10)
c<-c(10,20,0.7,0.8,0.3,0.4,0,0.9,1,1)

MAT<-cbind(a,b,c)
MAT

for (i in 1:nrow(MAT)){
  for (j in 1:ncol(MAT)){
  if (MAT[i,j]>5){
    MAT[i,j]="YES"
    } else {
    MAT[i,j]="NO"
    }
  }
}
print(MAT)

The output I got is like this and its wrong. Please help tell me what's wrong and how to fix it?

      a     b    c   
[1,] "NO"  "NO" "NO"
[2,] "NO"  "NO" "NO"
[3,] "NO"  "NO" "NO"
[4,] "NO"  "NO" "NO"
[5,] "NO"  "NO" "NO"
[6,] "NO"  "NO" "NO"
[7,] "NO"  "NO" "NO"
[8,] "YES" "NO" "NO"
[9,] "YES" "NO" "NO"
[10,] "NO"  "NO" "NO"
Thoron answered 15/8, 2021 at 11:16 Comment(0)
P
18

You do not need loops here. Just use the whole matrix in your call to x>5

ifelse(MAT>5, "YES", "NO")

This will do the logical operation over the entire matrix, and output a logical matrix.

You can reassign the VALUES from the output of ifelse() while keeping the STRUCTURE of MAT by using the empty brackets [], as in:

MAT[]<-ifelse(MAT>5, "YES", "NO")
Paralysis answered 15/8, 2021 at 11:21 Comment(5)
It works well as I want and it is the simplest code among others so I accept thisThoron
Empty brackets after MAT are not necessary here because ifelse returns a value with the same shape as its test argument. Since MAT>5 is a matrix, the value of ifelse will be a matrix as well.Codi
Yes, you are right for this particular case, @Robert. But I think including the empty brackets is good practice here, as it is a more general approach that enforces the preservation of the class of the original data. Ifelse does return an object the same type of the 'test' argument. But the '<' is a function that coerces to a logical vector or matrix. If MAT was a data.frame, the empty brackets would be necessary, otherwise MAT would be coerced into a matrix.Paralysis
@Paralysis This is a good point, but in your answer you state that the output from ifelse would be a vector (a matrix stripped from its dim attribute) which is not true.Codi
Yes, the answer included an innacurate statement, thanks for spoting that out, @Robert. I corrected the answer, considering your observations.Paralysis
G
10

Reason of Failure

The reason you failed in your attempt comes from this part:

  if (MAT[i,j]>5){
    MAT[i,j]="YES"
    } else {
    MAT[i,j]="NO"
    }
  }

You should have be aware of that MAT is numerical, but you are assigning characters to MAT with in if...else... statement, which will make MAT converted to a character matrix. In this case, when you run MAT[i,j] > 5, you are comparing a character with a numeric value, e.g., "18" > 5, which returns an undesired FALSE.


Workaround

A workaround is using another variable to store the values after if...else..., instead of replacing values in MAT:

a <- c(5, 1, 0, 3, 2, 0.6, 1.6, 7, 9, 0)
b <- c(11, 0, 1, 18, 11, 11, 0, 13, 20, 10)
c <- c(10, 20, 0.7, 0.8, 0.3, 0.4, 0, 0.9, 1, 1)

MAT <- cbind(a, b, c)
out <- MAT

for (i in 1:nrow(MAT)) {
  for (j in 1:ncol(MAT)) {
    if (MAT[i, j] > 5) {
      out[i, j] <- "YES"
    } else {
      out[i, j] <- "NO"
    }
  }
}

such that

> out
      a     b     c
 [1,] "NO"  "YES" "YES"
 [2,] "NO"  "NO"  "YES"
 [3,] "NO"  "NO"  "NO"
 [4,] "NO"  "YES" "NO"
 [5,] "NO"  "YES" "NO"
 [6,] "NO"  "YES" "NO"
 [7,] "NO"  "NO"  "NO"
 [8,] "YES" "YES" "NO"
 [9,] "YES" "YES" "NO"
[10,] "NO"  "YES" "NO"

Alternative

There are already many answers to this question, and below is another base R option

> `dim<-`(as.character(factor(MAT > 5, labels = c("NO", "YES"))), dim(MAT))
      [,1]  [,2]  [,3]
 [1,] "NO"  "YES" "YES"
 [2,] "NO"  "NO"  "YES"
 [3,] "NO"  "NO"  "NO"
 [4,] "NO"  "YES" "NO"
 [5,] "NO"  "YES" "NO"
 [6,] "NO"  "YES" "NO"
 [7,] "NO"  "NO"  "NO"
 [8,] "YES" "YES" "NO"
 [9,] "YES" "YES" "NO"
[10,] "NO"  "YES" "NO"
Gesner answered 15/8, 2021 at 20:12 Comment(3)
For the alternative, it's also possible to write structure(as.character(...), dim = dim(MAT)) or array(as.character(...), dim = dim(MAT)).Nadanadab
@Jean-ClaudeArbaut Yes, exactly.Gesner
@Jean-ClaudeArbaut could you please check my post here #68793963 since it does not work when I use the codes inside the r function I madeThoron
N
5

Using just logical matrix converted to numeric index

MAT[] <- c("NO", "YES")[1 + (MAT > 5)]

-ouptut

> MAT
      a     b     c    
 [1,] "NO"  "YES" "YES"
 [2,] "NO"  "NO"  "YES"
 [3,] "NO"  "NO"  "NO" 
 [4,] "NO"  "YES" "NO" 
 [5,] "NO"  "YES" "NO" 
 [6,] "NO"  "YES" "NO" 
 [7,] "NO"  "NO"  "NO" 
 [8,] "YES" "YES" "NO" 
 [9,] "YES" "YES" "NO" 
[10,] "NO"  "YES" "NO" 
Nightie answered 15/8, 2021 at 18:46 Comment(0)
C
3

Try this instead:

apply(MAT, 2, function(x) ifelse(x > 5, "YES", "NO"))
      a     b     c    
 [1,] "NO"  "YES" "YES"
 [2,] "NO"  "NO"  "YES"
 [3,] "NO"  "NO"  "NO" 
 [4,] "NO"  "YES" "NO" 
 [5,] "NO"  "YES" "NO" 
 [6,] "NO"  "YES" "NO" 
 [7,] "NO"  "NO"  "NO" 
 [8,] "YES" "YES" "NO" 
 [9,] "YES" "YES" "NO" 
[10,] "NO"  "YES" "NO" 
Cacique answered 15/8, 2021 at 11:21 Comment(0)
C
2

Update: After the helpful notes of Jean-Claude Arbaut, ThomasIsCoding and GuedesBF please note that the first answer is wrong, here is an alternative with dplyr:

We could use across after changing matrix to tibble class and rechange to matrix after our operation:

library(tibble)
library(dplyr)

MAT <- MAT %>% 
  as_tibble() %>% 
  mutate(across(everything(), ~ifelse(. > 5, "YES", "NO"))) %>% 
  as.matrix()

First answer: Warning!

Do not use this code

MAT[MAT>5] <- "yes"
MAT[MAT<=5] <- "no"

as Jean-Claude Arbaut, ThomasIsCoding and GuedesBF indicated, it will coerce to character after the first assignment, which could lead to unexpected outcomes in downstream operations.

Cinque answered 15/8, 2021 at 11:21 Comment(3)
Highly buggy code. Note that after the first instruction, MAT is a character matrix. Then, for instance, "6e-12" <= 5 is false. Even if you don't change the type inadvertently, it's dangereous to write it this way. Replace "yes" with 1 and "no" with 0 and it fails again.Nadanadab
This will coerce to character after the first assignment, which could lead to unexpected outcomes in dowstream operations as observed by @Jean-ClaudeArbaut and ThomasIsCodingParalysis
Thank you all for your input. I have updated my answer. Please tell me if I should delete the answer or maybe leave it to have an inverse learning effect.Cinque

© 2022 - 2024 — McMap. All rights reserved.