From b4c7c874f55efeb8bd46b199a5d2bdbd7189f834 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 23 May 2018 04:49:30 -0400 Subject: [PATCH 01/16] ds more robust to non-str keys --- xarray/core/dataarray.py | 9 ++++++++- xarray/tests/test_dataset.py | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index fc7091dad85..e9d6098f3c7 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -466,7 +466,14 @@ def _getitem_coord(self, key): return self._replace_maybe_drop_dims(var, name=key) def __getitem__(self, key): - if isinstance(key, basestring): + try: + is_coord_key = key in set(self.dims).union(self.coords) + except TypeError: + # not hashable, but testing with collections.Hashable is not + # complete, since# tuples with slices inside will suggest + # they're hashable + is_coord_key = False + if is_coord_key: return self._getitem_coord(key) else: # xarray-style array indexing diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 76e41c43c6d..038cc78dda8 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4181,6 +4181,12 @@ def test_dir_non_string(data_set): result = dir(data_set) assert not (5 in result) + # GH2172 + sample_data = np.random.uniform(size=[2, 2000, 10000]) + x = xr.Dataset({"sample_data": (sample_data.shape, sample_data)}) + x2 = x["sample_data"] + dir(x2) + def test_dir_unicode(data_set): data_set[u'unicode'] = 'uni' From fc3f729ae34123a3456f1cc35e30403228ad7de6 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 23 May 2018 04:49:39 -0400 Subject: [PATCH 02/16] formatting --- setup.cfg | 1 + xarray/tests/test_dataarray.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 850551b3579..4dd1bffe043 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,7 @@ testpaths=xarray/tests [flake8] max-line-length=79 ignore= + W503 exclude= doc/ diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index 35e270f0db7..25d8a844eec 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -17,8 +17,8 @@ from xarray.core.pycompat import OrderedDict, iteritems from xarray.tests import ( ReturnItem, TestCase, assert_allclose, assert_array_equal, assert_equal, - assert_identical, raises_regex, requires_bottleneck, requires_dask, - requires_scipy, source_ndarray, unittest, requires_cftime) + assert_identical, raises_regex, requires_bottleneck, requires_cftime, + requires_dask, requires_scipy, source_ndarray, unittest) class TestDataArray(TestCase): From e29273b11f6b77dd19bf3f349f386693cff857e6 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 23 May 2018 06:09:30 -0400 Subject: [PATCH 03/16] time.dayofyear needs cover in dataarray getitem --- xarray/core/dataarray.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index e9d6098f3c7..0a16d69bd09 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -467,7 +467,10 @@ def _getitem_coord(self, key): def __getitem__(self, key): try: - is_coord_key = key in set(self.dims).union(self.coords) + is_coord_key = any([ + isinstance(key, basestring), + key in set(self.dims).union(self.coords) + ]) except TypeError: # not hashable, but testing with collections.Hashable is not # complete, since# tuples with slices inside will suggest From c15bcd0a50efa70c120882c9881e298213b08565 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Thu, 24 May 2018 04:43:06 -0400 Subject: [PATCH 04/16] trial of indexer_dict --- xarray/core/dataarray.py | 26 +++++++++++--------------- xarray/core/dataset.py | 11 +++++++++-- xarray/core/variable.py | 8 +++++++- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 0a16d69bd09..e30cd28e3b7 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -466,21 +466,11 @@ def _getitem_coord(self, key): return self._replace_maybe_drop_dims(var, name=key) def __getitem__(self, key): - try: - is_coord_key = any([ - isinstance(key, basestring), - key in set(self.dims).union(self.coords) - ]) - except TypeError: - # not hashable, but testing with collections.Hashable is not - # complete, since# tuples with slices inside will suggest - # they're hashable - is_coord_key = False - if is_coord_key: + if isinstance(key, basestring): return self._getitem_coord(key) else: # xarray-style array indexing - return self.isel(**self._item_key_to_dict(key)) + return self.isel(indexer_dict=self._item_key_to_dict(key)) def __setitem__(self, key, value): if isinstance(key, basestring): @@ -508,7 +498,7 @@ def _attr_sources(self): @property def _item_sources(self): """List of places to look-up items for key-completion""" - return [self.coords, {d: self[d] for d in self.dims}, + return [self.coords, {d: self.coords[d] for d in self.dims}, LevelCoordinatesSource(self)] def __contains__(self, key): @@ -752,7 +742,7 @@ def chunk(self, chunks=None, name_prefix='xarray-', token=None, token=token, lock=lock) return self._from_temp_dataset(ds) - def isel(self, drop=False, **indexers): + def isel(self, indexer_dict=None, drop=False, **indexers): """Return a new DataArray whose dataset is given by integer indexing along the specified dimension(s). @@ -761,7 +751,13 @@ def isel(self, drop=False, **indexers): Dataset.isel DataArray.sel """ - ds = self._to_temp_dataset().isel(drop=drop, **indexers) + if indexer_dict is not None: + if indexers: + # could combine them (easier syntax when we drop Py2) + raise ValueError("Both indexer_dict and indexers supplied. " + "Please only provide one") + indexers = indexer_dict + ds = self._to_temp_dataset().isel(drop=drop, indexer_dict=indexers) return self._from_temp_dataset(ds) def sel(self, method=None, tolerance=None, drop=False, **indexers): diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index fff11dedb01..fcf6d288145 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1368,7 +1368,7 @@ def _get_indexers_coordinates(self, indexers): attached_coords[k] = v return attached_coords - def isel(self, drop=False, **indexers): + def isel(self, indexer_dict=None, drop=False, **indexers): """Returns a new dataset with each array indexed along the specified dimension(s). @@ -1404,12 +1404,19 @@ def isel(self, drop=False, **indexers): Dataset.sel DataArray.isel """ + if indexer_dict is not None: + if indexers: + raise ValueError("Both indexer_dict and indexers supplied. " + "Please only provide one") + indexers = indexer_dict + assert isinstance(drop, bool) + indexers_list = self._validate_indexers(indexers) variables = OrderedDict() for name, var in iteritems(self._variables): var_indexers = {k: v for k, v in indexers_list if k in var.dims} - new_var = var.isel(**var_indexers) + new_var = var.isel(indexer_dict=var_indexers) if not (drop and name in var_indexers): variables[name] = new_var diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 9dcb99459d4..2c6dab3ab30 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -824,7 +824,7 @@ def chunk(self, chunks=None, name=None, lock=False): return type(self)(self.dims, data, self._attrs, self._encoding, fastpath=True) - def isel(self, **indexers): + def isel(self, indexer_dict=None, **indexers): """Return a new array indexed along the specified dimension(s). Parameters @@ -841,6 +841,12 @@ def isel(self, **indexers): unless numpy fancy indexing was triggered by using an array indexer, in which case the data will be a copy. """ + if indexer_dict is not None: + if indexers: + raise ValueError("Both indexer_dict and indexers supplied. " + "Please only provide one") + indexers = indexer_dict + invalid = [k for k in indexers if k not in self.dims] if invalid: raise ValueError("dimensions %r do not exist" % invalid) From b765c3c34bc1109c96bc88aeb9ccd87557eb497a Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Fri, 25 May 2018 06:57:43 -0400 Subject: [PATCH 05/16] feedback from stephan --- xarray/core/dataarray.py | 13 +++++-------- xarray/core/dataset.py | 27 +++++++++++++-------------- xarray/core/variable.py | 12 ++++-------- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index e30cd28e3b7..0565832db3b 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -18,7 +18,9 @@ from .formatting import format_item from .options import OPTIONS from .pycompat import OrderedDict, basestring, iteritems, range, zip -from .utils import decode_numpy_dict_values, ensure_us_time_resolution +from .utils import ( + combine_pos_and_kw_args, decode_numpy_dict_values, + ensure_us_time_resolution) from .variable import ( IndexVariable, Variable, as_compatible_data, as_variable, assert_unique_multiindex_level_names) @@ -742,7 +744,7 @@ def chunk(self, chunks=None, name_prefix='xarray-', token=None, token=token, lock=lock) return self._from_temp_dataset(ds) - def isel(self, indexer_dict=None, drop=False, **indexers): + def isel(self, indexers=None, drop=False, **indexers_kwargs): """Return a new DataArray whose dataset is given by integer indexing along the specified dimension(s). @@ -751,12 +753,7 @@ def isel(self, indexer_dict=None, drop=False, **indexers): Dataset.isel DataArray.sel """ - if indexer_dict is not None: - if indexers: - # could combine them (easier syntax when we drop Py2) - raise ValueError("Both indexer_dict and indexers supplied. " - "Please only provide one") - indexers = indexer_dict + indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'isel') ds = self._to_temp_dataset().isel(drop=drop, indexer_dict=indexers) return self._from_temp_dataset(ds) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index fcf6d288145..041b6c16618 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -17,8 +17,8 @@ rolling, utils) from .. import conventions from .alignment import align -from .common import (DataWithCoords, ImplementsDatasetReduce, - _contains_datetime_like_objects) +from .common import ( + DataWithCoords, ImplementsDatasetReduce, _contains_datetime_like_objects) from .coordinates import ( DatasetCoordinates, Indexes, LevelCoordinatesSource, assert_coordinate_consistent, remap_label_indexers) @@ -30,7 +30,7 @@ from .pycompat import ( OrderedDict, basestring, dask_array_type, integer_types, iteritems, range) from .utils import ( - Frozen, SortedKeysDict, decode_numpy_dict_values, + Frozen, SortedKeysDict, combine_pos_and_kw_args, decode_numpy_dict_values, ensure_us_time_resolution, hashable, maybe_wrap_array) from .variable import IndexVariable, Variable, as_variable, broadcast_variables @@ -1368,7 +1368,7 @@ def _get_indexers_coordinates(self, indexers): attached_coords[k] = v return attached_coords - def isel(self, indexer_dict=None, drop=False, **indexers): + def isel(self, indexers=None, drop=False, **indexers_kwargs): """Returns a new dataset with each array indexed along the specified dimension(s). @@ -1378,15 +1378,17 @@ def isel(self, indexer_dict=None, drop=False, **indexers): Parameters ---------- - drop : bool, optional - If ``drop=True``, drop coordinates variables indexed by integers - instead of making them scalar. - **indexers : {dim: indexer, ...} - Keyword arguments with names matching dimensions and values given + indexers : dict, optional + A dict with keys matching dimensions and values given by integers, slice objects or arrays. indexer can be a integer, slice, array-like or DataArray. If DataArrays are passed as indexers, xarray-style indexing will be carried out. See :ref:`indexing` for the details. + drop : bool, optional + If ``drop=True``, drop coordinates variables indexed by integers + instead of making them scalar. + **indexers : {dim: indexer, ...} + The keyword arguments form of ``indexers`` Returns ------- @@ -1404,11 +1406,8 @@ def isel(self, indexer_dict=None, drop=False, **indexers): Dataset.sel DataArray.isel """ - if indexer_dict is not None: - if indexers: - raise ValueError("Both indexer_dict and indexers supplied. " - "Please only provide one") - indexers = indexer_dict + + indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'isel') assert isinstance(drop, bool) indexers_list = self._validate_indexers(indexers) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 2c6dab3ab30..e4066a6b2ff 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -11,13 +11,13 @@ import xarray as xr # only for Dataset and DataArray from . import ( - arithmetic, common, dtypes, duck_array_ops, indexing, nputils, ops, utils,) + arithmetic, common, dtypes, duck_array_ops, indexing, nputils, ops, utils) from .indexing import ( BasicIndexer, OuterIndexer, PandasIndexAdapter, VectorizedIndexer, as_indexable) from .pycompat import ( OrderedDict, basestring, dask_array_type, integer_types, zip) -from .utils import OrderedSet +from .utils import OrderedSet, combine_pos_and_kw_args try: import dask.array as da @@ -824,7 +824,7 @@ def chunk(self, chunks=None, name=None, lock=False): return type(self)(self.dims, data, self._attrs, self._encoding, fastpath=True) - def isel(self, indexer_dict=None, **indexers): + def isel(self, indexers=None, drop=False, **indexers_kwargs): """Return a new array indexed along the specified dimension(s). Parameters @@ -841,11 +841,7 @@ def isel(self, indexer_dict=None, **indexers): unless numpy fancy indexing was triggered by using an array indexer, in which case the data will be a copy. """ - if indexer_dict is not None: - if indexers: - raise ValueError("Both indexer_dict and indexers supplied. " - "Please only provide one") - indexers = indexer_dict + indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'isel') invalid = [k for k in indexers if k not in self.dims] if invalid: From e441614bbde77507bc9d0c8318fc1a3bc917dc9e Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Fri, 25 May 2018 08:38:22 -0400 Subject: [PATCH 06/16] a few more methods --- xarray/core/coordinates.py | 8 +++++--- xarray/core/dataarray.py | 22 ++++++++++++++-------- xarray/core/dataset.py | 31 ++++++++++++++++++------------- xarray/tests/test_dataset.py | 2 +- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/xarray/core/coordinates.py b/xarray/core/coordinates.py index cb22c0b687b..7c5a50e0bb3 100644 --- a/xarray/core/coordinates.py +++ b/xarray/core/coordinates.py @@ -9,10 +9,9 @@ from .merge import ( expand_and_merge_variables, merge_coords, merge_coords_for_inplace_math) from .pycompat import OrderedDict -from .utils import Frozen, ReprObject +from .utils import Frozen, ReprObject, combine_pos_and_kw_args from .variable import Variable - # Used as the key corresponding to a DataArray's variable when converting # arbitrary DataArray objects to datasets _THIS_ARRAY = ReprObject('') @@ -332,7 +331,8 @@ def assert_coordinate_consistent(obj, coords): .format(k, obj[k], coords[k])) -def remap_label_indexers(obj, method=None, tolerance=None, **indexers): +def remap_label_indexers(obj, indexers=None, method=None, tolerance=None, + **indexers_kwargs): """ Remap **indexers from obj.coords. If indexer is an instance of DataArray and it has coordinate, then this @@ -345,6 +345,8 @@ def remap_label_indexers(obj, method=None, tolerance=None, **indexers): new_indexes: mapping of new dimensional-coordinate. """ from .dataarray import DataArray + indexers = combine_pos_and_kw_args( + indexers, indexers_kwargs, 'remap_label_indexers') v_indexers = {k: v.variable.data if isinstance(v, DataArray) else v for k, v in indexers.items()} diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 0565832db3b..416e9597f74 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -472,7 +472,7 @@ def __getitem__(self, key): return self._getitem_coord(key) else: # xarray-style array indexing - return self.isel(indexer_dict=self._item_key_to_dict(key)) + return self.isel(indexers=self._item_key_to_dict(key)) def __setitem__(self, key, value): if isinstance(key, basestring): @@ -754,10 +754,11 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): DataArray.sel """ indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'isel') - ds = self._to_temp_dataset().isel(drop=drop, indexer_dict=indexers) + ds = self._to_temp_dataset().isel(drop=drop, indexers=indexers) return self._from_temp_dataset(ds) - def sel(self, method=None, tolerance=None, drop=False, **indexers): + def sel(self, indexers=None, method=None, tolerance=None, drop=False, + **indexers_kwargs): """Return a new DataArray whose dataset is given by selecting index labels along the specified dimension(s). @@ -779,11 +780,12 @@ def sel(self, method=None, tolerance=None, drop=False, **indexers): DataArray.isel """ - ds = self._to_temp_dataset().sel(drop=drop, method=method, - tolerance=tolerance, **indexers) + indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'sel') + ds = self._to_temp_dataset().sel( + indexers=indexers, drop=drop, method=method, tolerance=tolerance) return self._from_temp_dataset(ds) - def isel_points(self, dim='points', **indexers): + def isel_points(self, indexers=None, dim='points', **indexers_kwargs): """Return a new DataArray whose dataset is given by pointwise integer indexing along the specified dimension(s). @@ -791,11 +793,13 @@ def isel_points(self, dim='points', **indexers): -------- Dataset.isel_points """ + indexers = combine_pos_and_kw_args( + indexers, indexers_kwargs, 'isel_points') ds = self._to_temp_dataset().isel_points(dim=dim, **indexers) return self._from_temp_dataset(ds) - def sel_points(self, dim='points', method=None, tolerance=None, - **indexers): + def sel_points(self, indexers=None, dim='points', method=None, + tolerance=None, **indexers_kwargs): """Return a new DataArray whose dataset is given by pointwise selection of index labels along the specified dimension(s). @@ -803,6 +807,8 @@ def sel_points(self, dim='points', method=None, tolerance=None, -------- Dataset.sel_points """ + indexers = combine_pos_and_kw_args( + indexers, indexers_kwargs, 'sel_points') ds = self._to_temp_dataset().sel_points( dim=dim, method=method, tolerance=tolerance, **indexers) return self._from_temp_dataset(ds) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 041b6c16618..76b4041f041 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1387,7 +1387,7 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): drop : bool, optional If ``drop=True``, drop coordinates variables indexed by integers instead of making them scalar. - **indexers : {dim: indexer, ...} + **indexers_kwarg : {dim: indexer, ...} The keyword arguments form of ``indexers`` Returns @@ -1415,7 +1415,7 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): variables = OrderedDict() for name, var in iteritems(self._variables): var_indexers = {k: v for k, v in indexers_list if k in var.dims} - new_var = var.isel(indexer_dict=var_indexers) + new_var = var.isel(indexers=var_indexers) if not (drop and name in var_indexers): variables[name] = new_var @@ -1431,7 +1431,8 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): .union(coord_vars)) return self._replace_vars_and_dims(variables, coord_names=coord_names) - def sel(self, method=None, tolerance=None, drop=False, **indexers): + def sel(self, indexers=None, method=None, tolerance=None, drop=False, + **indexers_kwargs): """Returns a new dataset with each array indexed by tick labels along the specified dimension(s). @@ -1450,6 +1451,14 @@ def sel(self, method=None, tolerance=None, drop=False, **indexers): Parameters ---------- + ---------- + indexers : dict, optional + A dict with keys matching dimensions and values given + by scalars, slices or arrays of tick labels. For dimensions with + multi-index, the indexer may also be a dict-like object with keys + matching index level names. + If DataArrays are passed as indexers, xarray-style indexing will be + carried out. See :ref:`indexing` for the details. method : {None, 'nearest', 'pad'/'ffill', 'backfill'/'bfill'}, optional Method to use for inexact matches (requires pandas>=0.16): @@ -1465,13 +1474,8 @@ def sel(self, method=None, tolerance=None, drop=False, **indexers): drop : bool, optional If ``drop=True``, drop coordinates variables in `indexers` instead of making them scalar. - **indexers : {dim: indexer, ...} - Keyword arguments with names matching dimensions and values given - by scalars, slices or arrays of tick labels. For dimensions with - multi-index, the indexer may also be a dict-like object with keys - matching index level names. - If DataArrays are passed as indexers, xarray-style indexing will be - carried out. See :ref:`indexing` for the details. + **indexers_kwargs : {dim: indexer, ...} + The keyword arguments form of ``indexers`` Returns ------- @@ -1490,9 +1494,10 @@ def sel(self, method=None, tolerance=None, drop=False, **indexers): Dataset.isel DataArray.sel """ - pos_indexers, new_indexes = remap_label_indexers(self, method, - tolerance, **indexers) - result = self.isel(drop=drop, **pos_indexers) + indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'sel') + pos_indexers, new_indexes = remap_label_indexers( + self, indexers=indexers, method=method, tolerance=tolerance) + result = self.isel(indexers=pos_indexers, drop=drop) return result._replace_indexes(new_indexes) def isel_points(self, dim='points', **indexers): diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 038cc78dda8..38e2dce1633 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -1435,7 +1435,7 @@ def test_sel_method(self): with raises_regex(TypeError, '``method``'): # this should not pass silently - data.sel(data) + data.sel(method=data) # cannot pass method if there is no associated coordinate with raises_regex(ValueError, 'cannot supply'): From 4612ac06503aa7286ceb056c4c3da6909ffb5215 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Fri, 25 May 2018 09:16:35 -0400 Subject: [PATCH 07/16] reindex added --- xarray/core/dataarray.py | 29 +++++++++++++++-------------- xarray/core/dataset.py | 6 +++--- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 416e9597f74..2f2a81251c2 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -785,7 +785,7 @@ def sel(self, indexers=None, method=None, tolerance=None, drop=False, indexers=indexers, drop=drop, method=method, tolerance=tolerance) return self._from_temp_dataset(ds) - def isel_points(self, indexers=None, dim='points', **indexers_kwargs): + def isel_points(self, dim='points', **indexers): """Return a new DataArray whose dataset is given by pointwise integer indexing along the specified dimension(s). @@ -793,13 +793,11 @@ def isel_points(self, indexers=None, dim='points', **indexers_kwargs): -------- Dataset.isel_points """ - indexers = combine_pos_and_kw_args( - indexers, indexers_kwargs, 'isel_points') ds = self._to_temp_dataset().isel_points(dim=dim, **indexers) return self._from_temp_dataset(ds) - def sel_points(self, indexers=None, dim='points', method=None, - tolerance=None, **indexers_kwargs): + def sel_points(self, dim='points', method=None, tolerance=None, + **indexers): """Return a new DataArray whose dataset is given by pointwise selection of index labels along the specified dimension(s). @@ -807,8 +805,6 @@ def sel_points(self, indexers=None, dim='points', method=None, -------- Dataset.sel_points """ - indexers = combine_pos_and_kw_args( - indexers, indexers_kwargs, 'sel_points') ds = self._to_temp_dataset().sel_points( dim=dim, method=method, tolerance=tolerance, **indexers) return self._from_temp_dataset(ds) @@ -860,12 +856,18 @@ def reindex_like(self, other, method=None, tolerance=None, copy=True): return self.reindex(method=method, tolerance=tolerance, copy=copy, **indexers) - def reindex(self, method=None, tolerance=None, copy=True, **indexers): + def reindex(self, indexers=None, method=None, tolerance=None, copy=True, + **indexers_kwargs): """Conform this object onto a new set of indexes, filling in missing values with NaN. Parameters ---------- + **indexers : dict + Dictionary with keys given by dimension names and values given by + arrays of coordinates tick labels. Any mis-matched coordinate + values will be filled in with NaN, and any mis-matched dimension + names will simply be ignored. copy : bool, optional If ``copy=True``, data in the return value is always copied. If ``copy=False`` and reindexing is unnecessary, or can be performed @@ -883,11 +885,8 @@ def reindex(self, method=None, tolerance=None, copy=True, **indexers): Maximum distance between original and new labels for inexact matches. The values of the index at the matching locations most satisfy the equation ``abs(index[indexer] - target) <= tolerance``. - **indexers : dict - Dictionary with keys given by dimension names and values given by - arrays of coordinates tick labels. Any mis-matched coordinate - values will be filled in with NaN, and any mis-matched dimension - names will simply be ignored. + **indexers_kwargs : {dim: indexer, ...} + The keyword arguments form of ``indexers`` Returns ------- @@ -900,8 +899,10 @@ def reindex(self, method=None, tolerance=None, copy=True, **indexers): DataArray.reindex_like align """ + indexers = combine_pos_and_kw_args( + indexers, indexers_kwargs, 'reindex') ds = self._to_temp_dataset().reindex( - method=method, tolerance=tolerance, copy=copy, **indexers) + indexers=indexers, method=method, tolerance=tolerance, copy=copy) return self._from_temp_dataset(ds) def rename(self, new_name_or_name_dict): diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 76b4041f041..db3d1f04404 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1745,7 +1745,7 @@ def reindex_like(self, other, method=None, tolerance=None, copy=True): **indexers) def reindex(self, indexers=None, method=None, tolerance=None, copy=True, - **kw_indexers): + **indexers_kwargs): """Conform this object onto a new set of indexes, filling in missing values with NaN. @@ -1774,7 +1774,7 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, ``copy=False`` and reindexing is unnecessary, or can be performed with only slice operations, then the output may share memory with the input. In either case, a new xarray object is always returned. - **kw_indexers : optional + **indexers_kwargs : optional Keyword arguments in the same form as ``indexers``. Returns @@ -1788,7 +1788,7 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, align pandas.Index.get_indexer """ - indexers = utils.combine_pos_and_kw_args(indexers, kw_indexers, + indexers = utils.combine_pos_and_kw_args(indexers, indexers_kwargs, 'reindex') bad_dims = [d for d in indexers if d not in self.dims] From b301b178785cf3ef963e20258d68f05d9f4e081d Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 26 May 2018 00:46:50 -0400 Subject: [PATCH 08/16] rename to either_dict_or_kwargs --- xarray/core/coordinates.py | 4 ++-- xarray/core/dataarray.py | 8 ++++---- xarray/core/dataset.py | 8 ++++---- xarray/core/utils.py | 2 +- xarray/core/variable.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/xarray/core/coordinates.py b/xarray/core/coordinates.py index 7c5a50e0bb3..efe8affb2a3 100644 --- a/xarray/core/coordinates.py +++ b/xarray/core/coordinates.py @@ -9,7 +9,7 @@ from .merge import ( expand_and_merge_variables, merge_coords, merge_coords_for_inplace_math) from .pycompat import OrderedDict -from .utils import Frozen, ReprObject, combine_pos_and_kw_args +from .utils import Frozen, ReprObject, either_dict_or_kwargs from .variable import Variable # Used as the key corresponding to a DataArray's variable when converting @@ -345,7 +345,7 @@ def remap_label_indexers(obj, indexers=None, method=None, tolerance=None, new_indexes: mapping of new dimensional-coordinate. """ from .dataarray import DataArray - indexers = combine_pos_and_kw_args( + indexers = either_dict_or_kwargs( indexers, indexers_kwargs, 'remap_label_indexers') v_indexers = {k: v.variable.data if isinstance(v, DataArray) else v diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 2f2a81251c2..07bf39b44a0 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -19,7 +19,7 @@ from .options import OPTIONS from .pycompat import OrderedDict, basestring, iteritems, range, zip from .utils import ( - combine_pos_and_kw_args, decode_numpy_dict_values, + either_dict_or_kwargs, decode_numpy_dict_values, ensure_us_time_resolution) from .variable import ( IndexVariable, Variable, as_compatible_data, as_variable, @@ -753,7 +753,7 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): Dataset.isel DataArray.sel """ - indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'isel') + indexers = either_dict_or_kwargs(indexers, indexers_kwargs, 'isel') ds = self._to_temp_dataset().isel(drop=drop, indexers=indexers) return self._from_temp_dataset(ds) @@ -780,7 +780,7 @@ def sel(self, indexers=None, method=None, tolerance=None, drop=False, DataArray.isel """ - indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'sel') + indexers = either_dict_or_kwargs(indexers, indexers_kwargs, 'sel') ds = self._to_temp_dataset().sel( indexers=indexers, drop=drop, method=method, tolerance=tolerance) return self._from_temp_dataset(ds) @@ -899,7 +899,7 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, DataArray.reindex_like align """ - indexers = combine_pos_and_kw_args( + indexers = either_dict_or_kwargs( indexers, indexers_kwargs, 'reindex') ds = self._to_temp_dataset().reindex( indexers=indexers, method=method, tolerance=tolerance, copy=copy) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index db3d1f04404..1b32000b349 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -30,7 +30,7 @@ from .pycompat import ( OrderedDict, basestring, dask_array_type, integer_types, iteritems, range) from .utils import ( - Frozen, SortedKeysDict, combine_pos_and_kw_args, decode_numpy_dict_values, + Frozen, SortedKeysDict, either_dict_or_kwargs, decode_numpy_dict_values, ensure_us_time_resolution, hashable, maybe_wrap_array) from .variable import IndexVariable, Variable, as_variable, broadcast_variables @@ -1407,7 +1407,7 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): DataArray.isel """ - indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'isel') + indexers = either_dict_or_kwargs(indexers, indexers_kwargs, 'isel') assert isinstance(drop, bool) indexers_list = self._validate_indexers(indexers) @@ -1494,7 +1494,7 @@ def sel(self, indexers=None, method=None, tolerance=None, drop=False, Dataset.isel DataArray.sel """ - indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'sel') + indexers = either_dict_or_kwargs(indexers, indexers_kwargs, 'sel') pos_indexers, new_indexes = remap_label_indexers( self, indexers=indexers, method=method, tolerance=tolerance) result = self.isel(indexers=pos_indexers, drop=drop) @@ -1788,7 +1788,7 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, align pandas.Index.get_indexer """ - indexers = utils.combine_pos_and_kw_args(indexers, indexers_kwargs, + indexers = utils.either_dict_or_kwargs(indexers, indexers_kwargs, 'reindex') bad_dims = [d for d in indexers if d not in self.dims] diff --git a/xarray/core/utils.py b/xarray/core/utils.py index 06bb3ede393..8a5476c9c37 100644 --- a/xarray/core/utils.py +++ b/xarray/core/utils.py @@ -183,7 +183,7 @@ def is_full_slice(value): return isinstance(value, slice) and value == slice(None) -def combine_pos_and_kw_args(pos_kwargs, kw_kwargs, func_name): +def either_dict_or_kwargs(pos_kwargs, kw_kwargs, func_name): if pos_kwargs is not None: if not is_dict_like(pos_kwargs): raise ValueError('the first argument to .%s must be a dictionary' diff --git a/xarray/core/variable.py b/xarray/core/variable.py index e4066a6b2ff..52d470accfe 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -17,7 +17,7 @@ as_indexable) from .pycompat import ( OrderedDict, basestring, dask_array_type, integer_types, zip) -from .utils import OrderedSet, combine_pos_and_kw_args +from .utils import OrderedSet, either_dict_or_kwargs try: import dask.array as da @@ -841,7 +841,7 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): unless numpy fancy indexing was triggered by using an array indexer, in which case the data will be a copy. """ - indexers = combine_pos_and_kw_args(indexers, indexers_kwargs, 'isel') + indexers = either_dict_or_kwargs(indexers, indexers_kwargs, 'isel') invalid = [k for k in indexers if k not in self.dims] if invalid: From 3edfda6ca0bb9201dd7af06051bb5298596f5717 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 26 May 2018 00:47:23 -0400 Subject: [PATCH 09/16] remove assert check --- xarray/core/dataset.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 1b32000b349..02d73115b9f 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1408,7 +1408,6 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): """ indexers = either_dict_or_kwargs(indexers, indexers_kwargs, 'isel') - assert isinstance(drop, bool) indexers_list = self._validate_indexers(indexers) From 2ad74253afc90d2437fa8ea99b8475090ba3ea81 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 26 May 2018 00:52:05 -0400 Subject: [PATCH 10/16] docstring --- xarray/core/dataarray.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 07bf39b44a0..c5d424e9613 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -863,11 +863,12 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, Parameters ---------- - **indexers : dict + indexers : dict, optional Dictionary with keys given by dimension names and values given by arrays of coordinates tick labels. Any mis-matched coordinate values will be filled in with NaN, and any mis-matched dimension names will simply be ignored. + One of indexers or indexers_kwargs must be provided. copy : bool, optional If ``copy=True``, data in the return value is always copied. If ``copy=False`` and reindexing is unnecessary, or can be performed @@ -886,7 +887,8 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, matches. The values of the index at the matching locations most satisfy the equation ``abs(index[indexer] - target) <= tolerance``. **indexers_kwargs : {dim: indexer, ...} - The keyword arguments form of ``indexers`` + The keyword arguments form of ``indexers``. + One of indexers or indexers_kwargs must be provided. Returns ------- From 7052520a7bbedad6065d065c6bfc179a53cb9169 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 26 May 2018 00:56:17 -0400 Subject: [PATCH 11/16] more docstring --- xarray/core/dataset.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 02d73115b9f..e7cd5e19ca4 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1384,11 +1384,13 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): indexer can be a integer, slice, array-like or DataArray. If DataArrays are passed as indexers, xarray-style indexing will be carried out. See :ref:`indexing` for the details. + One of indexers or indexers_kwargs must be provided. drop : bool, optional If ``drop=True``, drop coordinates variables indexed by integers instead of making them scalar. **indexers_kwarg : {dim: indexer, ...} - The keyword arguments form of ``indexers`` + The keyword arguments form of ``indexers``. + One of indexers or indexers_kwargs must be provided. Returns ------- @@ -1458,6 +1460,7 @@ def sel(self, indexers=None, method=None, tolerance=None, drop=False, matching index level names. If DataArrays are passed as indexers, xarray-style indexing will be carried out. See :ref:`indexing` for the details. + One of indexers or indexers_kwargs must be provided. method : {None, 'nearest', 'pad'/'ffill', 'backfill'/'bfill'}, optional Method to use for inexact matches (requires pandas>=0.16): @@ -1474,7 +1477,8 @@ def sel(self, indexers=None, method=None, tolerance=None, drop=False, If ``drop=True``, drop coordinates variables in `indexers` instead of making them scalar. **indexers_kwargs : {dim: indexer, ...} - The keyword arguments form of ``indexers`` + The keyword arguments form of ``indexers``. + One of indexers or indexers_kwargs must be provided. Returns ------- @@ -1755,6 +1759,7 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, arrays of coordinates tick labels. Any mis-matched coordinate values will be filled in with NaN, and any mis-matched dimension names will simply be ignored. + One of indexers or indexers_kwargs must be provided. method : {None, 'nearest', 'pad'/'ffill', 'backfill'/'bfill'}, optional Method to use for filling index values in ``indexers`` not found in this dataset: @@ -1773,8 +1778,9 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, ``copy=False`` and reindexing is unnecessary, or can be performed with only slice operations, then the output may share memory with the input. In either case, a new xarray object is always returned. - **indexers_kwargs : optional + **indexers_kwargs : optional : {dim: indexer, ...} Keyword arguments in the same form as ``indexers``. + One of indexers or indexers_kwargs must be provided. Returns ------- From 8f01942d076e2a82728687069bd5aba97c6b2d05 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 26 May 2018 00:58:05 -0400 Subject: [PATCH 12/16] `optional` goes last --- xarray/core/dataset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index e7cd5e19ca4..f12e8d549f3 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1388,7 +1388,7 @@ def isel(self, indexers=None, drop=False, **indexers_kwargs): drop : bool, optional If ``drop=True``, drop coordinates variables indexed by integers instead of making them scalar. - **indexers_kwarg : {dim: indexer, ...} + **indexers_kwarg : {dim: indexer, ...}, optional The keyword arguments form of ``indexers``. One of indexers or indexers_kwargs must be provided. @@ -1476,7 +1476,7 @@ def sel(self, indexers=None, method=None, tolerance=None, drop=False, drop : bool, optional If ``drop=True``, drop coordinates variables in `indexers` instead of making them scalar. - **indexers_kwargs : {dim: indexer, ...} + **indexers_kwarg : {dim: indexer, ...}, optional The keyword arguments form of ``indexers``. One of indexers or indexers_kwargs must be provided. @@ -1778,7 +1778,7 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, ``copy=False`` and reindexing is unnecessary, or can be performed with only slice operations, then the output may share memory with the input. In either case, a new xarray object is always returned. - **indexers_kwargs : optional : {dim: indexer, ...} + **indexers_kwarg : {dim: indexer, ...}, optional Keyword arguments in the same form as ``indexers``. One of indexers or indexers_kwargs must be provided. From 186d3e354d2083edd7907fc5662c044e2e3c0693 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 26 May 2018 00:59:43 -0400 Subject: [PATCH 13/16] last docstring --- xarray/core/dataarray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index c5d424e9613..da9acb48a7a 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -886,7 +886,7 @@ def reindex(self, indexers=None, method=None, tolerance=None, copy=True, Maximum distance between original and new labels for inexact matches. The values of the index at the matching locations most satisfy the equation ``abs(index[indexer] - target) <= tolerance``. - **indexers_kwargs : {dim: indexer, ...} + **indexers_kwarg : {dim: indexer, ...}, optional The keyword arguments form of ``indexers``. One of indexers or indexers_kwargs must be provided. From 8c75d1fd97b9775e404492da1b2b7a9c7dd89b02 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 26 May 2018 01:07:34 -0400 Subject: [PATCH 14/16] what's new --- doc/whats-new.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index d9f43fa1868..e9d76b6d1e1 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -46,6 +46,14 @@ Enhancements to manage its version strings. (:issue:`1300`). By `Joe Hamman `_. +- :py:meth:`~DataArray.sel`, :py:meth:`~DataArray.isel` & :py:meth:`~DataArray.reindex`, + (and their :py:class:`Dataset` counterparts) now support supplying a ``dict`` + as a first argument, as an alternative to the existing approach + of supplying a set of `kwargs`. This allows for more robust behavior + of dimension names which conflict with other keyword names, or are + not strings. + By `Maximilian Roos `_. + Bug fixes ~~~~~~~~~ From 2656bead943b0c368f023cdcb3ad802d059f5477 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sun, 27 May 2018 09:45:49 -0400 Subject: [PATCH 15/16] artefact --- xarray/core/dataset.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index f12e8d549f3..d6a5ac1c172 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1452,7 +1452,6 @@ def sel(self, indexers=None, method=None, tolerance=None, drop=False, Parameters ---------- - ---------- indexers : dict, optional A dict with keys matching dimensions and values given by scalars, slices or arrays of tick labels. For dimensions with From e2241df8b643559ee4b2f57482a87db48847cd28 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sun, 27 May 2018 10:01:23 -0400 Subject: [PATCH 16/16] test either_dict_or_kwargs --- xarray/tests/test_utils.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_utils.py b/xarray/tests/test_utils.py index 0b3b0ee7dd6..e1645f2903c 100644 --- a/xarray/tests/test_utils.py +++ b/xarray/tests/test_utils.py @@ -1,17 +1,21 @@ from __future__ import absolute_import, division, print_function +from datetime import datetime + import numpy as np import pandas as pd import pytest -from datetime import datetime from xarray.coding.cftimeindex import CFTimeIndex from xarray.core import duck_array_ops, utils from xarray.core.options import set_options from xarray.core.pycompat import OrderedDict +from xarray.core.utils import either_dict_or_kwargs + +from . import ( + TestCase, assert_array_equal, has_cftime, has_cftime_or_netCDF4, + requires_dask) from .test_coding_times import _all_cftime_date_types -from . import (TestCase, requires_dask, assert_array_equal, - has_cftime_or_netCDF4, has_cftime) class TestAlias(TestCase): @@ -235,3 +239,17 @@ def test_hidden_key_dict(): hkd[hidden_key] with pytest.raises(KeyError): del hkd[hidden_key] + + +def test_either_dict_or_kwargs(): + + result = either_dict_or_kwargs(dict(a=1), None, 'foo') + expected = dict(a=1) + assert result == expected + + result = either_dict_or_kwargs(None, dict(a=1), 'foo') + expected = dict(a=1) + assert result == expected + + with pytest.raises(ValueError, match=r'foo'): + result = either_dict_or_kwargs(dict(a=1), dict(a=1), 'foo')