Repository: pydata/xarray
Issue: order matters when doing comparisons against scalar xray objects

Working on some bounding box extraction code, I computed a bounding box by taking mins and maxes of the coordinates from an xray object resulting in a dictionary of scalar xray objects. When comparing an xray DataArray against this scalar xray object, the order seems to matter. This results in problems down the road that wouldn't happen if I just had a scalar value instead of a scalar xray object.

```
>>> bbox
{'longitude': (<xray.DataArray 'longitude' ()>
array(-102.8782), <xray.DataArray 'longitude' ()>
array(-94.6244))}
>>> a = bbox['longitude'][0] <= mod['longitude']
>>> b = mod['longitude'] <= bbox['longitude'][1]
>>> c = mod['longitude'] >= bbox['longitude'][0]
>>> a
<xray.DataArray 'longitude' (longitude: 7001)>
array([False, False, False, ...,  True,  True,  True], dtype=bool)
Coordinates:
  * longitude  (longitude) bool False False False False False False False False False ...
>>> b
<xray.DataArray (longitude: 7001)>
array([ True,  True,  True, ..., False, False, False], dtype=bool)
Coordinates:
  * longitude  (longitude) float32 -129.995 -129.985 -129.975 -129.965 -129.955 ...
```

See that the "a" object has a name "longitude" while the "b" object does not. Therefore...

```
>>> a & b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/nas/home/broot/centos6/lib/python2.7/site-packages/xray/core/dataarray.py", line 850, in func
    ds = self.coords.merge(other_coords)
  File "/nas/home/broot/centos6/lib/python2.7/site-packages/xray/core/coordinates.py", line 122, in merge
    conflicts = self._merge_validate(other)
  File "/nas/home/broot/centos6/lib/python2.7/site-packages/xray/core/coordinates.py", line 80, in _merge_validate
    raise ValueError('index %r not aligned' % k)
ValueError: index 'longitude' not aligned
```

But, if I use the "c" object instead which was created flipping the comparison around:

```
>>> c * b
<xray.DataArray (longitude: 7001)>
array([False, False, False, ..., False, False, False], dtype=bool)
Coordinates:
  * longitude  (longitude) float32 -129.995 -129.985 -129.975 -129.965 -129.955 ...
>>>
```

everything works as expected. I have a vague idea of why this is happening, but I am not exactly sure how one should go about dealing with this. It is a similar problem elsewhere with subclassed numpy arrays. For now, I am going to have to go with the rule of keeping the xray dataarray object first, but that really isn't going to work in other places where I may not know that I am passing xray objects.


Changed files:
xray/test/test_dataarray.py, xray/core/dataarray.py

diff --git a/xray/core/dataarray.py b/xray/core/dataarray.py
index 8a9cf165e..acfa717a6 100644
--- a/xray/core/dataarray.py
+++ b/xray/core/dataarray.py
@@ -76,6 +76,10 @@ class _LocIndexer(object):
         self.data_array[self._remap_key(key)] = value
 
 
+# singleton object for checking
+UNNAMED = object()
+
+
 class DataArray(AbstractArray):
     """N-dimensional array with labeled coordinates and dimensions.
 
@@ -877,14 +881,29 @@ class DataArray(AbstractArray):
             return False
 
     def _result_name(self, other=None):
-        # these names are used to index a dimension, so we don't want to
-        # preserve them
-        name_is_dim = self.name in self.dims
+
+        if self.name in self.dims:
+            # these names match dimension, so if we preserve them we will also
+            # rename indexes
+            return None
+
+        if other is None:
+            # shortcut
+            return self.name
+
+        other_name = getattr(other, 'name', UNNAMED)
+        other_dims = getattr(other, 'dims', ())
+
+        if other_name in other_dims:
+            # same trouble as above
+            return None
+
         # use the same naming heuristics as pandas:
         # https://github.com/ContinuumIO/blaze/issues/458#issuecomment-51936356
-        ambiguous = (other is not None and hasattr(other, 'name')
-                     and other.name != self.name)
-        return None if name_is_dim or ambiguous else self.name
+        if other_name is UNNAMED or other_name == self.name:
+            return self.name
+
+        return None
 
     def __array_wrap__(self, obj, context=None):
         new_var = self.variable.__array_wrap__(obj, context)
diff --git a/xray/test/test_dataarray.py b/xray/test/test_dataarray.py
index 8bf5465e4..441b93141 100644
--- a/xray/test/test_dataarray.py
+++ b/xray/test/test_dataarray.py
@@ -607,6 +607,14 @@ class TestDataArray(TestCase):
         expected = DataArray(1 + np.arange(3), coords=[('x', range(3))])
         self.assertDataArrayIdentical(expected, actual)
 
+        # regression tests for #254
+        actual = orig[0] < orig
+        expected = DataArray([False, True, True], coords=[('x', range(3))])
+        self.assertDataArrayIdentical(expected, actual)
+
+        actual = orig > orig[0]
+        self.assertDataArrayIdentical(expected, actual)
+
     def test_dataset_math(self):
         # more comprehensive tests with multiple dataset variables
         obs = Dataset({'tmin': ('x', np.arange(5)),