Fix isel() bugs: accept slices and boolean arrays, and do not smash input dict - #1729
Fix isel() bugs: accept slices and boolean arrays, and do not smash input dict#1729Sevans711 wants to merge 8 commits into
Conversation
also: - Improves docstrings for isel and sel to clarify indexing does not care about data location at all; it instead always selects the minimal grid containing all nodes, edges, or faces specified. - Creates a clear DimensionError for 2D+ grid dim indexers and clarifies in docstring that grid dim indexers must be 1D - Removes promise of working on non-inclusive indexing, along with NotImplementedError implementation stubs for `inclusive=False` case. - Add leading underscores for subgrid_..._indices vars which are (currently) being cached directly to Grid._ds.
This completes the solution for #1639. (Also, deletes corresponding workarounds in sel() calls, now that this issue is fixed.)
|
@Sevans711 From what I see, You can put any dimension name in isel and it preserves that name down the line (even nonsense names like I did here to make it more visible in the output), whereas it didn't keep the names before. Hopefully this helps visualize the new vs old output: Run on this branch's env
Run on an older branch I had on hand
|
Great find @dylannelson and thank you for providing this example! Indeed it was introduced due to changing the line you pointed to. I reproduced on my end, fixed, and added regression test Considering this along with issue #1712 led me to writing down the following statements (in the test's docstring), which I think are an accurate description of the desired behavior?
|


Closes #1639, closes #1728
Closes #1711
Overview
Mapping from changes in this PR to solved issues:
isel()silently mishandles boolean indexer arrays #1728 and isel() fails withsliceobject, in UxDataArray and UxDataset #1639; both were ultimately caused by the_slice_face_indicesfunction inuxarray/grid/slice.pyincorrectly attempting to convert allindicesinputs vianp.atleast_1d(np.asarray(indices, dtype=INT_DTYPE)). Thenp.asarraydoesn't make sense forsliceobjects, while thedtype=INT_DTYPEdoesn't make sense for boolean inputs..isel()modifies indexers dict, causing inconsistent behavior across repeated calls #1711 was simply to add the lineindexers = indexers.copy() # don't modify the original dictin the appropriate locations in UxDataArray.isel() and UxDataset.isel().This PR also updates isel() and sel() docstrings to clarify something I previously misunderstood. Indexing grid dimensions does not actually care about data location at all; it instead always selects the minimal grid containing all nodes, edges, or faces specified. For example, if your data lives on "n_edge", indexing by n_edge=7 will not lead to a result with only edge 7. Instead, the result would be on the minimal grid containing edge 7, which is just the two faces touching edge 7 along with all of their edges and nodes. So, the result from selecting n_edge=7 will actually contain all edges from both of those two faces. The previous docstrings were not technically wrong, they were just maybe a bit misleading about this.
Added regression tests for all issues solved in this PR, and confirmed that all new regression tests fail on main but pass here.
Minor expansions of PR scope:
selected = data.isel(timesteps=xr.DataArray([[1,3,5],[4,5,6]], dims=['experiment_name', 't_index'], coords={'timestep_group_name': ['odd_timesteps_only', 'just_4_5_6']}; selected.mean('t_index')gets the mean value of the data for each timestep group). However, supporting it for grid dimensions is not possible, due to the limit of only one Grid object attached to a given UxDataset or UxDataArray.IndexError: Unlabeled multi-dimensional array cannot be used for indexing: n_facebut that was particularly misleading if providing a well-labeled xarray.DataArray). Added a test to ensure this case raises DimensionError.inclusive=Truekwargs from the internal_slice_node_indices,_slice_edge_indices, and_slice_face_indicesfunctions. Passinginclusive=Falsewas never actually supported (it just raised NotImplementedError). This could be restored ifinclusive=Falseindexing ever gets implemented, or maybe would implement entirely separate functions instead, but in the meantime there doesn't seem to be a compelling reason to keep it.Tiny expansion of PR scope:
_ds:_subgrid_node_indices,_subgrid_face_indices, and_subgrid_edge_indices, along with comment to clarify these are only being used by_slice_from_grid, and that if multiple isel() operations are performed then these only reflect the indices from the latest one, not the indices relative to the original Grid.PR Checklist
General
Testing & Benchmarking
Documentation and Examples
docs/api.rst; internal (private) function names start with an underscore (_)AI Disclosure
AI Usage: Some Claude chats, plus GitHub Copilot's inline code suggestions