迭代几个numpy数组并处理当前和以前元素的有效方法是什么?

时间:2016-08-28 01:06:36

标签: python arrays performance numpy itertools

我已经阅读了很多关于最近迭代numpy数组的不同技术,似乎共识不是迭代(例如,参见a comment here)。关于SO有几个类似的问题,但我的情况有点不同,因为我必须结合"迭代" (或不迭代)并访问先前的值。

让我们说列表float128中有N(N是小的,通常是4,可能多达7个){1}的1-D numpy数组,所有数组都是大小相同。为了给你一点见解,这些是来自PDE集成的数据,每个数组代表一个功能,我想应用Poincare部分。遗憾的是,该算法应该具有内存和时间效率,因为这些阵列有时每个大约1Gb,并且板上只有4Gb的RAM(我刚刚了解了关于numpy数组的memmap和现在的数据,现在考虑使用它们而不是常规的。)

其中一个数组用于&#34;过滤&#34;其他人,所以我从X开始。现在我必须找到secaxis = X.pop(idx)的索引对,然后将简单的代数转换应用于剩余的数组(secaxis[i-1] > 0 and secaxis[i] < 0) or (secaxis[i-1] < 0 and secaxis[i] > 0)(并保存结果)。值得一提的是,在此操作过程中不应浪费数据。

有多种方法可以做到这一点,但对我来说,它们似乎都没有效率(而且足够优雅)。一种是类似C的方法,只需迭代for循环:

X

这显然非常低效,而且不是Pythonic方式。

另一种方法是使用numpy.nditer,但我还没弄清楚如何访问前一个值,尽管它允许一次迭代多个数组:

import array # better than lists
res = [ array.array('d') for _ in X ]
for i in xrange(1,secaxis.size):
  if condition: # see above
    co = -secaxis[i-1]/secaxis[i]
    for j in xrange(N):
      res[j].append( (X[j][i-1] + co*X[j][i])/(1+co) )

第三种可能性是首先用有效的numpy切片找到所寻找的指数,然后将它们用于批量乘法/加法。我现在更喜欢这个:

# without secaxis = X.pop(idx)
it = numpy.nditer(X)
for vec in it:
  # vec[idx] is current value, how do you get the previous (or next) one?

但这似乎是在7 + 2 *(N - 1)次传递中完成的,此外,我不确定res = [] inds, = numpy.where((secaxis[:-1] < 0) * (secaxis[1:] > 0) + (secaxis[:-1] > 0) * (secaxis[1:] < 0)) coefs = -secaxis[inds] / secaxis[inds+1] # array of coefficients for f in X: # loop is done only N-1 times, that is, 3 to 6 res.append( (f[inds] + coefs*f[inds+1]) / (1+coefs) ) 类型的寻址(它不是切片,通常它必须找到所有索引的元素就像第一种方法一样,不是吗?)。

最后,我也尝试过使用itertools,它导致了巨大而模糊的结构,这可能源于我对函数式编程不太熟悉的事实:

secaxis[inds]

这不仅非常丑陋,而且还需要花费大量时间才能完成。

所以,我有以下问题:

  1. 在所有这些方法中,第三个方法确实是最好的吗?如果是这样,可以做些什么来推动最后一个呢?
  2. 还有其他更好的吗?
  3. 出于纯粹的好奇心,有没有办法用nditer解决问题?
  4. 最后,我会更好地使用numpy数组的memmap版本,还是会减慢很多东西?也许我只应该将def filt(x): return (x[0] < 0 and x[1] > 0) or (x[0] > 0 and x[1] < 0) import array from itertools import izip, tee, ifilter res = [ array.array('d') for _ in X ] iters = [iter(x) for x in X] # N-1 iterators in a list prev, curr = tee(izip(*iters)) # 2 similar iterators, each of which # consists of N-1 iterators next(curr, None) # one of them is now for current value seciter = tee(iter(secaxis)) next(seciter[1], None) for x in ifilter(filt, izip(seciter[0], seciter[1], prev, curr)): co = - x[0]/x[1] for r, p, c in zip(res, x[2], x[3]): r.append( (p+co*c) / (1+co) ) 数组加载到RAM中,将其他数据保存在磁盘上并使用第三种方法?
  5. (奖金问题)等长的1-D numpy数组的列表来自加载N secaxis个文件,这些文件的大小事先未知(但是N是)。读取一个数组,然后为一个2-D numpy数组分配内存(这里是轻微的内存开销)并将剩余的数据读入该二维数组会更有效吗?

