Repository: pydata/xarray
Issue: BUG: reindex method creates new dims/coords

If you reindex an xray dataset or dataarray with a coordinate with a different name, an extra dimension and coordinate is added to the xray object (with the name of the reindexing coordinate). Here's a simple example:

```
x1 = xray.DataArray(np.random.randn(5, 6, 7), dims=["lon", "lat", "time"])
time2 = xray.DataArray([1, 3], dims="time2")
x2 = x1.reindex(time=time2)
```

x2 now has coordinates "time" and "time2".

I'm using xray version 0.3.1-4-gee1369f.


Changed files:
xray/core/coordinates.py, xray/test/test_dataset.py, xray/test/test_dataarray.py, xray/core/alignment.py, xray/core/variable.py, xray/core/utils.py, xray/core/dataset.py, xray/core/ops.py

diff --git a/xray/core/alignment.py b/xray/core/alignment.py
index 37c3aa1ef..44355e8b3 100644
--- a/xray/core/alignment.py
+++ b/xray/core/alignment.py
@@ -138,12 +138,9 @@ def reindex_variables(variables, indexes, indexers, copy=True):
     reindexed = OrderedDict()
     for name, var in iteritems(variables):
         if name in indexers:
-            new_var = indexers[name]
-            if not hasattr(new_var, 'dims') or not hasattr(new_var, 'values'):
-                new_var = Coordinate(var.dims, new_var, var.attrs,
-                                     var.encoding)
-            elif copy:
-                new_var = as_variable(new_var).copy()
+            # no need to copy, because index data is immutable
+            new_var = Coordinate(var.dims, indexers[name], var.attrs,
+                                 var.encoding)
         else:
             assign_to = var_indexers(var, to_indexers)
             assign_from = var_indexers(var, from_indexers)
@@ -156,6 +153,7 @@ def reindex_variables(variables, indexes, indexers, copy=True):
                 data = np.empty(shape, dtype=dtype)
                 data[:] = fill_value
                 # create a new Variable so we can use orthogonal indexing
+                # use fastpath=True to avoid dtype inference
                 new_var = Variable(var.dims, data, var.attrs, fastpath=True)
                 new_var[assign_to] = var[assign_from].values
             elif any_not_full_slices(assign_from):
diff --git a/xray/core/coordinates.py b/xray/core/coordinates.py
index e18eae4cf..bbdb49013 100644
--- a/xray/core/coordinates.py
+++ b/xray/core/coordinates.py
@@ -1,5 +1,6 @@
 from collections import Mapping
 from contextlib import contextmanager
+import pandas as pd
 
 from .pycompat import iteritems, basestring
 from . import formatting
@@ -64,8 +65,7 @@ class AbstractCoordinates(Mapping):
         if ordered_dims is None:
             ordered_dims = self.dims
         indexes = [self._dataset._arrays[k].to_index() for k in ordered_dims]
