Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficiently sum a small numpy array, broadcast across a ginormous numpy array?

I want to calculate an indexed weight sum across a large (1,000,000 x 3,000) boolean numpy array. The large boolean array changes infrequently, but the weights come at query time, and I need answers very fast, without copying the whole large array, or expanding the small weight array to the size of the large array.

The result should be an array with 1,000,000 entries, each having the sum of the weights array entries corresponding to that row's True values.

I looked into using masked arrays, but they seem to require building a weights array the size of my large boolean array.

The code below gives the correct results, but I can't afford that copy during the multiply step. The multiply isn't even necessary, since the values array is boolean, but at least it handles the broadcasting properly.

I'm new to numpy, and loving it, but I'm about to give up on it for this particular problem. I've learned enough numpy to know to stay away from anything that loops in python.

My next step will be to write this routine in C (which has the added benefit of letting me save memory by using bits instead of bytes, by the way.)

Unless one of you numpy gurus can save me from cython?

from numpy import array, multiply, sum

# Construct an example values array, alternating True and False.
# This represents four records of three attributes each:
#    array([[False,  True, False],
#           [ True, False,  True],
#           [False,  True, False],
#           [ True, False,  True]], dtype=bool)
values = array([(x % 2) for x in range(12)], dtype=bool).reshape((4,3))

# Construct example weights, one for each attribute:
#    array([1, 2, 3])
weights = array(range(1, 4))

# Create expensive NEW array with the weights for the True attributes.
# Broadcast the weights array into the values array.
#    array([[0, 2, 0],
#           [1, 0, 3],
#           [0, 2, 0],
#           [1, 0, 3]])
weighted = multiply(values, weights)

# Add up the weights:
#    array([2, 4, 2, 4])
answers = sum(weighted, axis=1)

print answers

# Rejected masked_array solution is too expensive (and oddly inverts
# the results):
masked = numpy.ma.array([[1,2,3]] * 4, mask=values)
like image 276
Jesse Montrose Avatar asked Apr 19 '12 00:04

Jesse Montrose


1 Answers

The dot product (or inner product) is what you want. It allows you to take a matrix of size m×n and a vector of length n and multiply them together yielding a vector of length m, where each entry is the weighted sum of a row of the matrix with the entries of the vector of as weights.

Numpy implements this as array1.dot(array2) (or numpy.dot(array1, array2) in older versions). e.g.:

from numpy import array

values = array([(x % 2) for x in range(12)], dtype=bool).reshape((4,3))

weights = array(range(1, 4))

answers = values.dot(weights)
print answers
# output: [ 2 4 2 4 ]

(You should benchmark this though, using the timeit module.)

like image 77
huon Avatar answered Nov 15 '22 06:11

huon