Vectorized NumPy linspace for multiple start and stop values Vectorized NumPy linspace for multiple start and stop values numpy numpy

Vectorized NumPy linspace for multiple start and stop values


Here's an approach using broadcasting -

def create_ranges(start, stop, N, endpoint=True):    if endpoint==1:        divisor = N-1    else:        divisor = N    steps = (1.0/divisor) * (stop - start)    return steps[:,None]*np.arange(N) + start[:,None]

Sample run -

In [22]: # Setup start, stop for each row and no. of elems in each row    ...: start = np.array([1,4,2])    ...: stop  = np.array([6,7,6])    ...: N = 5    ...: In [23]: create_ranges(start, stop, 5)Out[23]: array([[ 1.  ,  2.25,  3.5 ,  4.75,  6.  ],       [ 4.  ,  4.75,  5.5 ,  6.25,  7.  ],       [ 2.  ,  3.  ,  4.  ,  5.  ,  6.  ]])In [24]: create_ranges(start, stop, 5, endpoint=False)Out[24]: array([[ 1. ,  2. ,  3. ,  4. ,  5. ],       [ 4. ,  4.6,  5.2,  5.8,  6.4],       [ 2. ,  2.8,  3.6,  4.4,  5.2]])

Let's leverage multi-core!

We can leverage multi-core with numexpr module for large data and to gain memory efficiency and hence performance -

import numexpr as nedef create_ranges_numexpr(start, stop, N, endpoint=True):    if endpoint==1:        divisor = N-1    else:        divisor = N    s0 = start[:,None]    s1 = stop[:,None]    r = np.arange(N)    return ne.evaluate('((1.0/divisor) * (s1 - s0))*r + s0')


NumPy >= 1.16.0:

It is now possible to supply array-like values to start and stop parameters of the np.linspace.

For the example given in the question the syntax would be:

>>> np.linspace((0, 0, 0), (2, 4, 6), 3, axis=1)array([[0., 1., 2.],       [0., 2., 4.],       [0., 3., 6.]])

New axis parameter specifies in which direction data will be generated. By default it is 0:

>>> np.linspace((0, 0, 0), (2, 4, 6), 3)array([[0., 0., 0.],       [1., 2., 3.],       [2., 4., 6.]])


Like the OP's this use of linspace assumes the start is 0 for all rows.

x=np.linspace(0,1,N)[:,None]*np.arange(0,2*N,2)

(edit - this is the transpose of what I should get; either transpose it or switch the use of [:,None])

For N=3000, it's noticeably faster than @Divaker's solution. I'm not entirely sure why.

In [132]: timeit N=3000;x=np.linspace(0,1,N)[:,None]*np.arange(0,2*N,2)10 loops, best of 3: 91.7 ms per loopIn [133]: timeit create_ranges(np.zeros(N),np.arange(0,2*N,2),N)1 loop, best of 3: 197 ms per loopIn [134]: def foo(N):     ...:     D=np.ones((N,N))*np.arange(N)     ...:     D=D/D[:,-1]     ...:     W=np.arange(0,2*N,2)     ...:     return (D.T*W).T     ...: In [135]: timeit foo(3000)1 loop, best of 3: 454 ms per loop

============

With starts and stops I could use:

In [201]: starts=np.array([1,4,2]); stops=np.array([6,7,8])In [202]: x=(np.linspace(0,1,5)[:,None]*(stops-starts)+starts).TIn [203]: xOut[203]: array([[ 1.  ,  2.25,  3.5 ,  4.75,  6.  ],       [ 4.  ,  4.75,  5.5 ,  6.25,  7.  ],       [ 2.  ,  3.5 ,  5.  ,  6.5 ,  8.  ]])

With the extra calculations that makes it a bit slower than create_ranges.

In [208]: timeit N=3000;starts=np.zeros(N);stops=np.arange(0,2*N,2);x=(np.linspace(0,1,N)[:,None]*(stops-starts)+starts).T1 loop, best of 3: 227 ms per loop

All these solutions are just variations the idea of doing a linear interpolation between the starts and stops.