简体   繁体   中英

Removing diagonal elements from matrix in R

How can I remove the diagonal elements (diagL) from my matrix L using R? I tried using the following:

subset(L, select=-diag(L)) or
subset(L, select=-c(diag(L)))

but I get 0 numbers...

The R programming language? I like C better, it is easier to spell.

One way is to create a matrix with the numbers the way I like them to look:

a<-t(matrix(1:16,nrow=4,ncol=4))

which looks like:

     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    5    6    7    8
[3,]    9   10   11   12
[4,]   13   14   15   16

Delete the values on the diagonal:

diag(a)=NA

which results in:

     [,1] [,2] [,3] [,4]
[1,]   NA    2    3    4
[2,]    5   NA    7    8
[3,]    9   10   NA   12
[4,]   13   14   15   NA

To actually REMOVE the values, rather than just making them go away, we need to recast:

a<-t(matrix(t(a)[which(!is.na(a))],nrow=3,ncol=4))

Which results in:

     [,1] [,2] [,3]
[1,]    2    3    4
[2,]    5    7    8
[3,]    9   10   12
[4,]   13   14   15

which is the same thing as we got in C, above.

This is a little circuitous but it results in what I see as a correct answer. I would be interested in seeing an improved solution by somebody that knows R better than I do.

A bit of an explanation on the assignment:

a<-t(matrix(t(a)[which(!is.na(a))],nrow=3,ncol=4))
  1. The !is.na(a) gives us a list of TRUE, FALSE values for which elements were nulled out.
  2. The which(!is.na(a)) gives us a list of subscripts for each of the true elements.
  3. The t(a) transposes the matrix since we need to pull based upon the subscripts in #2.
  4. t(a)[which(!is.na(a))] gives us a list of numbers that is missing the diagonal NA values.
  5. matrix(t(a)[which(!is.na(a))],nrow=3,ncol=4) converts the list from #4 into a matrix, which is the transpose of what we want.
  6. a<-t(matrix(1:16,nrow=4,ncol=4)) (the whole thing) transposes #5 into the form we want and assigns it to the a variable.

This works with cases such as a<-t(matrix(11:26,nrow=4,ncol=4)) .

Here is some artificial data for illustration:

x <- matrix(1:16, 4, 4)
n <- nrow(x)
x
      [,1] [,2] [,3] [,4]
 [1,]    1    5    9   13
 [2,]    2    6   10   14
 [3,]    3    7   11   15
 [4,]    4    8   12   16

After vectorizing the matrix x , the diagonal elements correspond to the indices 1, n+2, 2*n+3, ... , that is, to the sequence seq(1, n^2, n+1) . You can remove these indices by

x[-seq(1,n^2,n+1)]
[1]  2  3  4  5  7  8  9 10 12 13 14 15

After "removing the diagonal" of the matrix, you can either shift the lower triangular matrix upward to get a matrix with n-1 rows and n columns by

matrix(x[-seq(1,n^2,n+1)], n-1, n)
     [,1] [,2] [,3] [,4]
[1,]    2    5    9   13
[2,]    3    7   10   14
[3,]    4    8   12   15

or, and this is probably what you want, you can shift the lower triangular matrix to the right to get a matrix with n rows and n-1 columns by transposing x before removing the diagonal indices and transposing it back afterwards

t(matrix(t(x)[-seq(1,n^2,n+1)], n-1, n))
     [,1] [,2] [,3]
[1,]    5    9   13
[2,]    2   10   14
[3,]    3    7   15
[4,]    4    8   12

Keep in mind that the diagonal is going to have the same X and Y index. A quick program to zero out the diagonal in C follows:

#include <stdio.h>
static void printMat(char mat[4][4], char *comment)
{
    printf("%s:\n", comment);
    for(int jj=0; jj<4; jj++) {
        for(int ii=0; ii<4; ii++) {
            printf("%2d ",mat[jj][ii]);
        }
        printf("\n");
    }
}
main()
{
    static char matrix[4][4]= {
        { 1, 2, 3, 4},
        { 5, 6, 7, 8},
        { 9,10,11,12},
        {13,14,15,16}
    };


    printMat(matrix,"Before");
    for(int ii=0; ii<4; ii++) {
        matrix[ii][ii]=0;

    }
    printMat(matrix,"After");
}

This results in:

Before:
 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16
After:
 0  2  3  4
 5  0  7  8
 9 10  0 12
13 14 15  0

To REMOVE rather that just clear the diagonal is more complicated.

This should do the trick: (Keep in mind that a memcpy of zero bytes can address elements that are not there.)

#include <stdio.h>
#include <strings.h>
static void printMat(char *mat, int xDim, int yDim,char *comment)
{
    printf("%s:\n", comment);
    for(int jj=0; jj<yDim; jj++) {
        for(int ii=0; ii<xDim; ii++) {
            printf("%2d ",(mat[(jj)*xDim+ii]) );
        }
        printf("\n");
    }
}
main()
{
    static char matrix[4][4]= {
        { 1, 2, 3, 4},
        { 5, 6, 7, 8},
        { 9,10,11,12},
        {13,14,15,16}
    };
    static char new[4][3];

    printMat((char*)matrix,4,4,"Before");

    for(int ii=0; ii<4; ii++) {
        memcpy(&new[ii][0], &matrix[ii][0],ii);
        memcpy(&new[ii][ii],&matrix[ii][ii+1], 4-ii);
    }

    printMat((char*)new,3,4,"After");
}

Results in:

Before:
 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16
After:
 2  3  4
 5  7  8
 9 10 12
13 14 15

Of course, if you want something in another language, it helps to ask.

Still using basic R, it is possible to use a combination of upper.tri() and lower.tri to find what you are looking for in one line. To have it handier, I created a one-line function. The code goes as follows.

a <- matrix(rnorm(100), nrow = 4, ncol = 4)
select_all_but_diag <- function(x) matrix(x[lower.tri(x, diag = F) | upper.tri(x, diag = F)], nrow = nrow(x) - 1, ncol = ncol(x))
select_all_but_diag(a)

This is the matrix a before (in my case):

    [,1] [,2] [,3] [,4]
[1,]  0.3  2.5 -0.5  2.8
[2,]  0.7  1.1 -1.4 -0.7
[3,]  0.9  0.8  1.6  0.5
[4,] -0.8 -0.3 -0.9  1.6

And this is the select_all_but_diag(a) output matrix:

   [,1] [,2] [,3] [,4]
[1,]  0.7  2.5 -0.5  2.8
[2,]  0.9  0.8 -1.4 -0.7
[3,] -0.8 -0.3 -0.9  0.5

EDIT for row-major

Instead, if you want the collapse to be row-major, you can use this extended version of the function which allows you to collapse the matrix reducing the number of columns instead of the number of rows.

select_all_but_diag <- function(x, collapse_by = "row") {
  if(collapse_by == "row") matrix(x[lower.tri(x, diag = F) | upper.tri(x, diag = F)], nrow = nrow(x) - 1, ncol = ncol(x))
  else if(collapse_by == "col") t(matrix(t(x)[lower.tri(x, diag = F) | upper.tri(x, diag = F)], nrow = nrow(x) - 1, ncol = ncol(x)))
  else stop("collapse_by accepts only 'row' or 'col'.")
}
a
select_all_but_diag(a, collapse_by = "col")

This is the output of the latter:

     [,1] [,2] [,3]
[1,]  2.5 -0.5  2.8
[2,]  0.7 -1.4 -0.7
[3,]  0.9  0.8  0.5
[4,] -0.8 -0.3 -0.9

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM