Repository: pydata/xarray

Issue:
Keep attributes across operations
#### The Problem
When I have two DataArrays and I use a standard operation (`` +, - ,*, /``) the attributes vanish. I think that should not be the case. Even when using as suggested the ``set_options``

```python
import numpy as np
import xarray as xr
a = xr.DataArray(np.random.randn(3,3), dims=('x','y'), name='temp', attrs={'units':'K'})
b = xr.DataArray(np.random.randn(3,3), dims=('x','y'), name='temp', attrs={'units':'K'})
print(a)
<xarray.DataArray 'temp' (x: 3, y: 3)>
array([[ 1.207407, -1.9429  ,  3.168454],
       [-0.773912, -0.121835, -0.139538],
       [ 1.823002,  0.185846,  0.53569 ]])
Dimensions without coordinates: x, y
Attributes:
    units:    K
print(a-b)
<xarray.DataArray 'temp' (x: 3, y: 3)>
array([[ 1.280892, -1.097781,  2.150318],
       [-0.208202, -0.03856 ,  0.805856],
       [ 2.192506,  1.049181,  2.277078]])
Dimensions without coordinates: x, y

with xr.set_options(keep_attrs=True):
    print(a-b)

<xarray.DataArray 'temp' (x: 3, y: 3)>
array([[ 1.280892, -1.097781,  2.150318],
       [-0.208202, -0.03856 ,  0.805856],
       [ 2.192506,  1.049181,  2.277078]])
Dimensions without coordinates: x, y
```

#### Problem description

Attributes vanish when a *normal* operation is applied!
From docs of set_options:
``keep_attrs``: rule for whether to keep attributes on xarray
  Datasets/dataarrays after operations. Either ``True`` to always keep
  attrs, ``False`` to always discard them, or ``'default'`` to use original
  logic that attrs should only be kept in unambiguous circumstances.
  Default: ``'default'``.

#### Expected Output

The Attributes should remain. Maybe keep only attributes from the left Array ?
Please adjust or advise me.

#### Output of ``xr.show_versions()``

<details>
``
xr.show_versions()
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.6.final.0
python-bits: 64
OS: Linux
OS-release: 4.15.0-39-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

xarray: 0.11.0
pandas: 0.23.4
numpy: 1.15.4
scipy: 1.1.0
netCDF4: 1.4.2
h5netcdf: None
h5py: 2.8.0
Nio: None
zarr: None
cftime: 1.0.2.1
PseudonetCDF: None
rasterio: None
iris: None
bottleneck: 1.2.1
cyordereddict: None
dask: 0.20.2
distributed: 1.24.2
matplotlib: 3.0.1
cartopy: 0.16.0
seaborn: 0.9.0
setuptools: 40.6.2
pip: 18.1
conda: 4.5.11
pytest: 4.0.0
IPython: 7.1.1
sphinx: 1.8.2
``
</details>


