New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: speed-up mlab.contiguous_regions using numpy #4174
Conversation
8ac9b92
to
ddc3b6a
Compare
I think you can do this more efficiently (at least for arrays with a large number of regions) by first calculating |
I think you just described what my code is doing... ;-) |
ddc3b6a
to
3f384aa
Compare
I know--I was trying to describe an implementation that is pure-numpy, without the loop hidden in the generator expression. It may be that your version is better for most real-world use cases. I don't know. |
I'm not sure I fully understand... You mean the We could also add checks to see if the call to |
boundaries.append((in_region, i)) | ||
in_region = None | ||
idx, = np.nonzero(mask[:-1] != mask[1:]) | ||
idx = np.concatenate((([0],) if mask[0] else ()) + (idx + 1,) + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you add a comment here to the effect of "prepending index 0 and appending index len(mask) if needed" (at least, that is my understanding of what it is doing. Too many parens.
In fact, perhaps you could have them be lists instead of tuples to help distinguish when I am looking at a sequence and when we are merely grouping for order of operations?
This is what I had in mind. Again, I don't know whether it is better; but it is the algorithm I normally use for this sort of thing. (I thought there was a nearly identical example already in mpl somewhere, but maybe not.) import numpy as np
def contiguous_regions(mask):
mask = np.asarray(mask, dtype=np.int8)
if mask.size == 0:
return []
if not mask.any():
return []
zero = np.zeros((1,), dtype=np.int8)
mask = np.hstack((zero, mask, zero))
diffm = np.diff(mask)
ind0 = list(np.nonzero(diffm == 1)[0])
ind1 = list(np.nonzero(diffm == -1)[0])
return(list(zip(ind0, ind1)))
masks = ([0, 0, 1, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
[1, 0, 1],
[1, 1, 1],
[1,],
[0,],
[],
)
for mask in masks:
inds = contiguous_regions(mask)
print(mask)
print(inds)
for i0, i1 in inds:
print(mask[i0:i1])
print('') |
@efiring My logic was that the index array is going to be smaller than the mask in most cases, so we will probably be better off appending and prepending to the indices than to the mask. May not always be true in a 64 bit machine with 1 byte booleans and 8 byte integers, but we probably need not worry much either way. What we are almost certainly going to be better off with is using a boolean array directly. When you do @WeatherGod I have made the whole concatenation much more explicit and readable and added some comments, let me know if you prefer it some other way. |
@jaimefrio The problem with Boolean is that on the most recent numpy, np.diff doesn't do what I need; I discovered this the hard way not too long ago. That's why I converted to int8. But I like your most recent version, which looks both efficient and readable. Elegant, even! |
@efiring, really?! crap! I think you just pointed me in the right direction On Fri, Feb 27, 2015 at 3:31 PM, Eric Firing notifications@github.com
|
Has behavior actually changed? I believe in 1.9 it simply issues a deprecation warning when you try to do boolean subtraction, but hasn't changed behavior unless you explicitly turn warnings into errors. This should be the relevant change in numpy. |
elif in_region is not None and not val: | ||
boundaries.append((in_region, i)) | ||
in_region = None | ||
# Add first and/or last index if needed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we are polishing this bikeshed: it's probably most efficient to convert idx to a list at this point. Then the prepending and/or appending can be done with a total of 4 lines, as in my example. These operations are very fast with python lists--probably faster than np.concatenate. I suspect the final indexing and zip operation would also be at least as fast if idx is already a list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lists are faster on my system for arrays of less than 10000 indices. After that arrays take over. It does seem like the more common use case, so I guess it does make sense to change it.
It's faster for arrays of less than 10000 indices.
ENH: speed-up mlab.contiguous_regions using numpy
The Travis failure is unrelated, and looks like a Travis glitch associated with building the docs. |
The docstring was asking for it, so here it goes. Added some tests as well.