1

Let's say I have a symmetric matrix A, for example:

> A <- matrix(runif(16),nrow = 4,byrow = T)
> ind <- lower.tri(A)
> A[ind] <- t(A)[ind]
> A
          [,1]      [,2]      [,3]       [,4]
[1,] 0.4212778 0.6874073 0.1551896 0.46757640
[2,] 0.6874073 0.5610995 0.1779030 0.54072946
[3,] 0.1551896 0.1779030 0.9515304 0.79429777
[4,] 0.4675764 0.5407295 0.7942978 0.01206526

I also have a 4 x 3 matrix B that gives specific positions of matrix A, for example:

> B<-matrix(c(1,2,4,2,1,3,3,2,4,4,1,3),nrow=4,byrow = T)
> B
      [,1] [,2] [,3]
[1,]    1    2    4
[2,]    2    1    3
[3,]    3    2    4
[4,]    4    1    3

The B matrix represents the following positions of A: (1,1), (1,2), (1,4), (2,2), (2,1), (2,3), (3,3), (3,2), (3,4), (4,4), (4,1), (4,3).

I want to change the values of A that are NOT in the positions given by B, replacing them by Inf. The result I want is:

          [,1]      [,2]      [,3]       [,4]
[1,] 0.4212778 0.6874073       Inf 0.46757640
[2,] 0.6874073 0.5610995 0.1779030        Inf
[3,]       Inf 0.1779030 0.9515304 0.79429777
[4,] 0.4675764       Inf 0.7942978 0.01206526

How can I do that quickly avoiding a for loop (which I'm able to code)? I've seen many similar posts, but no one gave me what I want. Thank you!

AndrewGB
  • 16,126
  • 5
  • 18
  • 49
coolsv
  • 781
  • 5
  • 16
  • 2
    What does the third column of `B` represent? Is column 1 of B the `row` index and col2 the column index? – emilliman5 Mar 21 '19 at 17:09
  • The B matrix represents the following coordinates: `(1,1), (1,2), (1,4), (2,2), (2,1), (2,3) ` and so on. I just changed the text, based on your question ;-) – coolsv Mar 21 '19 at 17:12

5 Answers5

6

You want to do something like matrix subsetting (e.g., P[Q]) except that you can't use negative indexing in matrix subsetting (e.g., P[-Q] is not allowed). Here's a work-around.

Store the elements you want to retain from A in a 2-column matrix where each row is a coordinate of A:

Idx <- cbind(rep(1:4, each=ncol(B)), as.vector(t(B)))

Create a matrix where all values are Inf, and then overwrite the values you wanted to "keep" from A:

Res <- matrix(Inf, nrow=nrow(A), ncol=ncol(A))
Res[Idx] <- A[Idx]

Result

Res
#          [,1]        [,2]        [,3]       [,4]
#[1,] 0.9043131 0.639718071         Inf 0.19158238
#[2,] 0.6397181 0.601327568 0.007363378        Inf
#[3,]       Inf 0.007363378 0.752123162 0.61428003
#[4,] 0.1915824         Inf 0.614280026 0.02932679
DanY
  • 5,920
  • 1
  • 13
  • 33
  • Yes :-) I saw the post concerning the matrix subsetting `P[Q]`, but as you said, negative indexing is not allowed. I thought about your solution too, but wanted to avoid the creation of another matrix, specially if the matrix `A` is very large. But that's seems to be the most direct way :-) – coolsv Mar 21 '19 at 17:39
2

Here is a one-liner

A[cbind(1:nrow(A), sum(c(1:ncol(A))) - rowSums(B))] <- Inf

          [,1]       [,2]       [,3]      [,4]
[1,] 0.4150663 0.23440503        Inf 0.6665222
[2,] 0.2344050 0.38736067 0.01352211       Inf
[3,]       Inf 0.01352211 0.88319263 0.9942303
[4,] 0.6665222        Inf 0.99423028 0.7630221
cropgen
  • 1,920
  • 15
  • 24
  • `sum() - rowSums()` is a cute workaround for that `-match()` approach that I did -- nice! – DanY Mar 21 '19 at 17:57
  • I tried with a `150 x 150` symmetric `A` matrix and a `150 x 3 ` `B` matrix but it doesn't work... Too bad.... Following error: `Error in A[cbind(1:nrow(A), sum(c(1:ncol(A))) - rowSums(B))] : subscript out of bounds` – coolsv Mar 21 '19 at 19:11
  • It is because `sum(c(1:ncol(A))) - rowSums(B))` expression outputs a single value which is less than the number of columns in `A`. That only happens when `B` has only one less column than `A`. If the difference is anything bigger or smaller, then I guess it won't work. This is very specific to your original question and might not work in generic cases. – cropgen Mar 21 '19 at 20:12
  • Ah ok, I see!Thank you! – coolsv Mar 21 '19 at 20:53
