用于多个开始和停止值的矢量化 NumPy linspace

Posted

技术标签:

【中文标题】用于多个开始和停止值的矢量化 NumPy linspace【英文标题】:Vectorized NumPy linspace for multiple start and stop values 【发布时间】:2017-03-30 04:41:34 【问题描述】:

我需要创建一个二维数组,其中每一行可能以不同的数字开始和结束。假设给出了每行的第一个和最后一个元素,并且所有其他元素只是根据行的长度进行插值在一个简单的情况下,假设我想创建一个 3X3 数组,该数组在 0 处具有相同的开始,但由下面的 W 给出不同的结束:

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

有没有比以下更好的方法来做到这一点:

D=np.ones((3,3))*np.arange(0,3)
D=D/D[:,-1] 
W=np.array([2,4,6]) # last element of each row assumed given
Res= (D.T*W).T  

【问题讨论】:

如果你想使用 pandas:pd.Series(W).apply(lambda e: np.linspace(0, e, 3)) 基本上你有两个向量(矩阵的第一列和最后一列),对吗?然后你想为每一行插入一些值。 @dayum 如果你想改变起始位置,它是相同的方法,但是你构建一个带有两个向量 start 和 stop 的 df,然后你再次调用 apply ,lambda 参数是 df.start , df.end, 3 【参考方案1】:

这是一种使用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]

示例运行 -

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]])

让我们利用多核!

我们可以利用multi-core with numexpr module 处理大数据并获得内存效率和性能 -

import numexpr as ne

def 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')

【讨论】:

为什么不利用linspace @Divakar 我的意思是this 这里没有搜索循环,我对numpy不是很流利,对pandas有太多偏见,但我想知道是否有办法以某种方式在numpy中广播函数? @Boud 好吧,有np.apply_along_axis,但这并不意味着性能。当涉及到 NumPy 数组时,广播元素实际上可以提供性能。 @Boud,Saullo 对linspace 的迭代是一个非常清晰的迭代,可能是我一时冲动使用的迭代。但我不会尝试让它比必要的更花哨。对于大型阵列,Divakar 的答案仅比 OP 快 2 倍。【参考方案2】:

NumPy >= 1.16.0:

现在可以为np.linspacestartstop 参数提供类似数组的值。

对于问题中给出的示例,语法为:

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

新的axis 参数指定生成数据的方向。默认是0:

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

【讨论】:

【参考方案3】:

像 OP 一样,linspace 的这种使用假设所有行的开始都是 0。

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

(编辑 - 这是我应该得到的转置;要么转置它,要么切换使用[:,None]

对于 N=3000,它明显快于 @Divaker's 解决方案。我不完全确定为什么。

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 loop
In [133]: timeit create_ranges(np.zeros(N),np.arange(0,2*N,2),N)
1 loop, best of 3: 197 ms per loop
In [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

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

我可以使用开始和停止:

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).T
In [203]: x
Out[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.  ]])

额外的计算使它比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).T
1 loop, best of 3: 227 ms per loop

所有这些解决方案只是在startsstops 之间进行线性插值的想法的变体。

【讨论】:

由于问题状态为"first and last element of each row is given",您将如何将每行的开始值和停止值合并到基于linspace 的解决方案中?【参考方案4】:

我基于@Divakar 的解决方案扩展了一些功能。它牺牲了一些速度,但现在兼容不同长度的N 而不仅仅是标量。另外,这个版本比@Saullo's sollution更快。

def create_ranges_divak(starts, stops, N, endpoint=True):
    if endpoint==1:
        divisor = N-1
    else:
        divisor = N
    steps = (1.0/divisor) * (stops - starts)
    uni_N = np.unique(N)
    if len(uni_N) == 1:
        return steps[:,None]*np.arange(uni_N) + starts[:,None]
    else:
        return [step * np.arange(n) + start for start, step, n in zip(starts, steps, N)]

【讨论】:

以上是关于用于多个开始和停止值的矢量化 NumPy linspace的主要内容,如果未能解决你的问题,请参考以下文章

使用Numpy以矢量化方式检索多个值的索引

是否可以在 numpy 中对这个计算进行矢量化?

是否可以在 Numpy 矢量化广播操作期间访问当前索引?

学习基础知识:数组和矢量计量Numpy

什么是创建 NumPy 数组的多个幂的矢量化方法?

pandas numpy 如何简化多个矢量化函数参数