Change values of matrix where row names equal column names

Question

I am trying to change the values of a matrix so that, for each element where the row name equals the column name, the resulting matrix will have a value of one.

> z<-matrix(0, nrow=10, ncol=8)
> colnames(z)<-letters[1:8]
> rownames(z)<-c("f", "c", "a", "f", "a", "b", "f", "b", "h", "c")
> z
  a b c d e f g h
f 0 0 0 0 0 0 0 0
c 0 0 0 0 0 0 0 0
a 0 0 0 0 0 0 0 0
f 0 0 0 0 0 0 0 0
a 0 0 0 0 0 0 0 0
b 0 0 0 0 0 0 0 0
f 0 0 0 0 0 0 0 0
b 0 0 0 0 0 0 0 0
h 0 0 0 0 0 0 0 0
c 0 0 0 0 0 0 0 0

z should be:

  a b c d e f g h
f 0 0 0 0 0 1 0 0
c 0 0 1 0 0 0 0 0
a 1 0 0 0 0 0 0 0
f 0 0 0 0 0 1 0 0
a 1 0 0 0 0 0 0 0
b 0 1 0 0 0 0 0 0
f 0 0 0 0 0 1 0 0
b 0 1 0 0 0 0 0 0
h 0 0 0 0 0 0 0 1
c 0 0 1 0 0 0 0 0

I tried:

> z[unique(rownames(z)), unique(rownames(z))]<-1
> z
  a b c d e f g h
f 1 1 1 0 0 1 0 1
c 1 1 1 0 0 1 0 1
a 1 1 1 0 0 1 0 1
f 0 0 0 0 0 0 0 0
a 0 0 0 0 0 0 0 0
b 1 1 1 0 0 1 0 1
f 0 0 0 0 0 0 0 0
b 0 0 0 0 0 0 0 0
h 1 1 1 0 0 1 0 1
c 0 0 0 0 0 0 0 0

and:

> z["a", "a"]<-1
> z
  a b c d e f g h
f 0 0 0 0 0 0 0 0
c 0 0 0 0 0 0 0 0
a 1 0 0 0 0 0 0 0
f 0 0 0 0 0 0 0 0
a 0 0 0 0 0 0 0 0
b 0 0 0 0 0 0 0 0
f 0 0 0 0 0 0 0 0
b 0 0 0 0 0 0 0 0
h 0 0 0 0 0 0 0 0
c 0 0 0 0 0 0 0 0

but that only changed the first 'a' in the 'a' column.


Show source
| R   | matrix   2016-12-28 19:12 3 Answers

