From 97a829a5ae5d849a5c20ec019f31f6879841b843 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sat, 17 Sep 2016 11:50:55 -0700 Subject: [PATCH] MultiIndex level coordinates as Dataset attributes Follow-up on GH947 This ensures that you can pull out MultiIndex levels via attribute style access on Dataset objects, as well as DataArray objects. CC benbovy --- xarray/core/common.py | 2 +- xarray/core/coordinates.py | 6 +++--- xarray/core/dataarray.py | 4 ++-- xarray/core/dataset.py | 7 ++++++- xarray/test/test_dataset.py | 3 +++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/xarray/core/common.py b/xarray/core/common.py index 5b04f45fc8e..831fb89d125 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -181,7 +181,7 @@ class AttrAccessMixin(object): @property def _attr_sources(self): """List of places to look-up items for attribute-style access""" - return [self, self.attrs] + return [] def __getattr__(self, name): if name != '__setstate__': diff --git a/xarray/core/coordinates.py b/xarray/core/coordinates.py index 30240c16b27..9eca9009460 100644 --- a/xarray/core/coordinates.py +++ b/xarray/core/coordinates.py @@ -215,8 +215,8 @@ def __delitem__(self, key): del self._data._coords[key] -class DataArrayLevelCoordinates(AbstractCoordinates): - """Dictionary like container for DataArray MultiIndex level coordinates. +class LevelCoordinates(AbstractCoordinates): + """Dictionary like container for MultiIndex level coordinates. Used for attribute style lookup. Not returned directly by any public methods. @@ -232,7 +232,7 @@ def _names(self): def variables(self): level_coords = OrderedDict( (k, self._data[v].variable.get_level_variable(k)) - for k, v in self._data._level_coords.items()) + for k, v in self._data._level_coords.items()) return Frozen(level_coords) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 41bc17f7e07..5836167326e 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -14,7 +14,7 @@ from . import utils from .alignment import align from .common import AbstractArray, BaseDataObject, squeeze -from .coordinates import (DataArrayCoordinates, DataArrayLevelCoordinates, +from .coordinates import (DataArrayCoordinates, LevelCoordinates, Indexes) from .dataset import Dataset from .pycompat import iteritems, basestring, OrderedDict, zip @@ -467,7 +467,7 @@ def __delitem__(self, key): @property def _attr_sources(self): """List of places to look-up items for attribute-style access""" - return [self.coords, DataArrayLevelCoordinates(self), self.attrs] + return [self.coords, LevelCoordinates(self), self.attrs] def __contains__(self, key): return key in self._coords diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 65353fc51ef..e0833ceb8d5 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -15,7 +15,7 @@ from . import formatting from .. import conventions from .alignment import align -from .coordinates import DatasetCoordinates, Indexes +from .coordinates import DatasetCoordinates, LevelCoordinates, Indexes from .common import ImplementsDatasetReduce, BaseDataObject from .merge import (dataset_update_method, dataset_merge_method, merge_data_and_coords) @@ -487,6 +487,11 @@ def __deepcopy__(self, memo=None): # copy.deepcopy return self.copy(deep=True) + @property + def _attr_sources(self): + """List of places to look-up items for attribute-style access""" + return [self, LevelCoordinates(self), self.attrs] + def __contains__(self, key): """The 'in' operator will return true or false depending on whether 'key' is an array in the dataset or not. diff --git a/xarray/test/test_dataset.py b/xarray/test/test_dataset.py index a1da10b4ca5..02f1fbf7171 100644 --- a/xarray/test/test_dataset.py +++ b/xarray/test/test_dataset.py @@ -1540,6 +1540,9 @@ def test_virtual_variable_multiindex(self): name='hour', coords=[mindex], dims='x') self.assertDataArrayIdentical(expected, data['level_date.hour']) + # attribute style access + self.assertDataArrayIdentical(data.level_str, data['level_str']) + def test_time_season(self): ds = Dataset({'t': pd.date_range('2000-01-01', periods=12, freq='M')}) expected = ['DJF'] * 2 + ['MAM'] * 3 + ['JJA'] * 3 + ['SON'] * 3 + ['DJF']