2

I have two 1D Numpy arrays start and stop, both of which contain integers (which are used to index some other array). I have the following piece of code.

index_list = []
for i in range(len(start)):
    temp = range(start[i], stop[i])
    index_list.extend(temp)
index_list = np.array(index_list)

Is there a simple way to vectorize this?

5
  • You mean like np.concatenate([np.arange(frm, to) for frm, to in zip(start, stop)]) but vectorized? Commented May 16, 2014 at 15:37
  • The current implementation is pretty fast. If using python2.x, you could shave off a few 10s of nanoseconds using index_list += t, but it's currently quite fast (4us on my machine for small start/stop). How long is start? Commented May 16, 2014 at 15:39
  • 1
    Does the length of range(start[i], stop[i]) vary across rows, or is it constant? Commented May 16, 2014 at 15:41
  • Exactly. I'm sure there's an easy way to do it but I haven't seen it yet. Commented May 16, 2014 at 15:45
  • start and stop will have up to ~100k elements, and range(start[i], stop[i]) will also vary from having a length of 0 to about 10 I believe. Commented May 16, 2014 at 15:48

2 Answers 2

5

You can vectorize this as follows:

def make_index_list(start, stop):
    lens = stop - start
    cum_lens = np.cumsum(lens)
    # Sequential indices the same length as the expected output
    out = np.arange(cum_lens[-1])
    # Starting index for each section of `out`
    cum_lens = np.concatenate(([0], cum_lens[:-1]))
    # How much each section of `out` is off from the correct value
    deltas = start - out[cum_lens]
    # Apply the correction
    out += np.repeat(deltas, lens)

    return out

With some made up data:

start = np.random.randint(100, size=(100000,))
stop = start + np.random.randint(1, 10 ,size=start.shape)

We can take the code for a test ride:

In [39]: %%timeit
   ....: index_list = []
   ....: for i in range(len(start)):
   ....:     temp = range(start[i], stop[i])
   ....:     index_list.extend(temp)
   ....: index_list = np.array(index_list)
   ....:
10 loops, best of 3: 137 ms per loop

In [40]: %timeit make_index_list(start, stop)
100 loops, best of 3: 9.27 ms per loop

In [41]: np.array_equal(make_index_list(start, stop), index_list)
Out[41]: True

So it is correct and about 15x faster, not bad at all...

Sign up to request clarification or add additional context in comments.

Comments

2

If you're prepared to get your hands dirty, you can speed it up quite significantly using Cython

Original function, for reference:

import numpy as np

def original_indices(start, stop):
    index_list = []
    for i in range(len(start)):
        temp = range(start[i], stop[i])
        index_list.extend(temp)
    return np.array(index_list)

Cythonized version:

#!python
# cython: boundscheck=False
# cython: wraparound=False

import numpy as np
cimport numpy as np

def cython_indices(Py_ssize_t[:] start, Py_ssize_t[:] stop):
    cdef:
        Py_ssize_t final_size, count, ii
        Py_ssize_t[:] index_array
    final_size = 0
    for ii in range(start.shape[0]):
        final_size += stop[ii] - start[ii]
    index_array = np.empty(final_size, dtype=np.int64)
    count = 0
    for ii in range(start.shape[0]):
        idx = start[ii]
        while idx < stop[ii]:
            index_array[count] = idx
            idx += 1
            count += 1
    return index_array

Some fake data:

start = np.random.random_integers(0, 1000, size=100000)
stop = start + np.random.random_integers(0, 10, size=100000)

Some timings:

%timeit original_indices(start, stop)
# 10 loops, best of 3: 79.4 ms per loop

%timeit cython_indices(start, stop)
# 1000 loops, best of 3: 1.35 ms per loop

Cython speeds things up by an order of magnitude compared with the original version.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.