Python / numpy棘手的切片问题

时间:2009-10-19 07:28:09

标签: python numpy slice

我遇到了一些问题。我需要一个numpy数组以一种不寻常的方式表现,方法是返回一个切片作为我切片的数据的视图,而不是副本。所以这是我想要做的一个例子:

假设我们有一个这样的简单数组:

a = array([1, 0, 0, 0])

我想用数组中的上一个条目更新数组中的连续条目(从左向右移动),使用如下语法:

a[1:] = a[0:3]

这将得到以下结果:

a = array([1, 1, 1, 1])

或类似的东西:

a[1:] = 2*a[:3]
# a = [1,2,4,8]

为了进一步说明,我想要以下类型的行为:

for i in range(len(a)):
    if i == 0 or i+1 == len(a): continue
    a[i+1] = a[i]

除了我想要numpy的速度。

numpy的默认行为是获取切片的副本,所以我实际得到的是:

a = array([1, 1, 0, 0])

我已将此数组作为ndarray的子​​类,因此如果需要,我可以对其进行进一步更改,我只需要右侧的切片不断更新,因为它会更新左侧的切片侧。

我是在梦想还是这种神奇可能?

更新:这都是因为我试图使用Gauss-Seidel迭代来解决线性代数问题,或多或少。这是一个涉及谐波函数的特殊情况,我试图避免进入这个因为它实际上没有必要并且可能会进一步混淆事情,但是这里有。

算法是这样的:

while not converged:
    for i in range(len(u[:,0])):
        for j in range(len(u[0,:])):
            # skip over boundary entries, i,j == 0 or len(u)
            u[i,j] = 0.25*(u[i-1,j] + u[i+1,j] + u[i, j-1] + u[i,j+1])

右?但是你可以用两种方式做到这一点,Jacobi涉及用它的邻居更新每个元素而不考虑你在while循环循环之前已经进行的更新,在循环中你要复制数组然后从复制的数组中更新一个数组。然而,Gauss-Seidel使用您已经为每个i-1和j-1条目更新的信息,因此不需要复制,循环应该基本上“知道”,因为在每个单元素更新后重新评估了数组。也就是说,每当我们调用像u [i-1,j]或u [i,j-1]这样的条目时,前一循环中计算的信息就会存在。

我想使用numpy切片用一个很好的简洁代码行替换这个缓慢而丑陋的嵌套循环情况:

u[1:-1,1:-1] = 0.25(u[:-2,1:-1] + u[2:,1:-1] + u[1:-1,:-2] + u[1:-1,2:])

但是结果是Jacobi迭代,因为当你采用切片时:u [:, - 2,1:-1]你复制了数据,因此切片不知道所做的任何更新。现在numpy还在循环吗?它不是平行的,它只是一种更快的循环方式,看起来像是python中的并行操作。我想利用这种行为通过一种黑客numpy来当我切片时返回一个指针而不是一个副本。对?然后,每次numpy循环,该片段将“更新”或真正只是复制更新中发生的任何事情。为此,我需要将数组两侧的切片作为指针。

无论如何,如果有一些非常聪明的人在那里很棒,但我几乎已经让自己相信唯一的答案就是在C中循环。

9 个答案:

答案 0 :(得分:4)

迟到的答案,但这出现在Google上,所以我可能会指出OP想要的文档。您的问题很明显:使用NumPy切片时,会创建临时图。通过快速调用weave.blitz来包装您的代码,以摆脱临时工作并获得您想要的行为。

阅读PerformancePython tutorial的weave.blitz部分了解详情。

答案 1 :(得分:2)

accumulate旨在满足您的需求;也就是说,沿阵列进行操作。这是一个例子:

from numpy import *

a = array([1,0,0,0])
a[1:] = add.accumulate(a[0:3])
# a = [1, 1, 1, 1]

b = array([1,1,1,1])
b[1:] = multiply.accumulate(2*b[0:3])
# b = [1 2 4 8]

另一种方法是将结果数组显式指定为输入数组。这是一个例子:

c = array([2,0,0,0])
multiply(c[:3], c[:3], c[1:])
# c = [  2   4  16 256]

答案 2 :(得分:1)

