21

Given input

A = np.array([[1,2,3],[4,5,6],[7,8,9]])
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

Need output :

array([[2, 3],
       [4, 6],
       [7, 8]])

It is easy to use iteration or loop to do this, but there should be a neat way to do this without using loops. Thanks

0

8 Answers 8

37

Approach #1

One approach with masking -

A[~np.eye(A.shape[0],dtype=bool)].reshape(A.shape[0],-1)

Sample run -

In [395]: A
Out[395]: 
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

In [396]: A[~np.eye(A.shape[0],dtype=bool)].reshape(A.shape[0],-1)
Out[396]: 
array([[2, 3],
       [4, 6],
       [7, 8]])

Approach #2

Using the regular pattern of non-diagonal elements that could be traced with broadcasted additions with range arrays -

m = A.shape[0]
idx = (np.arange(1,m+1) + (m+1)*np.arange(m-1)[:,None]).reshape(m,-1)
out = A.ravel()[idx]

Approach #3 (Strides Strikes!)

Abusing the regular pattern of non-diagonal elements from previous approach, we can introduce np.lib.stride_tricks.as_strided and some slicing help, like so -

m = A.shape[0]
strided = np.lib.stride_tricks.as_strided
s0,s1 = A.strides
out = strided(A.ravel()[1:], shape=(m-1,m), strides=(s0+s1,s1)).reshape(m,-1)

Runtime test

Approaches as funcs :

def skip_diag_masking(A):
    return A[~np.eye(A.shape[0],dtype=bool)].reshape(A.shape[0],-1)

def skip_diag_broadcasting(A):
    m = A.shape[0]
    idx = (np.arange(1,m+1) + (m+1)*np.arange(m-1)[:,None]).reshape(m,-1)
    return A.ravel()[idx]

def skip_diag_strided(A):
    m = A.shape[0]
    strided = np.lib.stride_tricks.as_strided
    s0,s1 = A.strides
    return strided(A.ravel()[1:], shape=(m-1,m), strides=(s0+s1,s1)).reshape(m,-1)

Timings -

In [528]: A = np.random.randint(11,99,(5000,5000))

In [529]: %timeit skip_diag_masking(A)
     ...: %timeit skip_diag_broadcasting(A)
     ...: %timeit skip_diag_strided(A)
     ...: 
10 loops, best of 3: 56.1 ms per loop
10 loops, best of 3: 82.1 ms per loop
10 loops, best of 3: 32.6 ms per loop
Sign up to request clarification or add additional context in comments.

1 Comment

Hello @Divakar! Do you know how can I remove the diagonal from 4D matrix? Thanks in advance!
5

Solution steps:

  • Flatten your array
  • Delete the location of the diagonal elements which is at the location range(0, len(x_no_diag), len(x) + 1)
  • Reshape your array to (num_rows, num_columns - 1)

The function:

import numpy as np

def remove_diag(x):
    x_no_diag = np.ndarray.flatten(x)
    x_no_diag = np.delete(x_no_diag, range(0, len(x_no_diag), len(x) + 1), 0)
    x_no_diag = x_no_diag.reshape(len(x), len(x) - 1)
    return x_no_diag

Example:

>>> x = np.random.randint(5, size=(3,3))
array([[0, 2, 3],
       [3, 4, 1],
       [2, 4, 0]])
>>> remove_diag(x)
array([[2, 3],
       [3, 1],
       [2, 4]])

Comments

3

I know I'm late to this party, but I have what I believe is a simper solution. So you want to remove the diagonal? Okay cool:

  • replace it with NaN
  • filter all but NaN (this converts to one dimensional as it can't assume the result will be square)
  • reset the dimensionality

`

 arr = np.array([[1,2,3],[4,5,6],[7,8,9]]).astype(np.float)
 np.fill_diagonal(arr, np.nan)
 arr[~np.isnan(arr)].reshape(arr.shape[0], arr.shape[1] - 1)

1 Comment

You will have to convert the array to 'float' type (if it was not; int type array) to be able to fill it with NaN values. Otherwise, the following error occurs: ValueError: cannot convert float NaN to integer. This approach is very slow as well compared to stackoverflow.com/a/63531391/11549398
3

Perhaps the cleanest way, based on Divakar's first solution but using len(array) instead of array.shape[0], is:

array_without_diagonal = array[~np.eye(len(array), dtype=bool)].reshape(len(array), -1)

1 Comment

I like this one the most.
2

Just with numpy, assuming a square matrix:

new_A = numpy.delete(A,range(0,A.shape[0]**2,(A.shape[0]+1))).reshape(A.shape[0],(A.shape[1]-1))

3 Comments

If you're going to post an almost exact duplicate of an existing answer, please explain why yours is better.
the other one is wrong, in range(0,A.shape[0]**2,(A.shape[0]+1)) the +1 is missed.
You can mention other answer, where is the difference, or just simply post a comment telling that others miss something.
1

If you do not mind creating a new array, then you can use list comprehension.

A = np.array([A[i][A[i] != A[i][i]] for i in range(len(A))])

Rerunning the same methods as @Divakar,

A = np.random.randint(11,99,(5000,5000))

skip_diag_masking
85.7 ms ± 1.55 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
skip_diag_broadcasting
163 ms ± 1.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
skip_diag_strided
52.5 ms ± 2.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
skip_diag_list_comp
101 ms ± 347 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Comments

1

I love all the answers here, but would like to add one in case your numpy object has more than 2-dimensions. In that case, you can use the following adjustment of Divakar's approach #1:

def remove_diag(A):
    removed = A[~np.eye(A.shape[0], dtype=bool)].reshape(A.shape[0], int(A.shape[0])-1, -1)
    return np.squeeze(removed)

Comments

-1

The other approach is to use numpy.delete(). assuming square matrix, you can use:

numpy.delete(A,range(0,A.shape[0]**2,A.shape[0])).reshape(A.shape[0],A.shape[1]-1)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.