简体   繁体   中英

How can I get exactly the same amount elements replaced in numpy 2D matrix?

I got a symmetrical 2D numpy matrix, it only contains ones and zeros and diagonal elements are always 0.

I want to replace part of the elements from one to zero, and the result need to keep symmetrical too. How many elements will be selected depends on the parameter replace_rate .

Since it's a symmetrical matrix, I take half of the matrix and select the elements(those values are 1) randomly, change them from 1 to 0. And then with a mirror operation, make sure the whole matrix are still symmetrical.

For example

com = np.array ([[0, 1, 1, 1, 1],
                 [1, 0, 1, 1, 1],
                 [1, 1, 0, 1, 1],
                 [1, 1, 1, 0, 1],
                 [1, 1, 1, 1, 0]])

replace_rate = 0.1
com = np.triu(com)
mask = np.random.choice([0,1],size=(com.shape),p=((1-replace_rate),replace_rate)).astype(np.bool)
r1 = np.random.rand(*com.shape) 
com[mask] = r1[mask] 
com += com.T - np.diag(com.diagonal())  

com is a (5,5) symmetrical matrix, and 10% of elements (only include those values are 1, the diagonal elements are excluded) will be replaced to 0 randomly.

The question is, how can I make sure the amount of elements changed keep the same each time? Keep the same replace_rate = 0.1 , sometimes I will get result like:

com = np.array([[0 1 1 1 1]
                [1 0 1 1 1]
                [1 1 0 1 1]
                [1 1 1 0 1]
                [1 1 1 1 0]])

Actually no one changed this time, and if I repeat it, I got 2 elements changed:

com = np.array([[0 1 1 1 1]
                [1 0 1 1 1]
                [1 1 0 1 0]
                [1 1 1 0 1]
                [1 1 0 1 0]])

I want to know how to fix the amount of elements changed with the same replace_rate ?

Thanks in advance!!

How about something like this:

def make_transform(m, replace_rate):

    changed = []  # keep track of indices we already changed

    def get_random():
        # Get a random pair of indices which are not equal (i.e. not on the diagonal)
        c1, c2 = random.choices(range(len(com)), k=2)
        if c1 == c2 or (c1,c2) in changed or (c2,c1) in changed:
            return get_random() # Recurse until we find an i,j pair : i!=j , that hasnt already been changed
        else:
            changed.append((c1,c2))
            return c1, c2

    n_changes = int(m.shape[0]**2 * replace_rate) # the number of changes to make
    print(n_changes)
    for _ in range(n_changes):
        i, j = get_random()  # Get an valid index 
        m[i][j] = m[j][i] = 0  

    return m

This is the solution I suggest:

def rand_zero(mat, replace_rate):
    triu_mat = np.triu(mat)

    _ind = np.where(triu_mat != 0)              # gets indices of non-zero elements, not just non-diagonals
    ind = [x for x in zip(*_ind)]

    chng = np.random.choice(range(len(ind)),    # select some indices, at rate 'replace_rate'
           size = int(replace_rate*mat.size), 
           replace = False)                     # do not select duplicates

    mod_mat = triu_mat
    for c in chng:
        mod_mat[ind[c]] = 0
    mod_mat = mod_mat + mod_mat.T

    return mod_mat

I use int() to truncate to an integer in size , but you can use round() if that's what you desire.

Hope this gives consistent results!

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