Answers ( 3 )

  1. 2016-12-28 19:12

    We can use row/column indexing to change the elements to 1

    z[cbind(1:nrow(z), match( rownames(z), colnames(z)))] <- 1
    z
    #  a b c d e f g h
    #f 0 0 0 0 0 1 0 0
    #c 0 0 1 0 0 0 0 0
    #a 1 0 0 0 0 0 0 0
    #f 0 0 0 0 0 1 0 0
    #a 1 0 0 0 0 0 0 0
    #b 0 1 0 0 0 0 0 0
    #f 0 0 0 0 0 1 0 0
    #b 0 1 0 0 0 0 0 0
    #h 0 0 0 0 0 0 0 1
    #c 0 0 1 0 0 0 0 0
    

    Or another option is (should be slower for big datasets)

    `dimnames<-`(+(sapply(colnames(z), `==`, rownames(z))), dimnames(z))
    #  a b c d e f g h
    #f 0 0 0 0 0 1 0 0
    #c 0 0 1 0 0 0 0 0
    #a 1 0 0 0 0 0 0 0
    #f 0 0 0 0 0 1 0 0
    #a 1 0 0 0 0 0 0 0
    #b 0 1 0 0 0 0 0 0
    #f 0 0 0 0 0 1 0 0
    #b 0 1 0 0 0 0 0 0
    #h 0 0 0 0 0 0 0 1
    #c 0 0 1 0 0 0 0 0
    

    NOTE: BTW, both the solutions are base R only solutions and not came from some external packages.

    Benchmarks

    z1 <- matrix(0, 5000, 5000)
    colnames(z1) <- 1:5000
    set.seed(24)
    row.names(z1) <- sample(1:5000, 5000, replace=TRUE)
    z2 <- z1
    z3 <- z1
    z4 <- z1
    system.time(z1[cbind(1:nrow(z1), match( rownames(z1), colnames(z1)))] <- 1)
    #    user  system elapsed 
    #   0.03    0.08    0.11 
    system.time(z2[outer(rownames(z2), colnames(z2), "==")] <- 1)
    #   user  system elapsed 
    #   0.67    0.16    0.83 
    identical(z1, z2)
    #[1] TRUE
    
    system.time( `dimnames<-`(+(sapply(colnames(z3), `==`, rownames(z3))), dimnames(z3)))
    #   user  system elapsed 
    #  31.70    0.39   32.28 
    
    system.time(z3[vapply(colnames(z3), function(x) x== rownames(z3), 
             logical(nrow(z3)))] <- 1)
    #  user  system elapsed 
    #   0.22    0.00    0.21 
    

    Testing with @Procrastinatus Maximus modification

    system.time(z4[sapply(colnames(z4), `==`, rownames(z4))] <- 1)
    #   user  system elapsed 
    #  28.42    0.36   28.85 
    

    By testing it on a 10000 x 10000 matrix, the timings are

    system.time(z1[cbind(1:nrow(z1), match( rownames(z1), colnames(z1)))] <- 1)
    #   user  system elapsed 
    #    0.12    0.32    0.44 
    system.time(z2[outer(rownames(z2), colnames(z2), "==")] <- 1)
    #   user  system elapsed 
    #   2.72    0.86    3.58 
    

    and on 20000 X 20000 matrix

    system.time(z1[cbind(1:nrow(z1), match( rownames(z1), colnames(z1)))] <- 1)
    #   user  system elapsed 
    #   0.95    1.00    1.95 
    system.time(z2[outer(rownames(z2), colnames(z2), "==")] <- 1)
    #    user  system elapsed 
    #   15.47    5.87   21.39 
    
  2. 2016-12-28 19:12

    You can also do this with base R using outer.

    z[outer(rownames(z), colnames(z), "==")] <- 1
    z
      a b c d e f g h
    f 0 0 0 0 0 1 0 0
    c 0 0 1 0 0 0 0 0
    a 1 0 0 0 0 0 0 0
    f 0 0 0 0 0 1 0 0
    a 1 0 0 0 0 0 0 0
    b 0 1 0 0 0 0 0 0
    f 0 0 0 0 0 1 0 0
    b 0 1 0 0 0 0 0 0
    h 0 0 0 0 0 0 0 1
    c 0 0 1 0 0 0 0 0
    
  3. 2016-12-28 20:12

    Another option is (which is a modification of @akrun's 2nd option):

    z[sapply(colnames(z), `==`, rownames(z))] <- 1
    

    which also gives the correct answer:

    > z
      a b c d e f g h
    f 0 0 0 0 0 1 0 0
    c 0 0 1 0 0 0 0 0
    a 1 0 0 0 0 0 0 0
    f 0 0 0 0 0 1 0 0
    a 1 0 0 0 0 0 0 0
    b 0 1 0 0 0 0 0 0
    f 0 0 0 0 0 1 0 0
    b 0 1 0 0 0 0 0 0
    h 0 0 0 0 0 0 0 1
    c 0 0 1 0 0 0 0 0
    

    The difference with @akrun's 'dimnames' solution is that in the above approach only the necessary spots are converted to 1 which is an advantage when the original matrix doesn't just contain zero's. This is also achieved by the 'outer'-option from @lmo and the 'cbind'-option of @akrun.

◀ Go back