3

I have a square matrix and now I want to replace all its off-diagonal elements with a fixed value. Some approaches how this can be done are discussed in Replacing non-diagonal elements in a matrix in R (hopefully a better asked this time)

For example,

m <- matrix(c(1,2,3,4,3,9,5,5,8),ncol=3)
m[upper.tri(m) | lower.tri(m)] <- 999
m

I am wondering if the step can be performed using dplyr chain rule, something like

library(dplyr)
matrix(c(1,2,3,4,3,9,5,5,8),ncol=3) %>%
 ### add this -> m[upper.tri(m) | lower.tri(m)] <- 999

Any pointer will be very helpful

Maël
  • 45,206
  • 3
  • 29
  • 67
Brian Smith
  • 1,200
  • 4
  • 16

5 Answers5

4

If it's for the sake of brevity, you could define + apply an anonymous function in your pipeline like so:

matrix(c(1,2,3,4,3,9,5,5,8),ncol=3) %>%
  (\(.) {.[upper.tri(.) | lower.tri(.)] = 999; .})() %>%
## rest of pipeline

Edit shamelessly stealing borrowing from @Maël 's elegant solution:

matrix(c(1,2,3,4,3,9,5,5,8),ncol=3) %>%
  ifelse(row(.) == col(.), ., 999) %>%
## rest of pipeline

(note: does not work with the native pipe operator |>)

I_O
  • 4,983
  • 2
  • 2
  • 15
4

For large enough m and n, it is much more efficient to initialize the m-by-n result with all elements equal to r and replace only the diagonal elements:

m <- 1000L
n <- 1000L
r <- -1L
x <- diag(seq_len(min(m, n)), m, n)

microbenchmark::microbenchmark(
    A = x |> (\(.) `diag<-`(array(r, dim(.), dimnames(.)), diag(., names = FALSE)))(),
    B = x |> (\(.) `[<-`(., row(.) != col(.), r))(),
    C = x |> (\(.) `[<-`(., upper.tri(.) | lower.tri(.), r))(),
    times = 1000L)
Unit: microseconds
 expr      min         lq       mean    median        uq      max neval
    A   67.445   283.2075   520.3644   315.823   343.498 12723.82  1000
    B 4475.232  5024.3450  5750.4401  5479.527  6081.837 16821.36  1000
    C 8863.134 10034.4835 11035.8599 10535.052 11030.066 22211.34  1000
Mikael Jagan
  • 9,012
  • 2
  • 17
  • 48
3

With ifelse:

ifelse(row(m) == col(m), m, 999)

Or with replace:

m %>% 
  replace(row(.) != col(.), 999)
m %>%
  replace(. != diag(.), 999)
m |>
  replace(m != diag(m), 999)
Maël
  • 45,206
  • 3
  • 29
  • 67
2

We may use

matrix(c(1,2,3,4,3,9,5,5,8),ncol=3) %>%
   `[<-`(upper.tri(.) | lower.tri(.), 999)

-output

      [,1] [,2] [,3]
[1,]    1  999  999
[2,]  999    3  999
[3,]  999  999    8
akrun
  • 874,273
  • 37
  • 540
  • 662
1

We can try pipes like below

matrix(c(1, 2, 3, 4, 3, 9, 5, 5, 8), ncol = 3) %>%
  {
    999 * (!diag(nrow(.))) + diag(.)
  }
ThomasIsCoding
  • 96,636
  • 9
  • 24
  • 81