Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to efficiently concatenate many arange calls in numpy?

I'd like to vectorize calls like numpy.arange(0, cnt_i) over a vector of cnt values and concatenate the results like this snippet:

import numpy
cnts = [1,2,3]
numpy.concatenate([numpy.arange(cnt) for cnt in cnts])

array([0, 0, 1, 0, 1, 2])

Unfortunately the code above is very memory inefficient due to the temporary arrays and list comprehension looping.

Is there a way to do this more efficiently in numpy?

like image 869
Joseph Hastings Avatar asked Nov 17 '13 06:11

Joseph Hastings


People also ask

Is NumPy concatenate slow?

Your answer is certainly faster than the method given in the question, but much slower than the best practices for numpy. It really is a clever way to merge pairs of arrays in the minimum number of operations, but concatenate accepts lists of any length so you aren't limited to pairs.

What is the difference between append and concatenate in NumPy?

append() and np. concatenate(). The append method will add an item to the end of an array and the Concatenation function will allow us to add two arrays together. In concatenate function the input can be any dimension while in the append function all input must be of the same dimension.

What is a correct method to join two or more arrays in NumPy?

You can use the numpy. concatenate() function to concat, merge, or join a sequence of two or multiple arrays into a single NumPy array. Concatenation refers to putting the contents of two or more arrays in a single array.


3 Answers

Here's a completely vectorized function:

def multirange(counts):
    counts = np.asarray(counts)
    # Remove the following line if counts is always strictly positive.
    counts = counts[counts != 0]

    counts1 = counts[:-1]
    reset_index = np.cumsum(counts1)

    incr = np.ones(counts.sum(), dtype=int)
    incr[0] = 0
    incr[reset_index] = 1 - counts1

    # Reuse the incr array for the final result.
    incr.cumsum(out=incr)
    return incr

Here's a variation of @Developer's answer that only calls arange once:

def multirange_loop(counts):
    counts = np.asarray(counts)
    ranges = np.empty(counts.sum(), dtype=int)
    seq = np.arange(counts.max())
    starts = np.zeros(len(counts), dtype=int)
    starts[1:] = np.cumsum(counts[:-1])
    for start, count in zip(starts, counts):
        ranges[start:start + count] = seq[:count]
    return ranges

And here's the original version, written as a function:

def multirange_original(counts):
    ranges = np.concatenate([np.arange(count) for count in counts])
    return ranges

Demo:

In [296]: multirange_original([1,2,3])
Out[296]: array([0, 0, 1, 0, 1, 2])

In [297]: multirange_loop([1,2,3])
Out[297]: array([0, 0, 1, 0, 1, 2])

In [298]: multirange([1,2,3])
Out[298]: array([0, 0, 1, 0, 1, 2])

Compare timing using a larger array of counts:

In [299]: counts = np.random.randint(1, 50, size=50)

In [300]: %timeit multirange_original(counts)
10000 loops, best of 3: 114 µs per loop

In [301]: %timeit multirange_loop(counts)
10000 loops, best of 3: 76.2 µs per loop

In [302]: %timeit multirange(counts)
10000 loops, best of 3: 26.4 µs per loop
like image 120
Warren Weckesser Avatar answered Oct 19 '22 22:10

Warren Weckesser


Try the following for solving memory problem, efficiency is almost the same.

out = np.empty((sum(cnts)))
k = 0
for cnt in cnts:
    out[k:k+cnt] = np.arange(cnt)
    k += cnt

so no concatenation is used.

like image 3
Developer Avatar answered Oct 19 '22 21:10

Developer


np.tril_indices pretty much does this for you:

In [28]: def f(c):
   ....:     return np.tril_indices(c, -1)[1]

In [29]: f(10)
Out[29]:
array([0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 0, 1,
       2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 8])

In [33]: %timeit multirange(range(10))
10000 loops, best of 3: 93.2 us per loop

In [34]: %timeit f(10)
10000 loops, best of 3: 68.5 us per loop

much faster than @Warren Weckesser multirange when the dimension is small.

But becomes much slower when the dimension is larger (@hpaulj, you have a very good point):

In [36]: %timeit multirange(range(1000))
100 loops, best of 3: 5.62 ms per loop

In [37]: %timeit f(1000)
10 loops, best of 3: 68.6 ms per loop
like image 1
CT Zhu Avatar answered Oct 19 '22 22:10

CT Zhu