查找numpy数组中相同值序列的长度(运行长度编码)

在一个pylab程序(也可能是一个matlab程序),我有一个代表距离的数字numpy数组: d[t]是在时间t距离 (和我的数据的时间是len(d)时间单位) 。

我感兴趣的事件是距离低于某个阈值时,我想计算这些事件的持续时间。 很容易得到b = d<threshold的布尔数组,并且问题归结为计算b只包含真值的字的长度序列。 但我不知道如何有效地做到这一点(即使用numpy原语),我走了arrays,并做手动变化检测(即初始化计数器时,从False值为真,增加计数器,只要值为真当值返回到False时,将计数器输出到序列)。 但是这是非常缓慢的。

如何有效地检测numpy数组中的那种序列?

下面是一些说明我的问题的Python代码:第四个点需要很长时间才能出现(如果不是,则增加数组的大小)

 from pylab import * threshold = 7 print '.' d = 10*rand(10000000) print '.' b = d<threshold print '.' durations=[] for i in xrange(len(b)): if b[i] and (i==0 or not b[i-1]): counter=1 if i>0 and b[i-1] and b[i]: counter+=1 if (b[i-1] and not b[i]) or i==len(b)-1: durations.append(counter) print '.' 

itertools函数虽然不是普通的原始函数,但通常速度非常快,所以请尝试一下(当然, itertools测量包括这个在内的各种解决scheme的时间):

 def runs_of_ones(bits): for bit, group in itertools.groupby(bits): if bit: yield sum(group) 

如果你确实需要列表中的值,当然可以使用list(runs_of_ones(bits)), 但也许列表的理解可能会稍微快一点:

 def runs_of_ones_list(bits): return [sum(g) for b, g in itertools.groupby(bits) if b] 

转向“numpy-native”的可能性,那么:

 def runs_of_ones_array(bits): # make sure all runs of ones are well-bounded bounded = numpy.hstack(([0], bits, [0])) # get 1 at run starts and -1 at run ends difs = numpy.diff(bounded) run_starts, = numpy.where(difs > 0) run_ends, = numpy.where(difs < 0) return run_ends - run_starts 

再次说明一下:确保在现实中为您解决问题的方法。

完全numpyvector化和通用RLE的任何数组(与string,布尔等工作)。

输出游程长度,起始位置和值的元组。

 import numpy as np def rle(inarray): """ run length encoding. Partial credit to R rle function. Multi datatype arrays catered for including non Numpy returns: tuple (runlengths, startpositions, values) """ ia = np.array(inarray) # force numpy n = len(ia) if n == 0: return (None, None, None) else: y = np.array(ia[1:] != ia[:-1]) # pairwise unequal (string safe) i = np.append(np.where(y), n - 1) # must include last element posi z = np.diff(np.append(-1, i)) # run lengths p = np.cumsum(np.append(0, z))[:-1] # positions return(z, p, ia[i]) 

很快(i7):

 xx = np.random.randint(0, 5, 1000000) %timeit yy = rle(xx) 100 loops, best of 3: 18.6 ms per loop 

多种数据types:

 rle([True, True, True, False, True, False, False]) Out[8]: (array([3, 1, 1, 2]), array([0, 3, 4, 5]), array([ True, False, True, False], dtype=bool)) rle(np.array([5, 4, 4, 4, 4, 0, 0])) Out[9]: (array([1, 4, 2]), array([0, 1, 5]), array([5, 4, 0])) rle(["hello", "hello", "my", "friend", "okay", "okay", "bye"]) Out[10]: (array([2, 1, 1, 2, 1]), array([0, 2, 3, 4, 6]), array(['hello', 'my', 'friend', 'okay', 'bye'], dtype='|S6')) 

与上面的Alex Martelli相同的结果:

 xx = np.random.randint(0, 2, 20) xx Out[60]: array([1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1]) am = runs_of_ones_array(xx) tb = rle(xx) am Out[63]: array([4, 5, 2, 5]) tb[0][tb[2] == 1] Out[64]: array([4, 5, 2, 5]) %timeit runs_of_ones_array(xx) 10000 loops, best of 3: 28.5 µs per loop %timeit rle(xx) 10000 loops, best of 3: 38.2 µs per loop 

稍微慢于Alex(但还是非常快),而且更灵活。

为了万一有人好奇(因为你提到过MATLAB),下面是在MATLAB中解决这个问题的一种方法:

 threshold = 7; d = 10*rand(1,100000); % Sample data b = diff([false (d < threshold) false]); durations = find(b == -1)-find(b == 1); 

我对Python不太熟悉,但也许这可能会给你一些想法。 =)

这是一个只使用数组的解决scheme:它需要一个包含一系列bools的数组并计算转换的长度。

 >>> from numpy import array, arange >>> b = array([0,0,0,1,1,1,0,0,0,1,1,1,1,0,0], dtype=bool) >>> sw = (b[:-1] ^ b[1:]); print sw [False False True False False True False False True False False False True False] >>> isw = arange(len(sw))[sw]; print isw [ 2 5 8 12] >>> lens = isw[1::2] - isw[::2]; print lens [3 4] 

sw包含switch的情况下包含true, isw将它们转换为索引。 然后将isw的项目在lens两两相减。

请注意,如果序列以1开头,则会计算0序列的长度:这可以在计算透镜的索引中进行修正。 另外,我还没有testing过这样的长度为1的情况。


全function,返回所有True基元的开始位置和长度。

 import numpy as np def count_adjacent_true(arr): assert len(arr.shape) == 1 assert arr.dtype == np.bool if arr.size == 0: return np.empty(0, dtype=int), np.empty(0, dtype=int) sw = np.insert(arr[1:] ^ arr[:-1], [0, arr.shape[0]-1], values=True) swi = np.arange(sw.shape[0])[sw] offset = 0 if arr[0] else 1 lengths = swi[offset+1::2] - swi[offset:-1:2] return swi[offset:-1:2], lengths 

testing不同的布尔1D数组(空arrays;单/多个元素;偶数/奇数长度;以True / False开头;只有True / False元素)。

 durations = [] counter = 0 for bool in b: if bool: counter += 1 elif counter > 0: durations.append(counter) counter = 0 if counter > 0: durations.append(counter)