How can I combine an array of values and mask into a matrix?

2020-05-23 python numpy matrix scipy sparse-matrix

How can I combine an array of values and mask into a matrix, which contains each value from the array exactly once, but only in places where the mask is non-zero? You could say I want to fill-in the mask-matrix with values from the array.

I don't actually care about the order of the original elements in the result.

mask = [[0, 1, 0], [1, 0, 1], [0, 1, 0]]
values = [1, 2, 3, 4]
result = some_magical_operation(mask, values)

# output: result = [[0, 1, 0], [2, 0, 3], [0, 4, 0]]

# alternative output: result2 = [[0, 2, 0], [1, 0, 3], [0, 4, 0]]
# alternative output: result3 = [[0, 4, 0], [3, 0, 2], [0, 1, 0]]

Note: my actual matrices are significantly less dense than this.

Answers

here is something that i have tried,

mask = [[0, 1, 0], [1, 0, 1], [0, 1, 0]]
val = [1, 2, 3, 4]
result=list()
k=0

for i in mask:
    for j in range(0,len(i)):
        if i[j]!=0:
            i[j]=val[k]
            k+=1
    result.append(i)
print(result)

output:

[[0, 1, 0], [2, 0, 3], [0, 4, 0]]

Some minor modification will be required to this code also:

  1. Check if val[k] does not runs out of index

I don’t know if there is a NumPy function for this, so I created my own:

import numpy as np


def some_magical_operation_ndarray(m: np.ndarray, v: np.ndarray) -> np.ndarray:
    res = np.zeros(m.shape)
    n = 0
    for i in range(m.shape[0]):
        for j in range(m.shape[1]):
            if not m[i, j] == 0:
                res[i, j] = v[n]
                n += 1
                if n == len(v):
                    n = 0
    return res


mask = np.asarray([[0, 1, 0], [1, 0, 1], [0, 1, 0]])
values = np.asarray([1, 2, 3, 4])
result = some_magical_operation_ndarray(mask, values)
print(result)

If you want to work with an list instead of an ndarray this solution would work:

def some_magical_operation_list(m: list, v: list) -> list:
    res = []
    n = 0
    for i in range(len(m)):
        res.append([])
        for j in range(len(m[i])):
            if not m[i][j] == 0:
                res[i].append(v[n])
                n += 1
                if n == len(v):
                    n = 0
            else:
                res[i].append(0)
    return res


mask = [[0, 1, 0], [1, 0, 1], [0, 1, 0]]
values = [1, 2, 3, 4]
result = some_magical_operation_list(mask, values)
print(result)
In [229]: mask = [[0, 1, 0], [1, 0, 1], [0, 1, 0]] 
     ...: values = [1, 2, 3, 4]                                                          
In [230]: mask = np.array(mask, bool)                                                    
In [231]: mask                                                                           
Out[231]: 
array([[False,  True, False],
       [ True, False,  True],
       [False,  True, False]])
In [232]: res =  np.zeros(mask.shape, int)                                               

If mask is boolean, then it can be used to index the relevant slots of res, either for fetching values, or for assigning them.

In [233]: res[mask]                                                                      
Out[233]: array([0, 0, 0, 0])

In [234]: res[mask]= values                                                              
In [235]: res                                                                            
Out[235]: 
array([[0, 1, 0],
       [2, 0, 3],
       [0, 4, 0]])

To use scipy.sparse you could do:

In [241]: from scipy import sparse                                                       

Make a sparse matrix from mask:

In [243]: M = sparse.csr_matrix(mask, dtype=int)                                         
In [244]: M                                                                              
Out[244]: 
<3x3 sparse matrix of type '<class 'numpy.longlong'>'
    with 4 stored elements in Compressed Sparse Row format>
In [245]: M.A                                                                            
Out[245]: 
array([[0, 1, 0],
       [1, 0, 1],
       [0, 1, 0]], dtype=int64)

and then replace its data values with values:

In [246]: M.data                                                                         
Out[246]: array([1, 1, 1, 1], dtype=int64)                                                         
In [250]: M.data[:] = values                                                             
In [251]: M.A                                                                            
Out[251]: 
array([[0, 1, 0],
       [2, 0, 3],
       [0, 4, 0]], dtype=int64)

Try this:

_mask = np.nonzero(mask)
mask[_mask] = values

Should work exactly the way you ask for.

Example with a test set:

mask = np.random.randint(0, 2, (7,3))
mask[_mask] = np.arange(1, _mask[0].shape[0] + 1)    # the RHS is a test equivalent to 'values'

Result:

>>> mask (original):
array([[0, 0, 1],
       [1, 0, 0],
       [1, 1, 1],
       [0, 1, 1],
       [0, 1, 1],
       [0, 1, 0],
       [0, 1, 1]])

>>> mask (modified):
array([[ 0,  0,  1],
       [ 2,  0,  0],
       [ 3,  4,  5],
       [ 0,  6,  7],
       [ 0,  8,  9],
       [ 0, 10,  0],
       [ 0, 11, 12]])

Make a copy of mask and use _mask on the copied array if you do not wish to modify mask itself.

Related