Discussion:
pep8speaks: Hello @MBlaschek! Thanks for submitting the PR.

 - There are no PEP8 issues in the file [`xarray/core/variable.py`](https://github.com/MBlaschek/xarray/blob/56aeaaab2bf84d99db58aa2bc72e9fac0af81974/xarray/core/variable.py) !

 - In the file [`xarray/tests/test_variable.py`](https://github.com/MBlaschek/xarray/blob/56aeaaab2bf84d99db58aa2bc72e9fac0af81974/xarray/tests/test_variable.py), following                are the PEP8 issues :
> [Line 1547:1](https://github.com/MBlaschek/xarray/blob/56aeaaab2bf84d99db58aa2bc72e9fac0af81974/xarray/tests/test_variable.py#L1547): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 1551:51](https://github.com/MBlaschek/xarray/blob/56aeaaab2bf84d99db58aa2bc72e9fac0af81974/xarray/tests/test_variable.py#L1551): [E231](https://duckduckgo.com/?q=pep8%20E231) missing whitespace after ','
> [Line 1552:51](https://github.com/MBlaschek/xarray/blob/56aeaaab2bf84d99db58aa2bc72e9fac0af81974/xarray/tests/test_variable.py#L1552): [E231](https://duckduckgo.com/?q=pep8%20E231) missing whitespace after ','
> [Line 1561:22](https://github.com/MBlaschek/xarray/blob/56aeaaab2bf84d99db58aa2bc72e9fac0af81974/xarray/tests/test_variable.py#L1561): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 1564:1](https://github.com/MBlaschek/xarray/blob/56aeaaab2bf84d99db58aa2bc72e9fac0af81974/xarray/tests/test_variable.py#L1564): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace



dcherian: Thanks @MBlaschek. 
1. You'll need to also add a note to `doc/whats-new.rst`.
2. Looks like there's trailing whitespace in `test_variable.py` that will need to be fixed.

LGTM otherwise.

Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index d1c79953..9d4024a6 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,3 +1,5 @@
- - [ ] Closes #xxxx (remove if there is no corresponding issue, which should only be the case for minor changes)
- - [ ] Tests added (for all bug fixes or enhancements)
- - [ ] Fully documented, including `whats-new.rst` for all changes and `api.rst` for new API (remove if this change should not be visible to users, e.g., if it is an internal clean-up, or if this is part of a larger project that will be documented later)
+<!-- Feel free to remove check-list items aren't relevant to your change -->
+
+ - [ ] Closes #xxxx
+ - [ ] Tests added
+ - [ ] Fully documented, including `whats-new.rst` for all changes and `api.rst` for new API
diff --git a/.travis.yml b/.travis.yml
index defb37ec..4ebd4f39 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -44,6 +44,7 @@ matrix:
         - libhdf5-serial-dev
         - netcdf-bin
         - libnetcdf-dev
+  - env: CONDA_ENV=py36-dask-dev
   - env: CONDA_ENV=py36-pandas-dev
   - env: CONDA_ENV=py36-bottleneck-dev
   - env: CONDA_ENV=py36-condaforge-rc
diff --git a/doc/time-series.rst b/doc/time-series.rst
index c225c246..7f5389d3 100644
--- a/doc/time-series.rst
+++ b/doc/time-series.rst
@@ -263,14 +263,16 @@ For data indexed by a :py:class:`~xarray.CFTimeIndex` xarray currently supports:
    da.sel(time=slice('0001-05', '0002-02'))
 
 - Access of basic datetime components via the ``dt`` accessor (in this case
-  just "year", "month", "day", "hour", "minute", "second", "microsecond", and
-  "season"): 
+  just "year", "month", "day", "hour", "minute", "second", "microsecond",
+  "season", "dayofyear", and "dayofweek"): 
 
 .. ipython:: python
 
    da.time.dt.year
    da.time.dt.month
    da.time.dt.season
+   da.time.dt.dayofyear
+   da.time.dt.dayofweek
 
 - Group-by operations based on datetime accessor attributes (e.g. by month of
   the year):
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index 040f72ac..87a0e425 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -41,10 +41,17 @@ Enhancements
 - :py:class:`CFTimeIndex` uses slicing for string indexing when possible (like
   :py:class:`pandas.DatetimeIndex`), which avoids unnecessary copies.
   By `Stephan Hoyer <https://github.com/shoyer>`_
+- Like :py:class:`pandas.DatetimeIndex`, :py:class:`CFTimeIndex` now supports
+  "dayofyear" and "dayofweek" accessors (:issue:`2597`).  By `Spencer Clark
+  <https://github.com/spencerkclark>`_. 
+
 
 Bug fixes
 ~~~~~~~~~
 
+- Fix h5netcdf saving scalars with filters or chunks (:issue:`2563`).
+  By `Martin Raspaud <https://github.com/mraspaud>`_.
+
 .. _whats-new.0.11.0:
 
 v0.11.0 (7 November 2018)
@@ -152,7 +159,9 @@ Enhancements
   to returning (and is now deprecated). This was changed in order to facilitate
   using tutorial datasets with dask.
   By `Joe Hamman <https://github.com/jhamman>`_.
-
+- ``DataArray`` can now use ``xr.set_option(keep_attrs=True)`` and retain attributes in binary operations, 
+  such as (``+, -, * ,/``). Default behaviour is unchanged (*Attributes will be dismissed*). By `Michael Blaschek <https://github.com/MBlaschek>`_
+  
 Bug fixes
 ~~~~~~~~~
 
diff --git a/xarray/backends/h5netcdf_.py b/xarray/backends/h5netcdf_.py
index 59cd4e84..90f63e88 100644
--- a/xarray/backends/h5netcdf_.py
+++ b/xarray/backends/h5netcdf_.py
@@ -208,10 +208,12 @@ class H5NetCDFStore(WritableCFDataStore):
 
         encoding['chunks'] = encoding.pop('chunksizes', None)
 
-        for key in ['compression', 'compression_opts', 'shuffle',
-                    'chunks', 'fletcher32']:
-            if key in encoding:
-                kwargs[key] = encoding[key]
+        # Do not apply compression, filters or chunking to scalars.
+        if variable.shape:
+            for key in ['compression', 'compression_opts', 'shuffle',
+                        'chunks', 'fletcher32']:
+                if key in encoding:
+                    kwargs[key] = encoding[key]
         if name not in self.ds:
             nc4_var = self.ds.create_variable(
                 name, dtype=dtype, dimensions=variable.dims,
diff --git a/xarray/coding/cftimeindex.py b/xarray/coding/cftimeindex.py
index 71fa7f2f..98954e9a 100644
--- a/xarray/coding/cftimeindex.py
+++ b/xarray/coding/cftimeindex.py
@@ -207,6 +207,9 @@ class CFTimeIndex(pd.Index):
     second = _field_accessor('second', 'The seconds of the datetime')
     microsecond = _field_accessor('microsecond',
                                   'The microseconds of the datetime')
+    dayofyear = _field_accessor('dayofyr',
+                                'The ordinal day of year of the datetime')
+    dayofweek = _field_accessor('dayofwk', 'The day of week of the datetime')
     date_type = property(get_date_type)
 
     def __new__(cls, data, name=None):
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index c2897038..888d2566 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -1946,7 +1946,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords,
         """
         from . import missing
 
-        coords = either_dict_or_kwargs(coords, coords_kwargs, 'rename')
+        coords = either_dict_or_kwargs(coords, coords_kwargs, 'interp')
         indexers = OrderedDict(self._validate_indexers(coords))
 
         obj = self if assume_sorted else self.sortby([k for k in coords])
diff --git a/xarray/core/variable.py b/xarray/core/variable.py
index 0bff06e7..7a921805 100644
--- a/xarray/core/variable.py
+++ b/xarray/core/variable.py
@@ -1658,11 +1658,13 @@ class Variable(common.AbstractArray, arithmetic.SupportsArithmetic,
             if isinstance(other, (xr.DataArray, xr.Dataset)):
                 return NotImplemented
             self_data, other_data, dims = _broadcast_compat_data(self, other)
+            keep_attrs = _get_keep_attrs(default=False)
+            attrs = self._attrs if keep_attrs else None
             with np.errstate(all='ignore'):
                 new_data = (f(self_data, other_data)
                             if not reflexive
                             else f(other_data, self_data))
-            result = Variable(dims, new_data)
+            result = Variable(dims, new_data, attrs=attrs)
             return result
         return func
 


Test patch:
diff --git a/xarray/tests/test_accessors.py b/xarray/tests/test_accessors.py
index 38038fc8..37b9c272 100644
--- a/xarray/tests/test_accessors.py
+++ b/xarray/tests/test_accessors.py
@@ -158,7 +158,8 @@ def times_3d(times):
 
 
 @pytest.mark.skipif(not has_cftime, reason='cftime not installed')
-@pytest.mark.parametrize('field', ['year', 'month', 'day', 'hour'])
+@pytest.mark.parametrize('field', ['year', 'month', 'day', 'hour',
+                                   'dayofyear', 'dayofweek'])
 def test_field_access(data, field):
     result = getattr(data.time.dt, field)
     expected = xr.DataArray(
@@ -170,7 +171,8 @@ def test_field_access(data, field):
 
 @pytest.mark.skipif(not has_dask, reason='dask not installed')
 @pytest.mark.skipif(not has_cftime, reason='cftime not installed')
-@pytest.mark.parametrize('field', ['year', 'month', 'day', 'hour'])
+@pytest.mark.parametrize('field', ['year', 'month', 'day', 'hour',
+                                   'dayofyear', 'dayofweek'])
 def test_dask_field_access_1d(data, field):
     import dask.array as da
 
@@ -186,7 +188,8 @@ def test_dask_field_access_1d(data, field):
 
 @pytest.mark.skipif(not has_dask, reason='dask not installed')
 @pytest.mark.skipif(not has_cftime, reason='cftime not installed')
-@pytest.mark.parametrize('field', ['year', 'month', 'day', 'hour'])
+@pytest.mark.parametrize('field', ['year', 'month', 'day', 'hour', 'dayofyear',
+                                   'dayofweek'])
 def test_dask_field_access(times_3d, data, field):
     import dask.array as da
 
diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py
index 2361b8c2..0ce3bd72 100644
--- a/xarray/tests/test_backends.py
+++ b/xarray/tests/test_backends.py
@@ -1840,6 +1840,8 @@ class TestH5NetCDFData(NetCDF4Base):
             data['var2'].encoding.update(compr_in)
             data['var2'].encoding.update(compr_common)
             compr_out.update(compr_common)
+            data['scalar'] = ('scalar_dim', np.array([2.0]))
+            data['scalar'] = data['scalar'][0]
             with self.roundtrip(data) as actual:
                 for k, v in compr_out.items():
                     assert v == actual['var2'].encoding[k]
diff --git a/xarray/tests/test_cftimeindex.py b/xarray/tests/test_cftimeindex.py
index b9006774..ea411159 100644
--- a/xarray/tests/test_cftimeindex.py
+++ b/xarray/tests/test_cftimeindex.py
@@ -175,6 +175,20 @@ def test_cftimeindex_field_accessors(index, field, expected):
     assert_array_equal(result, expected)
 
 
+@pytest.mark.skipif(not has_cftime, reason='cftime not installed')
+def test_cftimeindex_dayofyear_accessor(index):
+    result = index.dayofyear
+    expected = [date.dayofyr for date in index]
+    assert_array_equal(result, expected)
+
+
+@pytest.mark.skipif(not has_cftime, reason='cftime not installed')
+def test_cftimeindex_dayofweek_accessor(index):
+    result = index.dayofweek
+    expected = [date.dayofwk for date in index]
+    assert_array_equal(result, expected)
+
+
 @pytest.mark.skipif(not has_cftime, reason='cftime not installed')
 @pytest.mark.parametrize(('string', 'date_args', 'reso'), [
     ('1999', (1999, 1, 1), 'year'),
diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py
index 0bd44078..84813f6c 100644
--- a/xarray/tests/test_variable.py
+++ b/xarray/tests/test_variable.py
@@ -28,6 +28,8 @@ from . import (
     assert_allclose, assert_array_equal, assert_equal, assert_identical,
     raises_regex, requires_dask, source_ndarray)
 
+from xarray import set_options
+
 
 class VariableSubclassobjects(object):
     def test_properties(self):
@@ -1545,6 +1547,18 @@ class TestVariable(VariableSubclassobjects):
         assert len(vm.attrs) == len(_attrs)
         assert vm.attrs == _attrs
 
+    def test_binary_ops_keep_attrs(self):
+        _attrs = {'units': 'test', 'long_name': 'testing'}
+        a = Variable(['x', 'y'], np.random.randn(3, 3), _attrs)
+        b = Variable(['x', 'y'], np.random.randn(3, 3), _attrs)
+        # Test dropped attrs
+        d = a - b   # just one operation
+        assert d.attrs == OrderedDict()
+        # Test kept attrs
+        with set_options(keep_attrs=True):
+            d = a - b
+        assert d.attrs == _attrs
+
     def test_count(self):
         expected = Variable([], 3)
         actual = Variable(['x'], [1, 2, 3, np.nan]).count()