Repository: pydata/xarray

Issue:
BUG: assigning a DataArray to a Dataset can override unrelated coordiantes
#### Code Sample, a copy-pastable example if possible

From StackOverflow: https://stackoverflow.com/questions/49947109/why-does-assigning-xr-ones-like-to-a-dataset-variable-change-unrelated-coordin
```python
import xarray as xr
import numpy as np

A, B, C = 2, 3, 4

ds = xr.Dataset()
ds.coords['source'] = (['a', 'b', 'c'], np.random.random((A, B, C)))
ds.coords['unrelated'] = (['a', 'c'], np.random.random((A, C)))

print('INITIAL:', ds['unrelated'], '\n')

# do 'ones_like' manually
ds['dest-1'] = (['a', 'b'], np.ones((A, B)))

print('AFTER dest-1:', ds['unrelated'], '\n')

ds['dest-2'] = xr.ones_like(ds['source'].isel(c=0))

print('AFTER dest-2:', ds['unrelated'], '\n')
```
Output:
```
INITIAL: <xarray.DataArray 'unrelated' (a: 2, c: 4)>
array([[0.185851, 0.962589, 0.772985, 0.570292],
       [0.905792, 0.865125, 0.412361, 0.666977]])
Coordinates:
    unrelated  (a, c) float64 0.1859 0.9626 0.773 0.5703 0.9058 0.8651 ...
Dimensions without coordinates: a, c

AFTER dest-1: <xarray.DataArray 'unrelated' (a: 2, c: 4)>
array([[0.185851, 0.962589, 0.772985, 0.570292],
       [0.905792, 0.865125, 0.412361, 0.666977]])
Coordinates:
    unrelated  (a, c) float64 0.1859 0.9626 0.773 0.5703 0.9058 0.8651 ...
Dimensions without coordinates: a, c

AFTER dest-2: <xarray.DataArray 'unrelated' (a: 2)>
array([0.185851, 0.905792])
Coordinates:
    unrelated  (a) float64 0.1859 0.9058
Dimensions without coordinates: a
```

#### Problem description

Assigning a variable shouldn't implicitly override unrelated existing coordinates.

#### Expected Output

dest-1 and dest-2 should be the same.

#### Output of ``xr.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.6.3.final.0
python-bits: 64
OS: Linux
OS-release: 4.4.111+
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

xarray: 0.10.3
pandas: 0.22.0
numpy: 1.14.2
scipy: 0.19.1
netCDF4: None
h5netcdf: None
h5py: 2.7.1
Nio: None
zarr: None
bottleneck: None
cyordereddict: None
dask: None
distributed: None
matplotlib: 2.1.2
cartopy: None
seaborn: 0.8.1
setuptools: 39.0.1
pip: 9.0.3
conda: None
pytest: None
IPython: 5.5.0
sphinx: None
</details>


Discussion:
fujiisoup: The test on windows 32bit is failing (#2094).
Maybe I should wait for #1252 ?


Patch:
diff --git a/.travis.yml b/.travis.yml
index 5a9bea81..e375f6fb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -101,7 +101,6 @@ install:
   - python xarray/util/print_versions.py
 
 script:
-  - flake8 -j auto xarray
   - python -OO -c "import xarray"
   - if [[ "$CONDA_ENV" == "docs" ]]; then
       conda install -c conda-forge sphinx sphinx_rtd_theme sphinx-gallery numpydoc;
diff --git a/ci/requirements-py27-windows.yml b/ci/requirements-py27-windows.yml
index 43b29210..75628747 100644
--- a/ci/requirements-py27-windows.yml
+++ b/ci/requirements-py27-windows.yml
@@ -3,7 +3,6 @@ channels:
   - conda-forge
 dependencies:
   - python=2.7
-  - cftime
   - dask
   - distributed
   - h5py
@@ -21,3 +20,5 @@ dependencies:
   - toolz
   - rasterio
   - zarr
+  - pip:
+    - cftime
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index d804fe7b..59aad702 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -40,6 +40,10 @@ Enhancements
 Bug fixes
 ~~~~~~~~~
 
+- When assigning a :py:class:`DataArray` to :py:class:`Dataset`, any conflicted
+  non-dimensional coordinates of the DataArray are now dropped.
+  (:issue:`2068`)
+  By `Keisuke Fujii <https://github.com/fujiisoup>`_.
 - Better error handling in ``open_mfdataset`` (:issue:`2077`).
   By `Stephan Hoyer <https://github.com/shoyer>`_.
 
diff --git a/xarray/core/merge.py b/xarray/core/merge.py
index 7069ca9d..8ecb2c33 100644
--- a/xarray/core/merge.py
+++ b/xarray/core/merge.py
@@ -547,6 +547,21 @@ def dataset_merge_method(dataset, other, overwrite_vars, compat, join):
 
 
 def dataset_update_method(dataset, other):
-    """Guts of the Dataset.update method"""
+    """Guts of the Dataset.update method
+
+    This drops a duplicated coordinates from `other` (GH:2068)
+    """
+    from .dataset import Dataset
+    from .dataarray import DataArray
+
+    other = other.copy()
+    for k, obj in other.items():
+        if isinstance(obj, (Dataset, DataArray)):
+            # drop duplicated coordinates
+            coord_names = [c for c in obj.coords
+                           if c not in obj.dims and c in dataset.coords]
+            if coord_names:
+                other[k] = obj.drop(*coord_names)
+
     return merge_core([dataset, other], priority_arg=1,
                       indexes=dataset.indexes)


Test patch:
diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py
index cbd1cd33..51871472 100644
--- a/xarray/tests/test_dataset.py
+++ b/xarray/tests/test_dataset.py
@@ -2327,6 +2327,45 @@ class TestDataset(TestCase):
         expected = Dataset({'x': ('y', [4, 5, 6])}, {'y': range(3)})
         assert_identical(ds, expected)
 
+    def test_setitem_with_coords(self):
+        # Regression test for GH:2068
+        ds = create_test_data()
+
+        other = DataArray(np.arange(10), dims='dim3',
+                          coords={'numbers': ('dim3', np.arange(10))})
+        expected = ds.copy()
+        expected['var3'] = other.drop('numbers')
+        actual = ds.copy()
+        actual['var3'] = other
+        assert_identical(expected, actual)
+        assert 'numbers' in other  # should not change other
+
+        # with alignment
+        other = ds['var3'].isel(dim3=slice(1, -1))
+        other['numbers'] = ('dim3', np.arange(8))
+        actual = ds.copy()
+        actual['var3'] = other
+        assert 'numbers' in other  # should not change other
+        expected = ds.copy()
+        expected['var3'] = ds['var3'].isel(dim3=slice(1, -1))
+        assert_identical(expected, actual)
+
+        # with non-duplicate coords
+        other = ds['var3'].isel(dim3=slice(1, -1))
+        other['numbers'] = ('dim3', np.arange(8))
+        other['position'] = ('dim3', np.arange(8))
+        actual = ds.copy()
+        actual['var3'] = other
+        assert 'position' in actual
+        assert 'position' in other
+
+        # assigning a coordinate-only dataarray
+        actual = ds.copy()
+        other = actual['numbers']
+        other[0] = 10
+        actual['numbers'] = other
+        assert actual['numbers'][0] == 10
+
     def test_setitem_align_new_indexes(self):
         ds = Dataset({'foo': ('x', [1, 2, 3])}, {'x': [0, 1, 2]})
         ds['bar'] = DataArray([2, 3, 4], [('x', [1, 2, 3])])