0

I have an array of integers/indices (i.e. arr_F_idx = np.arange(0,200)) and I would like to draw 100 pairs without repetition. I am using a masked array (I transform arr_F_idx after the first draw, as shown in the code below), but it seems that numpy.random.choice still draws the masked elements.

arr_F_idx = np.arange(0,200)
draw = np.random.choice(arr_F_idx,2,replace=False)
arr2_Drawn_pairs[0,0] = draw[0]
arr2_Drawn_pairs[0,1] = draw[1]
arr_F_idx_dum1 = np.array(arr_F_idx == draw[0])
arr_F_idx = np.ma.array(arr_F_idx, mask = arr_F_idx_dum1)
arr_F_idx_dum2 = np.array(arr_F_idx == draw[1])
arr_F_idx = np.ma.array(arr_F_idx, mask = arr_F_idx_dum2)

for i in range(1,100):
    draw = np.random.choice(arr_F_idx,2,replace=False)
    arr2_Drawn_pairs[i,0] = draw[0]
    arr2_Drawn_pairs[i,1] = draw[1]
    arr_F_idx_dum1 = np.array(arr_F_idx == draw[0])
    arr_F_idx = np.ma.array(arr_F_idx, mask = arr_F_idx_dum1)
    arr_F_idx_dum2 = np.array(arr_F_idx == draw[1])
    arr_F_idx = np.ma.array(arr_F_idx, mask = arr_F_idx_dum2)

The (sample) output that I get is

arr_F_idx

masked_array(data=[--, --, --, --, 4, 5, --, --, --, --, --, 11, 12, 13,
               --, --, 16, 17, --, 19, --, 21, 22, --, 24, --, --, --,
               28, --, --, --, 32, 33, 34, --, --, 37, 38, 39, --, 41,
               --, --, --, --, --, --, 48, --, --, --, --, --, --, --,
               --, 57, 58, --, 60, --, --, 63, 64, --, --, --, --, --,
               --, --, --, 73, --, --, 76, --, 78, --, --, --, --, --,
               --, --, --, --, 88, 89, --, --, 92, --, --, --, --, --,
               98, 99, --, 101, 102, --, --, --, 106, --, --, --, --,
               111, --, --, --, --, 116, --, --, --, --, 121, --, 123,
               124, 125, --, 127, --, --, --, 131, --, --, --, 135,
               --, --, --, --, --, 141, --, --, --, --, --, --, --,
               --, --, 151, --, --, --, 155, --, --, --, 159, --, 161,
               --, --, --, 165, --, 167, --, 169, --, 171, --, --, --,
               --, 176, --, 178, 179, --, --, --, 183, --, 185, --,
               --, --, 189, 190, --, --, 193, 194, --, 196, --, 198,
               199],
         mask=[ True,  True,  True,  True, False, False,  True,  True,
                True,  True,  True, False, False, False,  True,  True,
               False, False,  True, False,  True, False, False,  True,
               False,  True,  True,  True, False,  True,  True,  True,
               False, False, False,  True,  True, False, False, False,
                True, False,  True,  True,  True,  True,  True,  True,
               False,  True,  True,  True,  True,  True,  True,  True,
                True, False, False,  True, False,  True,  True, False,
               False,  True,  True,  True,  True,  True,  True,  True,
                True, False,  True,  True, False,  True, False,  True,
                True,  True,  True,  True,  True,  True,  True,  True,
               False, False,  True,  True, False,  True,  True,  True,
                True,  True, False, False,  True, False, False,  True,
                True,  True, False,  True,  True,  True,  True, False,
                True,  True,  True,  True, False,  True,  True,  True,
                True, False,  True, False, False, False,  True, False,
                True,  True,  True, False,  True,  True,  True, False,
                True,  True,  True,  True,  True, False,  True,  True,
                True,  True,  True,  True,  True,  True,  True, False,
                True,  True,  True, False,  True,  True,  True, False,
                True, False,  True,  True,  True, False,  True, False,
                True, False,  True, False,  True,  True,  True,  True,
               False,  True, False, False,  True,  True,  True, False,
                True, False,  True,  True,  True, False, False,  True,
                True, False, False,  True, False,  True, False, False],
   fill_value=999999)

For smaller ranges it happens as well; of course for very small ranges this is not a problem, but as I have mentioned, I want to exhaust the original array to the point that all its elements will be masked. The problem seems to be that the np.random.choice somehow still draws the masked elements, even though it is not supposed to (otherwise I do not see the point of the object called masked array). I may be doing something wrong. I will appreciate help on this issue, also if there is a simpler way to make the draws of pairs without repetition across and within pairs.

Edit: In fact, the numpy random.choice draws masked elements, as can be seen in the output (e.g. number 177 is drawn twice and 191 three times):

