Repository navigation
expand_dims() modifies numpy.ndarray.flags to write only, upon manually reverting this flag back, attempting to set a single inner value using .loc will instead set all of the inner array values #2891
Description
Activity
- changed the title
[-]expand_dims() modifies numpy.ndarray.flags to write only, when overridden setting value using .loc sets whole array[/-][+]expand_dims() modifies numpy.ndarray.flags to write only, upon manually reverting this flag back, attempting to set a single inner value using .loc will instead set the whole inner array values[/+]on Apr 13, 2019 - changed the title
[-]expand_dims() modifies numpy.ndarray.flags to write only, upon manually reverting this flag back, attempting to set a single inner value using .loc will instead set the whole inner array values[/-][+]expand_dims() modifies numpy.ndarray.flags to write only, upon manually reverting this flag back, attempting to set a single inner value using .loc will instead set all of the inner array values[/+]on Apr 13, 2019 As you've noticed, these arrays are "read only" because otherwise indexing can modify more than the original values, e.g., consider:
original = xr.DataArray(np.zeros(3), dims='x') result = original.expand_dims(y=2) result.data.flags.writeable = True result[0, 0] = 1 print(result)
Both "y" values were set to 1!
<xarray.DataArray (y: 2, x: 3)> array([[1., 0., 0.], [1., 0., 0.]]) Dimensions without coordinates: y, xThe work around is to call
.copy()on the array after callingexpand_dims(), e.g.,original = xr.DataArray(np.zeros(3), dims='x') result = original.expand_dims(y=2).copy() result[0, 0] = 1 print(result)Now the correct result is printed:
<xarray.DataArray (y: 2, x: 3)> array([[1., 0., 0.], [0., 0., 0.]]) Dimensions without coordinates: y, x
This is indeed intended behavior: by making the result read-only, we can expand dimensions without copying the original data, and without needing to worry about indexing modifying the wrong values.
That said, we could certainly improve the usability of this feature in xarray. Some options:
- Mention the work-around of using
.copy()in the error message xarray prints when an array is read-only. - Add a
copyargument toexpand_dims, so users can writecopy=Trueif they want a writeable result. - Consider changing the behavior when a dimension is inserted with size 1 to make the result writeable -- in this case, individual elements of result can be modified (which will also modify the input array). But maybe it would be surprising to users if the result of
expand_dims()is sometimes but not always writeable?
Reacted by Timothy Smith and Vikramaditya Gaonkar- Mention the work-around of using
Thank you @shoyer for taking the time to educate me on this. I understand completely now.
I agree that solutions one and two would be helpful for future developers new to Xarray and the expand_dims operation when they eventually encounter this behaviour. I also agree that option three would be confusing, and had it of been implemented as such I would still have found myself to be asking a similar question about why it is like that.
Another option to consider which might be easier still would be to just update the expand_dims documentation to include a note about this behaviour and the copy solution.
Thanks again!
OK, we would definitely welcome a pull request to improve this error message and the documentation for
expand_dims!I lean slightly against adding the
copyargument since it's just as easy to add.copy()afterwards (that's one less function argument).Yes good point. I do also think that the expand_dims interface should really only be responsible for that one single operation. If you then want to make a copy then go ahead and use that separate method afterwards.
Ok great, if I get time later today I'll see if I can't pick that up; that is if someone hasn't already done so in the meantime.
I am also affected in some code which used to work with earlier versions of xarray. In this case, I call
ds.expand_dims('new_dim')on some dataset (not DataArray), e.g.:ds = xr.Dataset({'testvar': (['x'], np.zeros(3))}) ds1 = ds.expand_dims('y').copy() ds1.testvar.data.flags C_CONTIGUOUS : True F_CONTIGUOUS : True OWNDATA : False WRITEABLE : False ALIGNED : True WRITEBACKIFCOPY : False UPDATEIFCOPY : FalseThe
.copy()workaround is not helping in this case, I am not sure how to fix this?I have just realized that
.copy(deep=True)is a possible fix for datasets.Reacted by Aritra Ghosh and Chase Dwelle
I am using the newly updated expand_dims API that was recently updated with this PR #2757. However the flag setting behaviour can also be observed using the old API syntax.
Problem description
When applying the operation 'expand_dims({'z': 3}, -1)' on a DataArray the underlying Numpy array flags are changed. 'C_CONTIGUOUS' is set to False, and 'WRITEABLE' is set to False, and 'OWNDATA' is set to False. Upon changing 'WRITEABLE' back to True, when I try to set a single value in the DataArray using the '.loc' operator it will instead set all the values in that selected inner array.
I am new to Xarray so I can't be entirely sure if this expected behaviour. Regardless I would expect that adding a new dimension to the array would not make that array 'read-only'. I would also not expect the '.loc' method to work differently to how it would otherwise.
It's also not congruent with the Numpy 'expand_dims' operation. Because when I call the operation 'np.expand_dims(np_arr, axis=-1)' the 'C_CONTIGUOUS ' and 'WRITEABLE ' flags will not be modified.
Expected Output
Here is a similar flow of operations that demonstrates the behaviour I would expect from the DataArray after applying 'expand_dims':
Output of
xr.show_versions()Details
INSTALLED VERSIONS ------------------ commit: None python: 3.6.7 (default, Dec 29 2018, 12:05:36) [GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)] python-bits: 64 OS: Darwin OS-release: 18.2.0 machine: x86_64 processor: i386 byteorder: little LC_ALL: None LANG: en_AU.UTF-8 LOCALE: en_AU.UTF-8 libhdf5: 1.10.2 libnetcdf: 4.4.1.1xarray: 0.12.1
pandas: 0.24.2
numpy: 1.16.2
scipy: 1.2.1
netCDF4: 1.5.0
pydap: None
h5netcdf: None
h5py: None
Nio: None
zarr: None
cftime: 1.0.3.4
nc_time_axis: None
PseudonetCDF: None
rasterio: None
cfgrib: 0.9.6.1.post1
iris: None
bottleneck: None
dask: None
distributed: None
matplotlib: 3.0.3
cartopy: 0.17.0
seaborn: None
setuptools: 39.0.1
pip: 10.0.1
conda: None
pytest: None
IPython: None
sphinx: None