I frequently want to break a 1D array into regions above and below some threshold, identifying all such subslices where the contiguous elements are above the threshold. I have two related implementations below to illustrate what I am after. The first "crossings" is rather naive in that it doesn't handle the case where an element is equal to the threshold (assuming zero for the threshold in the examples below). The second is correct (I think) but is pure python. Has anyone got a nifty numpy solution for this?
import numpy as np import matplotlib.pyplot as plt t = np.arange(0.0123, 2, 0.05) s = np.sin(2*np.pi*t)
def crossings(x): """ return a list of (above, ind0, ind1). ind0 and ind1 are regions such that the slice x[ind0:ind1]>0 when above is True and x[ind0:ind1]<0 when above is False """ N = len(x) crossings = x[:-1]*x[1:]<0 ind = np.nonzero(crossings)[0]+1 lastind = 0 data = [] for i in range(len(ind)): above = x[lastind]>0 thisind = ind[i] data.append((above, lastind, thisind)) lastind = thisind
# put the one past the end index if not already in if len(data) and data[-1]!=N-1: data.append((not data[-1][0], thisind, N)) return data
def contiguous_regions(mask): """ return a list of (ind0, ind1) such that mask[ind0:ind1].all() is True and we cover all such regions """
in_region = None boundaries = [] for i, val in enumerate(mask): if in_region is None and val: in_region = i elif in_region is not None and not val: boundaries.append((in_region, i)) in_region = None
if in_region is not None: boundaries.append((in_region, i+1)) return boundaries
fig = plt.figure() ax = fig.add_subplot(111) ax.set_title('using crossings')
ax.plot(t, s, 'o') ax.axhline(0)
for above, ind0, ind1 in crossings(s): if above: color='green' else: color = 'red' tslice = t[ind0:ind1] ax.axvspan(tslice[0], tslice[-1], facecolor=color, alpha=0.5)
fig = plt.figure() ax = fig.add_subplot(111) ax.set_title('using contiguous regions') ax.plot(t, s, 'o') ax.axhline(0)
for ind0, ind1 in contiguous_regions(s>0): tslice = t[ind0:ind1] ax.axvspan(tslice[0], tslice[-1], facecolor='green', alpha=0.5)
for ind0, ind1 in contiguous_regions(s<0): tslice = t[ind0:ind1] ax.axvspan(tslice[0], tslice[-1], facecolor='red', alpha=0.5)
plt.show()
John Hunter schrieb:
I frequently want to break a 1D array into regions above and below some threshold, identifying all such subslices where the contiguous elements are above the threshold. I have two related implementations below to illustrate what I am after. The first "crossings" is rather naive in that it doesn't handle the case where an element is equal to the threshold (assuming zero for the threshold in the examples below). The second is correct (I think) but is pure python. Has anyone got a nifty numpy solution for this?
import numpy as np import matplotlib.pyplot as plt t = np.arange(0.0123, 2, 0.05) s = np.sin(2*np.pi*t)
here my proposal, needs some polishing:
mask = (s>0).astype(int8) d = diff(mask) idx, = d.nonzero() #now handle the cases that s is above threshold at beginning or end of sequence if d[idx[0]] == -1: idx = r_[0, idx] if d[idx[-1]] == 1: idx = r_[idx, len(s)] idx.shape = (-1,2)
Gregor