2 个答案:

答案 0 :(得分:3)

numpy.where()版本足够快,您可以将其加速method3()。如果>条件可以更改为>=,您还可以使用method4()

import numpy as np

a = np.random.randn(100000)

def method1(a):
    idx = []
    for i in range(1, len(a)):
        if (a[i-1] > 0 and a[i] < 0) or (a[i-1] < 0 and a[i] > 0):
            idx.append(i)
    return idx

def method2(a):
    inds, = np.where((a[:-1] < 0) * (a[1:] > 0) +
                       (a[:-1] > 0) * (a[1:] < 0))
    return inds + 1

def method3(a):
    m = a < 0
    p = a > 0
    return np.where((m[:-1] & p[1:]) | (p[:-1] & m[1:]))[0] + 1

def method4(a):
    return np.where(np.diff(a >= 0))[0] + 1

assert np.allclose(method1(a), method2(a))
assert np.allclose(method2(a), method3(a))
assert np.allclose(method3(a), method4(a))

%timeit method1(a)
%timeit method2(a)
%timeit method3(a)
%timeit method4(a)

%timeit结果:

1 loop, best of 3: 294 ms per loop
1000 loops, best of 3: 1.52 ms per loop
1000 loops, best of 3: 1.38 ms per loop
1000 loops, best of 3: 1.39 ms per loop

答案 1 :(得分:3)

我需要更详细地阅读您的帖子,但会从一些一般性观察开始(来自之前的迭代问题)。

在Python中迭代数组并不是一种有效的方法,尽管有些东西可以减慢速度。我喜欢区分迭代机制("syd"nditer)和操作(for x in A:alist.append(...))。大时间消费者通常是动作,多次完成,而不是迭代机制本身。

x[i+1] += 1在编译代码中进行迭代是最快的。

numpy

快得多
 xdiff = x[1:] - x[:-1]

xdiff = np.zeros(x.shape[0]-1) for i in range(x.shape[0]: xdiff[i] = x[i+1] - x[i] 并不快。

建议将

np.nditer作为编译代码中的常规迭代工具。但它的主要价值在于处理广播和协调多个阵列(输入/输出)的迭代。你需要使用缓冲和nditer代码来从c获得最佳速度(我将查看最近的SO问题)。

https://stackoverflow.com/a/39058906/901925

请勿在未学习相关nditer教程页面(以nditer示例结尾的页面)的情况下使用iteration

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

从经验来看,这种方法将是最快的。是的,它会多次迭代cython,但这些都是在编译的代码中完成的,并且比Python中的任何迭代都要快得多。 secaxis迭代只是几次。

for f in X:

res = [] inds, = numpy.where((secaxis[:-1] < 0) * (secaxis[1:] > 0) + (secaxis[:-1] > 0) * (secaxis[1:] < 0)) coefs = -secaxis[inds] / secaxis[inds+1] # array of coefficients for f in X: res.append( (f[inds] + coefs*f[inds+1]) / (1+coefs) ) 已经探索了使@HYRY步骤更快的替代方案。但正如你所看到的那样,差异并不大。其他可能的调整

where

如果inds1 = inds+1 coefs = -secaxis[inds] / secaxis[inds1] coefs1 = coefs+1 for f in X: res.append(( f[inds] + coefs*f[inds1]) / coefs1) 是数组,X也可以是数组。

res

但对于小res = (X[:,inds] + coefs*X[:,inds1])/coefs1 我怀疑列表N同样好。不要让阵列超出必要的范围。调整很小,只是试图避免重新计算。

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

res的使用只是np.where。这实际上是两次传递数组,一次使用np.nonzero来确定它将返回多少个值,并创建返回结构(现在已知长度的数组列表)。并填写第二个循环来填写这些指数。因此,如果让操作变得简单,那么多次迭代就可以了。