1

Another way would be to identify the cells with an apply and set then to inf.

cnum <- 1:ncol(A)
A[cbind(1:nrow(A), apply(B, 1, function(x) cnum[-which(cnum %in% x)]))] <- Inf
A
#           [,1]      [,2]      [,3]      [,4]
# [1,] 0.9148060 0.9370754       Inf 0.8304476
# [2,] 0.9370754 0.5190959 0.7365883       Inf
# [3,]       Inf 0.7365883 0.4577418 0.7191123
# [4,] 0.8304476       Inf 0.7191123 0.9400145

Note: set.seed(42).

jay.sf
  • 60,139
  • 8
  • 53
  • 110
1
A <- matrix(runif(16),nrow = 4,byrow = T)
ind <- lower.tri(A)
A[ind] <- t(A)[ind]

## >A[]
##        [,1]        [,2]      [,3]      [,4]
## [1,] 0.07317535 0.167118857 0.0597721 0.2128698
## [2,] 0.16711886 0.008661005 0.6419335 0.6114373
## [3,] 0.05977210 0.641933514 0.7269202 0.3547959
## [4,] 0.21286984 0.611437278 0.3547959 0.4927997

The first thing to notice is that the matrix B is not very helpful in its current form, because the information we need is the rows and each value in B

 B<-matrix(c(1,2,4,2,1,3,3,2,4,4,1,3),nrow=4,byrow = T)
> B
##      [,1] [,2] [,3]
## [1,]    1    2    4
## [2,]    2    1    3
## [3,]    3    2    4
## [4,]    4    1    3

So we can create that simply by using melt and use Var1 and value.

>melt(B)
##    Var1 Var2 value
## 1     1    1     1
## 2     2    1     2
## 3     3    1     3
## 4     4    1     4
## 5     1    2     2
## 6     2    2     1
## 7     3    2     2
## 8     4    2     1
## 9     1    3     4
## 10    2    3     3
## 11    3    3     4
## 12    4    3     3

We need to replace the non existing index in A by inf. This is not easy to do directly. So an easy way out would be to create another matrix of Inf and fill the values of A according to the index of melt(B)

> C<-matrix(Inf,nrow(A),ncol(A))

 idx <- as.matrix(melt(B)[,c("Var1","value")])
 C[idx]<-A[idx]

> C
##            [,1]        [,2]      [,3]      [,4]
## [1,] 0.07317535 0.167118857 0.0597721 0.2128698
## [2,] 0.16711886 0.008661005 0.6419335       Inf
## [3,]        Inf 0.641933514 0.7269202 0.3547959
## [4,] 0.21286984         Inf 0.3547959 0.4927997
DJJ
  • 2,481
  • 2
  • 28
  • 53
  • Yes, indeed ;-) Actually, the `B` matrix is the output of the command `dist_to_knn` from the `scanstatistics` package. I just created arbitrary similar matrices to simplify the problem. All the given answers are very useful :-) – coolsv Mar 21 '19 at 17:57
0

Another approach that accomplishes matrix subsetting (e.g., P[Q]) would be to create the index Q manually. Here's one approach.

Figure out which column index is "missing" from each row of B:

col_idx <- apply(B, 1, function(x) (1:nrow(A))[-match(x, 1:nrow(A))])

Create subsetting matrix Q

Idx <- cbind(1:nrow(A), col_idx)

Do the replacement

A[Idx] <- Inf

Of course, you can make this a one-liner if you really want to:

A[cbind(1:nrow(A), apply(B, 1, function(x) (1:nrow(A))[-match(x, 1:nrow(A))])]
DanY
  • 5,920
  • 1
  • 13
  • 33
  • 1
    Any reason you have 2 answers instead of merging them into one? – divibisan Mar 21 '19 at 17:32
  • Sort of. This answer relies heavily on the structure of the problem (that only one column index is missing from each row), but is likely a faster implementation if the real application is to very large matrices. The other answer relies less on the structure of the problem, but is probably slower for large matrices. I wanted to give the OP the freedom to select whichever works best for his/her real application. (Although, I get your point that they both directly answer the question as it is asked and could be combined.) – DanY Mar 21 '19 at 17:36
  • I rarely offer multiple answers on the same question. If I'm opposing a rule or norm here on SO, just let me know and I'll combine them. – DanY Mar 21 '19 at 17:37
  • Attractive solution too! – coolsv Mar 21 '19 at 17:40
  • @DanY Not that I know of, I've just always seem people combine multiple possible approaches into one big canonical answer. – divibisan Mar 21 '19 at 17:56