只需使用循环。我不能立即想到任何方法让切片操作符按照你想要它的方式运行,除了可能通过子类化numpy的array并用某些方法覆盖适当的方法有点像Python伏都教...但更重要的是,a[1:] = a[0:3]应该将a的第一个值复制到接下来的三个插槽中的想法对我来说似乎完全没有意义。我想这很容易让其他任何看过你代码的人感到困惑(至少前几次)。

答案 3 :(得分:1)

它必须与分配切片有关。但是,您可能已经知道,运营商会遵循您的预期行为:

>>> a = numpy.array([1,0,0,0])
>>> a[1:]+=a[:3]
>>> a
array([1, 1, 1, 1])

如果您的实例问题已经存在零,那么这就解决了它。否则,以增加的成本,通过乘以零或指定为零(以较快者为准)将它们设置为零

编辑: 我有另一个想法。你可能更喜欢这个:

numpy.put(a,[1,2,3],a[:3]) 

答案 4 :(得分:1)

这不是正确的逻辑。 我会尝试使用字母来解释它。

图片array = abcd,其中a,b,c,d为元素 现在,array[1:]表示位置1中的元素(从0开始)。
在这种情况下:bcdarray[0:3]表示从位置0中的字符到第三个字符(位置3-1中的字符),在这种情况下:{{1} }。

写下类似的东西:
'abc'

表示:将array[1:] = array[0:3]替换为bcd

要获得所需的输出,现在在python中,你应该使用类似的东西:

abc

答案 5 :(得分:1)

在执行setkey调用时,Numpy必须检查目标数组是否与输入数组相同。幸运的是,有很多方法可以解决它。首先,我尝试使用numpy.put代替

In [46]: a = numpy.array([1,0,0,0])

In [47]: numpy.put(a,[1,2,3],a[0:3])

In [48]: a
Out[48]: array([1, 1, 1, 1])

然后从文档中我尝试使用flatiters(a.flat

In [49]: a = numpy.array([1,0,0,0])

In [50]: a.flat[1:] = a[0:3]

In [51]: a
Out[51]: array([1, 1, 1, 1])

但这并没有解决你想到的问题

In [55]: a = np.array([1,0,0,0])

In [56]: a.flat[1:] = 2*a[0:3]

In [57]: a
Out[57]: array([1, 2, 0, 0])

这是失败的,因为乘法是在赋值之前完成的,而不是你想要的并行。

Numpy设计用于在数组中并行地重复应用完全相同的操作。要做一些更复杂的事情,除非你能找到像numpy.cumsumnumpy.cumprod这样的函数来分解它,你将不得不求助于类似scipy.weave或用C语言编写函数。 PerfomancePython页面了解更多详情。)(另外,我从未使用过编织,所以我不能保证它会做你想要的。)

答案 6 :(得分:1)

你可以看看np.lib.stride_tricks。

这些优秀幻灯片中有一些信息: http://mentat.za.net/numpy/numpy_advanced_slides/

stride_tricks从幻灯片29开始。

我对这个问题并不完全清楚,但不能提出更具体的建议 - 尽管我可能会在cython或fortran中使用f2py或编织。我现在更喜欢fortran,因为当你在cython中添加所有必需的类型注释时,我认为它看起来不像fortran那么清晰。

这里有这些方法的比较:

万维网。 SciPy的。 org / PerformancePython

(由于我是新用户,因此无法发布更多链接) 以及与您的案例类似的示例。

答案 7 :(得分:1)

最后我想出了和你一样的问题。我不得不求助于使用Jacobi迭代和织布工:

 while (iter_n < max_time_steps):
        expr = "field[1:-1, 1:-1] = (field[2:, 1:-1] "\
                                                      "+ field[:-2, 1:-1]+"\
                                                      "field[1:-1, 2:] +"\
                                                      "field[1:-1, :-2] )/4."                                       

        weave.blitz(expr, check_size=0)

         #Toroidal conditions
        field[:,0] = field[:,self.flow.n_x - 2]
        field[:,self.flow.n_x -1] = field[:,1]

        iter_n = iter_n + 1

它的工作原理很快,但不是Gauss-Seidel,所以收敛可能有点棘手。将Gauss-Seidel作为带索引的传统循环的唯一选择。

答案 8 :(得分:0)

我会建议cython而不是循环c。 可能是使用大量中间步骤让你的例子工作的一些奇特的numpy方式...但是既然你已经知道如何在c中编写它,那就快速写一下这一点作为cython功能,让cython的魔力使你的其余工作变得轻松。