Skip to content

Feature/2885 broadcast like #3127

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

Closed
Closed
5 changes: 5 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ Enhancements
Bug fixes
~~~~~~~~~

- Improved error handling and documentation for `.expand_dims()`
read-only view.

- Add a DataArray.broadcast_like() method.

.. _whats-new.0.12.3:

v0.12.3 (10 July 2019)
Expand Down
57 changes: 55 additions & 2 deletions xarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
utils)
from .accessor_dt import DatetimeAccessor
from .accessor_str import StringAccessor
from .alignment import align, reindex_like_indexers
from .alignment import align, broadcast, reindex_like_indexers
from .common import AbstractArray, DataWithCoords
from .coordinates import (
DataArrayCoordinates, LevelCoordinatesSource, assert_coordinate_consistent,
Expand Down Expand Up @@ -1156,6 +1156,57 @@ def interp(self, coords: Optional[Mapping[Hashable, Any]] = None,
**coords_kwargs)
return self._from_temp_dataset(ds)

def broadcast_like(self,
other: Union['DataArray', Dataset]) -> 'DataArray':
"""Broadcast a DataArray to the shape of another DataArray or Dataset

xarray objects are broadcast against each other in arithmetic
operations, so this method is not be necessary for most uses.

If no change is needed, the input data is returned to the output
without being copied.

If new coords are added by the broadcast, their values are
NaN filled.

Parameters
----------
other : Dataset or DataArray

Returns
-------
new_da: xr.DataArray

Examples
--------

>>> arr1
<xarray.DataArray (x: 2, y: 3)>
array([[0.824093, 0.769792, 0.571621],
[0.310378, 0.480418, 0.062015]])
Coordinates:
* x (x) <U1 'a' 'b'
* y (y) <U1 'a' 'b' 'c'
>>> arr2
<xarray.DataArray (x: 3, y: 2)>
array([[0.852992, 0.106589],
[0.087549, 0.563304],
[0.675744, 0.285752]])
Coordinates:
* x (x) <U1 'a' 'b' 'c'
* y (y) <U1 'a' 'b'
>>> arr1.broadcast_like(arr2)
<xarray.DataArray (x: 3, y: 3)>
array([[0.852992, 0.106589, nan],
[0.087549, 0.563304, nan],
[0.675744, 0.285752, nan]])
Coordinates:
* x (x) object 'a' 'b' 'c'
* y (y) object 'a' 'b' 'c'

"""
return broadcast(self, other)[1]

def interp_like(self, other: Union['DataArray', Dataset],
method: str = 'linear', assume_sorted: bool = False,
kwargs: Optional[Mapping[str, Any]] = None) -> 'DataArray':
Expand Down Expand Up @@ -1272,7 +1323,9 @@ def expand_dims(self, dim: Union[None, Hashable, Sequence[Hashable],
Mapping[Hashable, Any]] = None,
axis=None, **dim_kwargs: Any) -> 'DataArray':
"""Return a new object with an additional axis (or axes) inserted at
the corresponding position in the array shape.
the corresponding position in the array shape. The new object is a
view into the underlying array, not a copy.


If dim is already a scalar coordinate, it will be promoted to a 1D
coordinate consisting of a single value.
Expand Down
3 changes: 2 additions & 1 deletion xarray/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2516,7 +2516,8 @@ def swap_dims(self, dims_dict, inplace=None):

def expand_dims(self, dim=None, axis=None, **dim_kwargs):
"""Return a new object with an additional axis (or axes) inserted at
the corresponding position in the array shape.
the corresponding position in the array shape. The new object is a
view into the underlying array, not a copy.

If dim is already a scalar coordinate, it will be promoted to a 1D
coordinate consisting of a single value.
Expand Down
10 changes: 9 additions & 1 deletion xarray/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,15 @@ def __getitem__(self, key):

def __setitem__(self, key, value):
array, key = self._indexing_array_and_key(key)
array[key] = value
try:
array[key] = value
except ValueError:
# More informative exception if read-only view
if not array.flags.writeable and not array.flags.owndata:
raise ValueError("Assignment destination is a view. "
"Do you want to .copy() array first?")
else:
raise


class DaskIndexingAdapter(ExplicitlyIndexedNDArrayMixin):
Expand Down
9 changes: 9 additions & 0 deletions xarray/tests/test_dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ def test_broadcast_equals(self):
assert not a.broadcast_equals(c)
assert not c.broadcast_equals(a)

def test_broadcast_like(self):
arr1 = DataArray(np.ones((2, 3)), dims=['x', 'y'],
coords={'x': ['a', 'b'], 'y': ['a', 'b', 'c']})
arr2 = DataArray(np.ones((3, 2)), dims=['x', 'y'],
coords={'x': ['a', 'b', 'c'], 'y': ['a', 'b']})
broad1 = xr.broadcast(arr1, arr2)[1]
broad2 = arr1.broadcast_like(arr2)
assert broad1.identical(broad2)

def test_getitem(self):
# strings pull out dataarrays
assert_identical(self.dv, self.ds['foo'])
Expand Down
10 changes: 10 additions & 0 deletions xarray/tests/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ def test_indexer(data, x, expected_pos, expected_idx=None):
[True, True, True, True, False, False, False, False],
pd.MultiIndex.from_product([[1, 2], [-1, -2]]))

def test_read_only_view(self):
from collections import OrderedDict
arr = DataArray(np.random.rand(3, 3),
coords={'x': np.arange(3), 'y': np.arange(3)},
dims=('x', 'y')) # Create a 2D DataArray
arr = arr.expand_dims(OrderedDict([('z', 3)]), -1) # New dimension 'z'
arr['z'] = np.arange(3) # New coords to dimension 'z'
with pytest.raises(ValueError, match='Do you want to .copy()'):
arr.loc[0, 0, 0] = 999


class TestLazyArray:
def test_slice_slice(self):
Expand Down