Create numpy array with random integers each row w

2019-07-26 22:52发布

问题:

i need to make fast numpy array that generates random integers in each row with a different range.

Code that works by my but is slow when i increase vectors number to 300000:

import numpy as np
import random

population_size = 4
vectors_number = population_size * 3 

add_matrix = []
for i in range(0, int(vectors_number/population_size)):
    candidates = list(range(population_size*i, population_size*(i+1))) 
    random_index = random.sample(candidates, 4)
    add_matrix.append(random_index)

winning_matrix = np.row_stack(add_matrix)
print(winning_matrix)

Each row is choose 4 random numbers from variable range.

Output:

[[ 3  0  1  2]
 [ 4  6  7  5]
 [11  9  8 10]]

Best would by create that matrix using only numpy without loops

回答1:

Here's a vectorized approach following this trick to extract unique random samples -

ncols = 4
N = int(vectors_number/population_size)
offset = np.arange(N)[:,None]*population_size
winning_matrix = np.random.rand(N,population_size).argsort(1)[:,:ncols] + offset

We can also leverage np.argpartition to replace the last step -

r = np.random.rand(N,population_size)
out = r.argpartition(ncols,axis=1)[:,:ncols] + offset

Timings -

In [63]: import numpy as np
    ...: import random
    ...: 
    ...: population_size = 64
    ...: vectors_number = population_size * 300000

In [64]: %%timeit
    ...: add_matrix = []
    ...: for i in range(0, int(vectors_number/population_size)):
    ...:     candidates = list(range(population_size*i, population_size*(i+1))) 
    ...:     random_index = random.sample(candidates, 4)
    ...:     add_matrix.append(random_index)
    ...: 
    ...: winning_matrix = np.row_stack(add_matrix)
1 loop, best of 3: 1.82 s per loop

In [65]: %%timeit
    ...: ncols = 4
    ...: N = int(vectors_number/population_size)
    ...: offset = np.arange(N)[:,None]*population_size
    ...: out = np.random.rand(N,population_size).argsort(1)[:,:ncols] + offset
1 loop, best of 3: 718 ms per loop

In [66]: %%timeit
    ...: ncols = 4
    ...: N = int(vectors_number/population_size)
    ...: offset = np.arange(N)[:,None]*population_size
    ...: r = np.random.rand(N,population_size)
    ...: out = r.argpartition(ncols,axis=1)[:,:ncols] + offset
1 loop, best of 3: 428 ms per loop


回答2:

In your case the loops can be compressed using map and list comprehensions.

winning_matrix = np.vstack ([random.sample (candidate, d2) for candidate in map (lambda i: list(range(population_size*i, population_size*(i+1))), range(0, int(vectors_number/population_size)))])

Output:

array([[ 0,  1,  3,  2],
       [ 5,  6,  4,  7],
       [11, 10,  9,  8]])

This can be broken down as

# This is your loop generating the arrays from where you are sampling
range_list = map (lambda i: list(range(population_size*i, population_size*(i+1))), range(0, int(vectors_number/population_size)))
# This does the generation of the matrix, using exactly following your method
winning_matrix = np.vstack ([random.sample (candidate, d2) for candidate in range_list])

In case of generation of random integers with different ranges (and not from a sample), you can follow the below method.

How about something like this

# Generating upper and lower bounds for each row.
pair_ranges = product (list (range (1, 5)), list (range (5, 9)))
d2 = 4
np.vstack ([np.random.random_integers (x, y, [1, d2]) for x, y in pair_ranges])

Output:

array([[2, 5, 2, 5],
       [5, 6, 2, 4],
       [1, 3, 2, 3],
       [4, 2, 4, 4],
       [2, 6, 4, 6],
       [7, 2, 6, 3],
       [4, 5, 3, 5],
       [4, 6, 3, 6],
       [3, 6, 3, 6]])

The rows will have random integers between range

array([[1, 5],
       [1, 6],
       [1, 7],
       [2, 5],
       [2, 6],
       [2, 7],
       [3, 5],
       [3, 6],
       [3, 7]])


回答3:

Since we are only choosing 4 out of 64 collisions will be rare, we can therefore draw with replacement and correct afterwards.

import numpy as np

def multiperm(y, x, factor=16, remap=False):
    draw = np.random.randint(0, factor*x, (y, x))
    idx = np.full((y, factor*x), -1, dtype=np.int8 if factor*x < 128 else int)
    yi, xi = np.ogrid[:y, :x]
    idx[yi, draw] = xi
    yd, xd = np.where(idx[yi, draw] != xi)
    while yd.size > 0:
        ndraw = np.random.randint(0, factor*x, yd.shape)
        draw[yd, xd] = ndraw
        good = idx[yd, ndraw] == -1
        idx[yd[good], ndraw[good]] = xd[good]
        good[good] = idx[yd[good], ndraw[good]] == xd[good]
        yd, xd = yd[~good], xd[~good]
    if remap:
        idx = np.zeros((y, factor*x), dtype=np.int8)
        idx[yi, draw] = 1
        idx[0, 0] -= 1
        return idx.ravel().cumsum().reshape(idx.shape)[yi, draw]
    else:
        return draw + factor*x*yi

from timeit import timeit

print(timeit("multiperm(300_000, 4)", globals=globals(), number=100)*10, 'ms')

# sanity checks
check = multiperm(300_000, 4)
print(np.all(np.arange(300_000) * 64 <= check.T) and np.all(np.arange(1, 300_001) * 64 > check.T))
print(len(set(check.ravel().tolist())) == check.size)

Sample run:

44.83660604993929 ms
True
True


回答4:

Revisiting the question with better understanding, realizing only the first random 4 of a 64 population are needed for the result, I came to this answer. There is still a loop but it is a loop over small number of required columns, it basically swaps the only the first 4 (FINALIST) columns with a a random other column:

import numpy as np

PLAYERS   = 64      # per game
GAMES     = 300000
FINALISTS = 4       # we only want to know the first four

# every player in every game has a unique id
matrix = np.arange(PLAYERS * GAMES).reshape((GAMES, PLAYERS))

games  = np.arange(GAMES)
swaps  = np.random.randint(0, PLAYERS, size=(FINALISTS, GAMES))

for i in range(FINALISTS):
    # some trickey stuff to create tuples for indexing
    dst = tuple(np.vstack([ games, i * np.ones(GAMES, dtype=np.int) ]))
    src = tuple(np.vstack([ games, swaps[i] ]))
    # do the a swap for location i 
    matrix[dst], matrix[src] = matrix[src], matrix[dst]

winning_matrix = matrix[:,:FINALISTS]
print(winning_matrix)