<div dir="ltr"><div dir="ltr"><div dir="ltr"><div>Hi All,</div><div><br></div><div>I've been using numpy array objects to store collections of 2D (and soon ND) variables. When iterating through these collections, I often found it useful to use `ndindex`, which for `for loops` behaves much like `range` with only a `stop` parameter. <br></div><div><br></div><div>That said, it lacks a few features that are now present in `range` are missing from `ndindex`, most notably the ability to iterate over a subset of the ndindex.<br></div><div><br></div><div>I found myself often writing `itertools.product(range(1, data.shapep[0]), range(3, data.shape[2]))` for custom iterations. While it does flatten out the for loop, it is arguable less readable than having 1 or 2 levels of nested for loops.</div><div><br></div><div>It is quite possible that `nditer` would solve my problems, but unfortunately I am still not able to make sense of then numerous options it has.<br></div><div><br></div><div>I propose an `ndrange` class that can be used to iterate over nd-collections mimicking the API of `range` as much as possible and adapting it to the ND case (i.e. returning tuples instead of singletons).</div><div><br></div><div>Since this is an enhancement proposal, I am bringing the idea to the mailing list for reactions.</div><div><br></div><div>The implementation in this PR <a href="https://github.com/numpy/numpy/pull/12094">https://github.com/numpy/numpy/pull/12094</a> is based on keeping track of a tuple of python `range` range objects. The `__iter__` method returns the result of `itertools.product(*self._ranges)` <br></div><div><br></div><div>By leveraging python's `range` implementation, operations like `containement` `index`, `reversed`, `equality` and most importantly slicing of the ndrange object are possible to offer to the general numpy audiance.</div><div><br></div><div>For example, iterating through a 2D collection but avoiding indexing the first and last column used to look like this:</div><div><br></div><div>```</div><div><div>c = np.empty((4, 4), dtype=object)</div><div># ... compute on c</div><div>for j in range(c.shape[0]):</div><div>     for i in range(1, c.shape[1]-1):</div><div>         c[j, i] # = compute on c[j, i] that depends on the index i, j<br></div><div>```<br></div></div><div><br></div><div>With `np.ndrange` it can look something like this:<br></div><div><br></div><div>```<br></div><div><div>c = np.empty((4, 4), dtype=object)</div><div># ... compute on c</div><div>for i in np.ndrange(c.shape)[:, 1:-1]:</div><div>    c[i] # = some operation on c[i] that depends on the index i<br></div></div><div>```</div><div><br></div><div>very pythonic, very familiar to numpy users</div></div><div dir="ltr"><br></div><div>Thank you for the feedback,</div><div><br></div><div>Mark<br></div><div dir="ltr"><div><br></div><div>References:</div><div>An issue requesting expansion to the ndindex API on github: <a href="https://github.com/numpy/numpy/issues/6393">https://github.com/numpy/numpy/issues/6393</a><br></div></div></div></div>