Random numbers within Array in Python
Question: I'm new to Python. While reading, please mention any other suggestions
regarding ways to improve my Python code.

**Question:** How do I generate a 8xN dimensional array in Python containing
random numbers? **_The constraint is that each column of this array must
contain 8 draws without replacement from the integer set [1,8]_**. More
specifically, when N = 10, I want something like this.

    
    
    [[ 6.  2.  3.  4.  7.  5.  5.  7.  8.  4.]
     [ 1.  4.  5.  5.  4.  4.  8.  5.  7.  5.]
     [ 7.  3.  8.  8.  3.  8.  7.  3.  6.  7.]
     [ 3.  6.  7.  1.  5.  6.  2.  1.  5.  1.]
     [ 8.  1.  4.  3.  8.  2.  3.  4.  3.  3.]
     [ 5.  8.  1.  7.  1.  3.  6.  8.  1.  6.]
     [ 4.  5.  2.  6.  2.  1.  1.  6.  4.  2.]
     [ 2.  7.  6.  2.  6.  7.  4.  2.  2.  8.]]
    

To do this I use the following approach:

    
    
    import numpy.random
    import numpy
    def rand_M(N):
        M = numpy.zeros(shape = (8, N))
        for i in range (0, N):
            M[:, i] = numpy.random.choice(8, size = 8, replace = False) + 1 
        return M
    

In practice N will be ~1e7. The algorithm above is O(n) in time and it takes
roughly .38 secs when N=1e3. The time therefore when N = 1e7 is ~1hr (i.e.
3800 secs). There has to be a much more efficient way.

Timing the function

    
    
    from timeit import Timer 
    t = Timer(lambda: rand_M(1000))
    print(t.timeit(5))
    0.3863314103162543
    


Answer: Create a random array of specified shape and then sort along the axis where
you want to keep the limits, thus giving us a vectorized and very efficient
solution. This would be based on this [`smart
answer`](http://stackoverflow.com/a/29156976/3293881) to [`MATLAB randomly
permuting columns
differently`](http://stackoverflow.com/questions/29156823/matlab-randomly-
permuting-columns-differently). Here's the implementation -

Sample run -

    
    
    In [122]: N = 10
    
    In [123]: np.argsort(np.random.rand(8,N),axis=0)+1
    Out[123]: 
    array([[7, 3, 5, 1, 1, 5, 2, 4, 1, 4],
           [8, 4, 3, 2, 2, 8, 5, 5, 6, 2],
           [1, 2, 4, 6, 5, 4, 4, 3, 4, 7],
           [5, 6, 2, 5, 8, 2, 7, 8, 5, 8],
           [2, 8, 6, 3, 4, 7, 1, 1, 2, 6],
           [6, 7, 7, 8, 6, 6, 3, 2, 7, 3],
           [4, 1, 1, 4, 3, 3, 8, 6, 8, 1],
           [3, 5, 8, 7, 7, 1, 6, 7, 3, 5]], dtype=int64)
    

Runtime tests -

    
    
    In [124]: def sortbased_rand8(N):
         ...:     return np.argsort(np.random.rand(8,N),axis=0)+1
         ...: 
         ...: def rand_M(N):
         ...:     M = np.zeros(shape = (8, N))
         ...:     for i in range (0, N):
         ...:         M[:, i] = np.random.choice(8, size = 8, replace = False) + 1 
         ...:     return M
         ...: 
    
    In [125]: N = 5000
    
    In [126]: %timeit sortbased_rand8(N)
    100 loops, best of 3: 1.95 ms per loop
    
    In [127]: %timeit rand_M(N)
    1 loops, best of 3: 233 ms per loop
    

Thus, awaits a **`120x`** speedup!