-        return utils.multi_index_from_product(indexes,
-                                              names=list(ordered_dims))
+        return pd.MultiIndex.from_product(indexes, names=list(ordered_dims))
 
     def _merge_validate(self, other):
         """Determine conflicting variables to be dropped from either self or
diff --git a/xray/core/dataset.py b/xray/core/dataset.py
index d725696a2..5fdf5e3d3 100644
--- a/xray/core/dataset.py
+++ b/xray/core/dataset.py
@@ -18,8 +18,7 @@ from . import alignment
 from . import formatting
 from .. import backends, conventions
 from .coordinates import DatasetCoordinates, Indexes
-from .utils import (Frozen, SortedKeysDict, ChainMap,
-                    multi_index_from_product)
+from .utils import Frozen, SortedKeysDict, ChainMap
 from .pycompat import iteritems, itervalues, basestring, OrderedDict
 
 
@@ -1591,7 +1590,7 @@ class Dataset(Mapping, common.ImplementsDatasetReduce):
         if hasattr(idx, 'levels'):
             # it's a multi-index
             # expand the DataFrame to include the product of all levels
-            full_idx = multi_index_from_product(idx.levels, idx.names)
+            full_idx = pd.MultiIndex.from_product(idx.levels, names=idx.names)
             dataframe = dataframe.reindex(full_idx)
             dims = [name if name is not None else 'level_%i' % n
                     for n, name in enumerate(idx.names)]
diff --git a/xray/core/ops.py b/xray/core/ops.py
index 63320ca54..24953803c 100644
--- a/xray/core/ops.py
+++ b/xray/core/ops.py
@@ -83,9 +83,7 @@ _REDUCE_DOCSTRING_TEMPLATE = \\
 
 
 def count(self, axis=None):
-    nulls = np.asarray(utils.isnull(self))
-    not_nulls = np.logical_not(nulls, nulls)
-    return np.sum(not_nulls, axis=axis)
+    return np.sum(~pd.isnull(self), axis=axis)
 
 
 def inject_reduce_methods(cls):
diff --git a/xray/core/utils.py b/xray/core/utils.py
index f0d6584b4..8e6680f3b 100644
--- a/xray/core/utils.py
+++ b/xray/core/utils.py
@@ -44,30 +44,13 @@ def allclose_or_equiv(arr1, arr2, rtol=1e-5, atol=1e-8):
     return np.isclose(arr1, arr2, rtol=rtol, atol=atol, equal_nan=True).all()
 
 
-def isnull(obj):
-    """Like pd.isnull, but with a bug-fix for 0d object arrays.
-    """
-    obj = np.asarray(obj)
-    if obj.ndim == 0:
-        # work around for pd.isnull not working for 0-dimensional object
-        # arrays: https://github.com/pydata/pandas/pull/7176 (should be fixed
-        # in pandas 0.14)
-        # use .item() instead of keeping around 0-dimensional arrays to avoid
-        # the numpy quirk where object arrays are checked as equal by identity
-        # (hence NaN in an object array is equal to itself):
-        obj = obj.item()
-        return obj != obj
-    else:
-        return pd.isnull(obj)
-
-
 def array_equiv(arr1, arr2):
     """Like np.array_equal, but also allows values to be NaN in both arrays
     """
     arr1, arr2 = np.asarray(arr1), np.asarray(arr2)
     if arr1.shape != arr2.shape:
         return False
-    return ((arr1 == arr2) | (isnull(arr1) & isnull(arr2))).all()
+    return ((arr1 == arr2) | (pd.isnull(arr1) & pd.isnull(arr2))).all()
 
 
 def as_shape(array, shape):
@@ -113,15 +96,6 @@ def safe_cast_to_index(array):
     return index
 
 
-def multi_index_from_product(iterables, names=None):
-    """Like pandas.MultiIndex.from_product, but with a bug fix.
-    """
-    # fixed in 0.14: https://github.com/pydata/pandas/issues/6439
-    # note: pd.MultiIndex.from_product is new in pandas-0.13.1
-    coords = [np.asarray(v) for v in iterables]
-    return pd.MultiIndex.from_product(coords, names=names)
-
-
 def equivalent(first, second):
     """Compare two objects for equivalence (identity or equality), using
     array_equiv if either object is an ndarray
diff --git a/xray/core/variable.py b/xray/core/variable.py
index c3cf495c0..e33850e4f 100644
--- a/xray/core/variable.py
+++ b/xray/core/variable.py
@@ -192,14 +192,7 @@ class PandasIndexAdapter(utils.NDArrayMixin):
             else:
                 value = np.asarray(value, dtype=self.dtype)
         else:
-            arr = self.array[key]
-            if arr.dtype != self.array.dtype:
-                # pandas<0.14 does dtype inference when slicing:
-                # https://github.com/pydata/pandas/issues/6370
-                # To avoid this, slice values instead if necessary and accept
-                # that we will need to rebuild the index:
-                arr = self.array.values[key]
-            value = PandasIndexAdapter(arr, dtype=self.dtype)
+            value = PandasIndexAdapter(self.array[key], dtype=self.dtype)
 
         return value
 
diff --git a/xray/test/test_dataarray.py b/xray/test/test_dataarray.py
index 30f79aeea..887ff94c0 100644
--- a/xray/test/test_dataarray.py
+++ b/xray/test/test_dataarray.py
@@ -470,6 +470,12 @@ class TestDataArray(TestCase):
         expected[:2, :2] = bar
         self.assertDataArrayIdentical(bar.reindex_like(foo), expected)
 
+        # regression test for #279
+        expected = DataArray(np.random.randn(5), dims=["time"])
+        time2 = DataArray(np.arange(5), dims="time2")
+        actual = expected.reindex(time=time2)
+        self.assertDataArrayIdentical(actual, expected)
+
     def test_rename(self):
         renamed = self.dv.rename('bar')
         self.assertDatasetIdentical(
diff --git a/xray/test/test_dataset.py b/xray/test/test_dataset.py
index a1a208c71..338d82b07 100644
--- a/xray/test/test_dataset.py
+++ b/xray/test/test_dataset.py
@@ -523,6 +523,12 @@ class TestDataset(TestCase):
         actual = data.reindex(dim1=data['dim1'][:10:-1])
         self.assertDatasetIdentical(actual, expected)
 
+        # regression test for #279
+        expected = Dataset({'x': ('time', np.random.randn(5))})
+        time2 = DataArray(np.arange(5), dims="time2")
+        actual = expected.reindex(time=time2)
+        self.assertDatasetIdentical(actual, expected)
+
     def test_align(self):
         left = create_test_data()
         right = left.copy(deep=True)