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

10

我需要创建一个二维数组,其中每一行的开头和结尾可能是不同的数字。假设每行的第一个和最后一个元素已知,而所有其他元素都根据行的长度进行插值。在一个简单的例子中,假设我想创建一个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  

1
如果你想使用pandas:pd.Series(W).apply(lambda e: np.linspace(0, e, 3)) - Zeugma
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Kartik
1
如果你想改变起始位置,可以采用同样的方法,但需要构建一个包含两个向量start和stop的df,然后再次调用apply函数,lambda参数为df.start、df.end和3。 - Zeugma
4个回答

15

这里有一种使用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]])

让我们利用多核心!

我们可以使用 numexpr 模块和多核心技术来处理大数据,提高内存效率和性能 -

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 - Zeugma
1
@Divakar 我的意思是像这个那样。 - Zeugma
这里不是在寻找循环,我对numpy的熟练程度不够,太偏向pandas了,但我想知道是否有一种方法可以在numpy中广播函数? - Zeugma
2
@Boud 嗯,有 np.apply_along_axis,但那不是为了性能而设计的。当涉及到 NumPy 数组时,广播元素才是真正提高性能的方法。 - Divakar
@Boud,Saullo对linspace的迭代是一个很好、清晰的方法,可能是我在一时冲动中会使用的方法。但我不会试图让它比必要的更花哨。对于一个大数组,Divakar的答案只比OP快2倍。 - hpaulj
感谢@Boud,由于您提供的链接,我找到了这个解决方案 - mab

8

NumPy >= 1.16.0:

现在可以将类似于数组的值提供给 np.linspace 函数中的 startstop 参数。

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

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

1

像OP这样使用linspace的假设是所有行的起始值都为0。

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

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

对于N = 3000,它比@Divaker的解决方案快得多。 我不完全确定为什么。

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

所有这些解决方案都只是对在“starts”和“stops”之间进行线性插值的想法的变化。

由于问题陈述了“每行的第一个和最后一个元素已知”,您将如何将每行的起始值和停止值纳入基于linspace的解决方案中? - Divakar

0

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接