arr2_Drawn_pairs
Out[53]: 
array([[ 20.,  49.],
   [ 35., 114.],
   [ 44.,  42.],
   [ 52., 140.],
   [191.,  59.],    191 - the first time
   [147., 144.],
   [ 74., 143.],
   [ 23.,  43.],
   [130.,   1.],
   [146., 166.],
   [ 62.,  80.],
   [ 26., 138.],
   [152.,  71.],
   [ 50.,  87.],
   [ 69.,   9.],
   [ 20.,  65.],
   [  3., 162.],
   [ 30., 104.],
   [168., 145.],
   [154.,  54.],
   [129.,   2.],
   [ 79., 170.],
   [ 14., 188.],
   [107.,  30.],
   [119., 188.],
   [139.,  94.],
   [132., 158.],
   [  0.,  69.],
   [ 47.,  27.],
   [192.,  72.],
   [181., 160.],
   [ 95., 162.],
   [ 40.,  25.],
   [107.,   8.],
   [128.,  10.],
   [  7.,  83.],
   [ 91., 173.],
   [174.,  10.],
   [134.,  82.],
   [ 67.,  52.],
   [195., 172.],
   [197.,  96.],
   [ 15., 188.],
   [184., 164.],
   [ 18., 180.],
   [ 45.,  27.],
   [ 86.,  84.],
   [ 97., 128.],
   [149.,   6.],
   [109.,  85.],
   [182.,  62.],
   [ 53.,  68.],
   [157.,  81.],
   [188.,  25.],
   [107.,  45.],
   [117.,  86.],
   [195.,  47.],
   [105., 103.],
   [ 51., 162.],
   [187., 162.],
   [ 70.,  97.],
   [ 29., 156.],
   [175., 177.],
   [  0.,  10.],
   [ 87.,  46.],
   [  1., 119.],
   [ 93.,  90.],
   [174.,  53.],
   [ 77.,  84.],
   [ 84.,  66.],
   [ 91., 186.],
   [ 83.,  59.],
   [137., 140.],
   [136., 186.],
   [100., 195.],
   [173.,  81.],
   [120., 115.],
   [ 36.,  46.],
   [112., 148.],
   [118., 103.],
   [  8., 128.],
   [ 56.,  65.],
   [158., 145.],
   [180., 122.],
   [142., 126.],
   [133.,  45.],
   [ 59., 173.],
   [110., 119.],
   [177.,  31.],   177 - the first time!
   [ 82., 158.],
   [ 53., 113.],
   [ 85., 150.],
   [126.,  94.],
   [ 61., 152.],
   [ 93.,  40.],
   [  1.,  55.],
   [ 96., 162.],
   [153., 108.],
   [163.,   9.],
   [ 75.,  50.],
   [101.,  47.],
   [178., 148.],
   [188., 183.],
   [ 69., 177.],    177 - the second time!
   [141.,  16.],
   [ 31.,  28.],
   [106., 147.],
   [ 66., 176.],
   [156.,  96.],
   [  9.,  21.],
   [139.,  57.],
   [106.,  11.],
   [ 25.,   2.],
   [152.,  69.],
   [ 34., 169.],
   [148., 191.],    191 - the second time!
   [105.,  32.],
   [187., 156.],
   [105., 191.],    191 - the third time!
   [ 53., 128.],
   [ 56.,  30.],
   [176.,   7.],
   [168., 150.],
   [ 48., 101.],
   [105., 167.]])

Edit 2: A brute-force method for obtaining my desired result is perhaps creating a new array to sample from after every draw,

arr_F_idx_iter = np.append(arr_F_idx[0:draw[0]],arr_F_idx[draw[0]+1:199])

but I think the question of whether this can be done efficiently with masked arrays is still legitimate, as it would be quicker, maybe also points out a flaw in how masked arrays work.

Michael Szczesny
  • 4,911
  • 5
  • 15
  • 32
Hmn
  • 9
  • 2
  • 2
    Doesn't shuffling the array and `reshape(-1,2)` work: `arr = np.arange(200); np.random.shuffle(arr); arr.reshape(-1,2)`? It would be much easier to follow with a **minimal** example. – Michael Szczesny Jul 01 '22 at 10:30
  • Does this answer your question? [Random indices from masked array](https://stackoverflow.com/questions/34077864/random-indices-from-masked-array) – Michael Szczesny Jul 01 '22 at 10:38
  • For reasons that I have not mentioned above, I do not want to generate pairs simultaneously. – Hmn Jul 01 '22 at 10:59
  • Please include this requirement in the example, the included code will generate exactly the same result as shuffle, reshape. This looks like a [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Michael Szczesny Jul 01 '22 at 11:11
  • `numpy` functions don't have special "knowledge" about the masked array structure. As a default they will just use the `data` attribute (all values). The exceptions are `np.ma...` functions, and functions that delegate the action to `ma` methods. – hpaulj Jul 01 '22 at 14:58

1 Answers1

0

A simple 1d masked array:

In [28]: m = np.ma.masked_array(np.arange(10), mask=np.random.randint(0,2,10))    
In [29]: m
Out[29]: 
masked_array(data=[--, --, 2, 3, --, --, 6, 7, --, --],
             mask=[ True,  True, False, False,  True,  True, False, False,
                    True,  True],
       fill_value=999999)

choice, without special ma knowledge, draws from the data attribute:

In [31]: np.random.choice(m,3, replace=False)
Out[31]: array([4, 3, 8])

In [32]: np.random.choice(m.data,3, replace=False)
Out[32]: array([1, 8, 5])

If you want it to draw from the unmasked elements, you need to give it such an array, compressed:

In [33]: np.random.choice(m.compressed(),3, replace=False)
Out[33]: array([2, 6, 3])

In general np functions don't work correctly on masked arrays. That's why there's a large set of np.ma functions (and ma methods). Often those functions use the compressed to get the unmasked values. Or they replace masked values with some "innocent" fill.

In [35]: m.data
Out[35]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [36]: m.compressed()
Out[36]: array([2, 3, 6, 7])

In [37]: m.filled()
Out[37]: 
array([999999, 999999,      2,      3, 999999, 999999,      6,      7,
       999999, 999999])

In [38]: m.filled(0)
Out[38]: array([0, 0, 2, 3, 0, 0, 6, 7, 0, 0])
hpaulj
  • 221,503
  • 14
  • 230
  • 353