From 6209c98d6c1f3003084c67d24f61a898142bd752 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 11 Jul 2020 13:35:02 +0200 Subject: [PATCH 01/36] add a function to extract / retrieve unit attributes --- pint_xarray/conversion.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pint_xarray/conversion.py b/pint_xarray/conversion.py index 58252105..1150f24b 100644 --- a/pint_xarray/conversion.py +++ b/pint_xarray/conversion.py @@ -1,3 +1,5 @@ +import itertools + import pint from xarray import DataArray, Dataset, Variable @@ -196,6 +198,24 @@ def extract_units(obj): return units +def extract_unit_attributes(obj, delete=False): + method = dict.pop if delete else dict.get + if isinstance(obj, DataArray): + variables = itertools.chain([(obj.name, obj)], obj.coords.items(),) + units = {name: method(var.attrs, "units", None) for name, var in variables} + elif isinstance(obj, Dataset): + units = { + name: method(var.attrs, "units", None) + for name, var in obj.variables.items() + } + else: + raise ValueError( + f"cannot retrieve unit attributes from unknown type: {type(obj)}" + ) + + return units + + def strip_units(obj): if isinstance(obj, Variable): data = array_strip_units(obj.data) From 0edc9fc7fa3d4e6977ef7c30e7ba7a026a5d69ac Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 11 Jul 2020 13:35:46 +0200 Subject: [PATCH 02/36] add a function to zip mappings --- pint_xarray/accessors.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index ed22e58a..ca0bea65 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -1,4 +1,6 @@ # TODO is it possible to import pint-xarray from within xarray if pint is present? +import itertools + import numpy as np import pint from pint.quantity import Quantity @@ -35,6 +37,33 @@ def is_dict_like(obj): return hasattr(obj, "keys") and hasattr(obj, "__getitem__") +def zip_mappings(*mappings, fill_value=None): + """ zip mappings by combining values for common keys into a tuple + + Works like itertools.zip_longest, so if a key is missing from a + mapping, it is replaced by ``fill_value``. + + Parameters + ---------- + *mappings : dict-like + The mappings to zip + fill_value + The value to use if a key is missing from a mapping. + + Returns + ------- + zipped : dict-like + The zipped mapping + """ + keys = set(itertools.chain.from_iterable(mapping.keys() for mapping in mappings)) + + # TODO: could this be made more efficient using itertools.groupby? + zipped = { + key: tuple(mapping.get(key, fill_value) for mapping in mappings) for key in keys + } + return zipped + + # based on xarray.core.utils.either_dict_or_kwargs # https://github.com/pydata/xarray/blob/v0.15.1/xarray/core/utils.py#L249-L268 def either_dict_or_kwargs(positional, keywords, method_name): From 05ff46a918a62152a73819de1e7ad081395989f5 Mon Sep 17 00:00:00 2001 From: Keewis Date: Sat, 11 Jul 2020 13:38:47 +0200 Subject: [PATCH 03/36] partially rewrite Dataset.pint.quantify --- pint_xarray/accessors.py | 52 ++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index ca0bea65..b4bbf8c0 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -135,11 +135,13 @@ def _get_registry(unit_registry, registry_kwargs): return unit_registry -def _decide_units(units, registry, attrs): - if units is None: +def _decide_units(units, registry, unit_attribute): + if not units and not unit_attribute: + # or warn and return None? + raise ValueError("no units given") + elif not units: # TODO option to read and decode units according to CF conventions (see MetPy)? - attr_units = attrs["units"] - units = registry.parse_expression(attr_units) + units = registry.parse_expression(unit_attribute).units elif isinstance(units, Unit): # TODO do we have to check what happens if someone passes a Unit instance # without creating a unit registry? @@ -173,7 +175,9 @@ class PintDataArrayAccessor: def __init__(self, da): self.da = da - def quantify(self, units=None, unit_registry=None, registry_kwargs=None): + def quantify( + self, units=None, unit_registry=None, registry_kwargs=None, **unit_kwargs + ): """ Attaches units to the DataArray. @@ -185,10 +189,10 @@ def quantify(self, units=None, unit_registry=None, registry_kwargs=None): Parameters ---------- - units : pint.Unit or str, optional + units : pint.Unit or str or mapping of hashable to , optional Physical units to use for this DataArray. If not provided, will try - to read them from `DataArray.attrs['units']` using pint's parser. - unit_registry : `pint.UnitRegistry`, optional + to read them from ``DataArray.attrs['units']`` using pint's parser. + unit_registry : pint.UnitRegistry, optional Unit registry to be used for the units attached to this DataArray. If not given then a default registry will be created. registry_kwargs : dict, optional @@ -196,8 +200,9 @@ def quantify(self, units=None, unit_registry=None, registry_kwargs=None): Returns ------- - quantified - DataArray whose wrapped array data will now be a Quantity - array with the specified units. + quantified : DataArray + DataArray whose wrapped array data will now be a Quantity + array with the specified units. Examples -------- @@ -208,24 +213,35 @@ def quantify(self, units=None, unit_registry=None, registry_kwargs=None): * wavelength (wavelength) np.array 1e-4, 2e-4, 4e-4, 6e-4, 1e-3, 2e-3 """ - # TODO should also quantify coordinates (once explicit indexes ready) - if isinstance(self.da.data, Quantity): raise ValueError( f"Cannot attach unit {units} to quantity: data " f"already has units {self.da.data.units}" ) - registry = _get_registry(unit_registry, registry_kwargs) + if isinstance(units, (str, pint.Unit)): + if self.da.name in unit_kwargs: + raise ValueError( + f"ambiguous values given for {repr(self.da.name)}:" + f" {repr(units)} and {repr(unit_kwargs[self.da.name])}" + ) + unit_kwargs[self.da.name] = units + units = None - units = _decide_units(units, registry, self.da.attrs) + units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") - quantity = _array_attach_units(self.da.data, units, convert_from=None) + registry = _get_registry(unit_registry, registry_kwargs) # TODO should we (temporarily) remove the attrs here so that they don't become inconsistent? - return DataArray( - dims=self.da.dims, data=quantity, coords=self.da.coords, attrs=self.da.attrs - ) + unit_attrs = conversion.extract_unit_attributes(self.da, delete=False) + + units = { + name: _decide_units(unit, registry, unit_attribute) + for name, (unit, unit_attribute) in zip_mappings(units, unit_attrs).items() + if unit is not None or unit_attribute is not None + } + + return conversion.attach_units(self.da, units) def dequantify(self): """ From a831408957dc4639362ad74653f94ec99aa0d02f Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:16:01 +0200 Subject: [PATCH 04/36] add tests for the unit attribute extraction function --- pint_xarray/tests/test_conversion.py | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pint_xarray/tests/test_conversion.py b/pint_xarray/tests/test_conversion.py index e77366d2..be0a7775 100644 --- a/pint_xarray/tests/test_conversion.py +++ b/pint_xarray/tests/test_conversion.py @@ -309,6 +309,41 @@ def test_extract_units(self, typename, units): assert conversion.extract_units(obj) == units + @pytest.mark.parametrize( + ["obj", "expected"], + ( + pytest.param( + DataArray( + coords={ + "x": ("x", [], {"units": "m"}), + "u": ("x", [], {"units": "s"}), + }, + attrs={"units": "hPa"}, + dims="x", + ), + {"x": "m", "u": "s", None: "hPa"}, + id="DataArray", + ), + pytest.param( + Dataset( + data_vars={ + "a": ("x", [], {"units": "degK"}), + "b": ("x", [], {"units": "hPa"}), + }, + coords={ + "x": ("x", [], {"units": "m"}), + "u": ("x", [], {"units": "s"}), + }, + ), + {"a": "degK", "b": "hPa", "x": "m", "u": "s"}, + id="Dataset", + ), + ), + ) + def test_extract_unit_attributes(self, obj, expected): + actual = conversion.extract_unit_attributes(obj, delete=False) + assert expected == actual + @pytest.mark.parametrize( "obj", ( From a4dd9e5cd8a80ce9e919c13a20776587623e2a07 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:23:42 +0200 Subject: [PATCH 05/36] also make sure that deleting attributes works --- pint_xarray/tests/test_conversion.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pint_xarray/tests/test_conversion.py b/pint_xarray/tests/test_conversion.py index be0a7775..05531259 100644 --- a/pint_xarray/tests/test_conversion.py +++ b/pint_xarray/tests/test_conversion.py @@ -309,6 +309,9 @@ def test_extract_units(self, typename, units): assert conversion.extract_units(obj) == units + @pytest.mark.parametrize( + "delete", (pytest.param(False, id="keep"), pytest.param(True, id="delete")) + ) @pytest.mark.parametrize( ["obj", "expected"], ( @@ -340,10 +343,18 @@ def test_extract_units(self, typename, units): ), ), ) - def test_extract_unit_attributes(self, obj, expected): - actual = conversion.extract_unit_attributes(obj, delete=False) + def test_extract_unit_attributes(self, obj, expected, delete): + actual = conversion.extract_unit_attributes(obj, delete=delete) assert expected == actual + if delete: + remaining_attributes = conversion.extract_unit_attributes(obj, delete=False) + assert { + key: value + for key, value in remaining_attributes.items() + if value is not None + } == {} + @pytest.mark.parametrize( "obj", ( From 96d64c9c4fd699774c465f10a1dcce541eacc471 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:43:19 +0200 Subject: [PATCH 06/36] add a function to attach units as string attributes --- pint_xarray/conversion.py | 26 +++++++++++++++++++++++++- pint_xarray/tests/test_conversion.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/pint_xarray/conversion.py b/pint_xarray/conversion.py index 1150f24b..ed30457e 100644 --- a/pint_xarray/conversion.py +++ b/pint_xarray/conversion.py @@ -132,6 +132,30 @@ def attach_units(obj, units, registry=None): return new_obj +def attach_unit_attributes(obj, units, attr="units"): + new_obj = obj.copy() + if isinstance(obj, DataArray): + for name, var in itertools.chain([(obj.name, new_obj)], new_obj.coords.items()): + unit = units.get(name) + if unit is None: + continue + + var.attrs[attr] = unit + elif isinstance(obj, Dataset): + for name, var in new_obj.variables.items(): + unit = units.get(name) + if unit is None: + continue + + var.attrs[attr] = unit + elif isinstance(obj, Variable): + new_obj.attrs[attr] = units.get(None) + else: + raise ValueError(f"cannot attach unit attributes to {obj!r}: unknown type") + + return new_obj + + def convert_units(obj, units): if not isinstance(units, dict): units = {None: units} @@ -201,7 +225,7 @@ def extract_units(obj): def extract_unit_attributes(obj, delete=False): method = dict.pop if delete else dict.get if isinstance(obj, DataArray): - variables = itertools.chain([(obj.name, obj)], obj.coords.items(),) + variables = itertools.chain([(obj.name, obj)], obj.coords.items()) units = {name: method(var.attrs, "units", None) for name, var in variables} elif isinstance(obj, Dataset): units = { diff --git a/pint_xarray/tests/test_conversion.py b/pint_xarray/tests/test_conversion.py index 05531259..0ada0c26 100644 --- a/pint_xarray/tests/test_conversion.py +++ b/pint_xarray/tests/test_conversion.py @@ -176,6 +176,34 @@ def test_attach_units(self, obj, units): assert conversion.extract_units(actual) == units + @pytest.mark.parametrize( + ["obj", "units"], + ( + pytest.param( + DataArray(dims="x", coords={"x": [], "u": ("x", [])}), + {None: "hPa", "x": "m"}, + id="DataArray", + ), + pytest.param( + Dataset( + data_vars={"a": ("x", []), "b": ("x", [])}, + coords={"x": [], "u": ("x", [])}, + ), + {"a": "degK", "b": "hPa", "u": "m"}, + id="Dataset", + ), + pytest.param(Variable("x", []), {None: "hPa"}, id="Variable",), + ), + ) + def test_attach_unit_attributes(self, obj, units): + actual = conversion.attach_unit_attributes(obj, units) + actual_units = { + key: value + for key, value in conversion.extract_unit_attributes(actual).items() + if value is not None + } + assert units == actual_units + @pytest.mark.parametrize( "variant", ( From 5cb7605669c0032a561121443983959d62ede83d Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:46:43 +0200 Subject: [PATCH 07/36] also extract unit attributes from variable objects --- pint_xarray/conversion.py | 2 ++ pint_xarray/tests/test_conversion.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/pint_xarray/conversion.py b/pint_xarray/conversion.py index ed30457e..1bf79c2a 100644 --- a/pint_xarray/conversion.py +++ b/pint_xarray/conversion.py @@ -232,6 +232,8 @@ def extract_unit_attributes(obj, delete=False): name: method(var.attrs, "units", None) for name, var in obj.variables.items() } + elif isinstance(obj, Variable): + units = {None: method(obj.attrs, "units", None)} else: raise ValueError( f"cannot retrieve unit attributes from unknown type: {type(obj)}" diff --git a/pint_xarray/tests/test_conversion.py b/pint_xarray/tests/test_conversion.py index 0ada0c26..2a6baccd 100644 --- a/pint_xarray/tests/test_conversion.py +++ b/pint_xarray/tests/test_conversion.py @@ -369,6 +369,9 @@ def test_extract_units(self, typename, units): {"a": "degK", "b": "hPa", "x": "m", "u": "s"}, id="Dataset", ), + pytest.param( + Variable("x", [], {"units": "hPa"}), {None: "hPa"}, id="Variable", + ), ), ) def test_extract_unit_attributes(self, obj, expected, delete): From 33e602709ced1a01396051ec1179351d4e3bedb4 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:47:12 +0200 Subject: [PATCH 08/36] refactor the unit attr extraction tests --- pint_xarray/tests/test_conversion.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pint_xarray/tests/test_conversion.py b/pint_xarray/tests/test_conversion.py index 2a6baccd..1f211f10 100644 --- a/pint_xarray/tests/test_conversion.py +++ b/pint_xarray/tests/test_conversion.py @@ -379,12 +379,14 @@ def test_extract_unit_attributes(self, obj, expected, delete): assert expected == actual if delete: - remaining_attributes = conversion.extract_unit_attributes(obj, delete=False) - assert { + remaining_attributes = { key: value - for key, value in remaining_attributes.items() + for key, value in conversion.extract_unit_attributes( + obj, delete=False + ).items() if value is not None - } == {} + } + assert remaining_attributes == {} @pytest.mark.parametrize( "obj", From 0dbec68505ee9a325b5a32fee155e787d5f81e84 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:47:44 +0200 Subject: [PATCH 09/36] allow using a different attr name --- pint_xarray/conversion.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pint_xarray/conversion.py b/pint_xarray/conversion.py index 1bf79c2a..570871b2 100644 --- a/pint_xarray/conversion.py +++ b/pint_xarray/conversion.py @@ -222,18 +222,17 @@ def extract_units(obj): return units -def extract_unit_attributes(obj, delete=False): +def extract_unit_attributes(obj, delete=False, attr="units"): method = dict.pop if delete else dict.get if isinstance(obj, DataArray): variables = itertools.chain([(obj.name, obj)], obj.coords.items()) - units = {name: method(var.attrs, "units", None) for name, var in variables} + units = {name: method(var.attrs, attr, None) for name, var in variables} elif isinstance(obj, Dataset): units = { - name: method(var.attrs, "units", None) - for name, var in obj.variables.items() + name: method(var.attrs, attr, None) for name, var in obj.variables.items() } elif isinstance(obj, Variable): - units = {None: method(obj.attrs, "units", None)} + units = {None: method(obj.attrs, attr, None)} else: raise ValueError( f"cannot retrieve unit attributes from unknown type: {type(obj)}" From cb2fa65c1ed61f3460ebbe72ec4e2a8fee7b4791 Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:54:47 +0200 Subject: [PATCH 10/36] use the conversion functions to implement dequantify --- pint_xarray/accessors.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index b4bbf8c0..0ff273fa 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -256,20 +256,12 @@ def dequantify(self): that was previously wrapped by `pint.Quantity`. """ - if not isinstance(self.da.data, Quantity): - raise ValueError( - "Cannot remove units from data that does not have" " units" - ) - - # TODO also dequantify coords (once explicit indexes ready) - da = DataArray( - dims=self.da.dims, - data=self.da.pint.magnitude, - coords=self.da.coords, - attrs=self.da.attrs, + units = conversion.extract_units(self.da) + new_obj = conversion.attach_unit_attributes( + conversion.strip_units(self.da), units, ) - da.attrs["units"] = str(self.da.data.units) - return da + + return new_obj @property def magnitude(self): From 29add91f3dbfdb5725958c21bff50af3bd3cbf5a Mon Sep 17 00:00:00 2001 From: Keewis Date: Mon, 13 Jul 2020 17:55:07 +0200 Subject: [PATCH 11/36] update the docs on the return value --- pint_xarray/accessors.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 0ff273fa..6cbeac2f 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -252,8 +252,9 @@ def dequantify(self): Returns ------- - dequantified - DataArray whose array data is unitless, and of the type - that was previously wrapped by `pint.Quantity`. + dequantified : DataArray + DataArray whose array data is unitless, and of the type + that was previously wrapped by `pint.Quantity`. """ units = conversion.extract_units(self.da) From a8b14afd6ce8540cc2e3fc78ec7893507f737135 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 01:02:44 +0200 Subject: [PATCH 12/36] update the conditions for _decide_units --- pint_xarray/accessors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 6cbeac2f..5812a19b 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -136,10 +136,10 @@ def _get_registry(unit_registry, registry_kwargs): def _decide_units(units, registry, unit_attribute): - if not units and not unit_attribute: + if units is None and unit_attribute is None: # or warn and return None? raise ValueError("no units given") - elif not units: + elif units is None: # TODO option to read and decode units according to CF conventions (see MetPy)? units = registry.parse_expression(unit_attribute).units elif isinstance(units, Unit): From 1fde9a0e31db18751182a6a2b0b29e9286dd99de Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 01:10:27 +0200 Subject: [PATCH 13/36] rewrite the Dataset quantify and dequantify methods --- pint_xarray/accessors.py | 42 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 5812a19b..a475cf02 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -438,7 +438,9 @@ class PintDatasetAccessor: def __init__(self, ds): self.ds = ds - def quantify(self, units=None, unit_registry=None, registry_kwargs=None): + def quantify( + self, units=None, unit_registry=None, registry_kwargs=None, **unit_kwargs + ): """ Attaches units to each variable in the Dataset. @@ -466,41 +468,23 @@ def quantify(self, units=None, unit_registry=None, registry_kwargs=None): quantified - Dataset whose variables will now contain Quantity arrays with units. """ - - for var in self.ds.data_vars: - if isinstance(self.ds[var].data, Quantity): - raise ValueError( - f"Cannot attach unit to quantity: data " - f"variable {var} already has units " - f"{self.ds[var].data.units}" - ) - + units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") registry = _get_registry(unit_registry, registry_kwargs) - if units is None: - units = {name: None for name in self.ds} - + # TODO should we (temporarily) remove the attrs here so that they don't become inconsistent? + unit_attrs = conversion.extract_unit_attributes(self.ds, delete=False) units = { - name: _decide_units(units.get(name, None), registry, var.attrs) - for name, var in self.ds.data_vars.items() - } - - quantified_vars = { - name: _quantify_variable(var, units[name]) - for name, var in self.ds.data_vars.items() + name: _decide_units(unit, registry, attr) + for name, (unit, attr) in zip_mappings(units, unit_attrs).items() + if unit is not None or attr is not None } - # TODO should also quantify coordinates (once explicit indexes ready) - # TODO should we (temporarily) remove the attrs here so that they don't become inconsistent? - return Dataset( - data_vars=quantified_vars, coords=self.ds.coords, attrs=self.ds.attrs - ) + return conversion.attach_units(self.ds, units) def dequantify(self): - dequantified_vars = { - name: da.pint.to_base_units() for name, da in self.ds.items() - } - return Dataset(dequantified_vars, coords=self.ds.coords, attrs=self.ds.attrs) + units = conversion.extract_units(self.ds) + new_obj = conversion.attach_units(conversion.strip_units(self.ds), units) + return new_obj def to(self, units=None, **unit_kwargs): """ convert the quantities in a DataArray From 6868442817e10519fa3dbbef413c948472667841 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 13:54:03 +0200 Subject: [PATCH 14/36] raise on unit stripped warnings --- pint_xarray/tests/test_accessors.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index e8e5b227..13790348 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -8,27 +8,38 @@ from .utils import raises_regex +pytestmark = [ + pytest.mark.filterwarnings("error::pint.UnitStrippedWarning"), +] + # make sure scalars are converted to 0d arrays so quantities can # always be treated like ndarrays unit_registry = UnitRegistry(force_ndarray=True) Quantity = unit_registry.Quantity -@pytest.fixture() +@pytest.fixture def example_unitless_da(): array = np.linspace(0, 10, 20) x = np.arange(20) - da = xr.DataArray(data=array, dims="x", coords={"x": x}) - da.attrs["units"] = "m" - da.coords["x"].attrs["units"] = "s" + u = np.linspace(0, 1, 20) + da = xr.DataArray( + data=array, + dims="x", + coords={"x": ("x", x, {"units": "s"}), "u": ("x", u, {"units": "hour"})}, + attrs={"units": "m"}, + ) return da @pytest.fixture() def example_quantity_da(): array = np.linspace(0, 10, 20) * unit_registry.m - x = np.arange(20) * unit_registry.s - return xr.DataArray(data=array, dims="x", coords={"x": x}) + x = np.arange(20) + u = np.linspace(0, 1, 20) * unit_registry.hour + return xr.DataArray( + data=array, dims="x", coords={"x": ("x", x, {"units": "s"}), "u": ("x", u)}, + ) class TestQuantifyDataArray: From 68df60c76ad7b1a002c7831c2baa876cebbb6b06 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 15:18:04 +0200 Subject: [PATCH 15/36] remove the test checking that dequantify raises if there were no quantities --- pint_xarray/tests/test_accessors.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index 13790348..0f377c1e 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -92,10 +92,6 @@ def test_strip_units(self, example_quantity_da): assert isinstance(result.data, np.ndarray) assert isinstance(result.coords["x"].data, np.ndarray) - def test_error_if_no_units(self, example_unitless_da): - with raises_regex(ValueError, "does not have units"): - example_unitless_da.pint.dequantify() - def test_attrs_reinstated(self, example_quantity_da): da = example_quantity_da result = da.pint.dequantify() From ce4035677b166b2bb9f3ee582e80f06fe66556f4 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 15:18:44 +0200 Subject: [PATCH 16/36] make sure Dataset.pint.quantify raises only if we would overwrite a quantity --- pint_xarray/tests/test_accessors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index 0f377c1e..ecfc8abd 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -179,7 +179,7 @@ def test_attach_units_given_unit_objs(self, example_unitless_ds): def test_error_when_already_units(self, example_quantity_ds): with raises_regex(ValueError, "already has units"): - example_quantity_ds.pint.quantify() + example_quantity_ds.pint.quantify({"funds": "pounds"}) def test_error_on_nonsense_units(self, example_unitless_ds): ds = example_unitless_ds From c4dca2077ce7f2164041fd4bda6493584ff8fb68 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 15:29:28 +0200 Subject: [PATCH 17/36] don't try to put units in indexes --- pint_xarray/tests/test_accessors.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index ecfc8abd..7d341d9c 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -26,7 +26,7 @@ def example_unitless_da(): da = xr.DataArray( data=array, dims="x", - coords={"x": ("x", x, {"units": "s"}), "u": ("x", u, {"units": "hour"})}, + coords={"x": ("x", x), "u": ("x", u, {"units": "hour"})}, attrs={"units": "m"}, ) return da @@ -37,9 +37,7 @@ def example_quantity_da(): array = np.linspace(0, 10, 20) * unit_registry.m x = np.arange(20) u = np.linspace(0, 1, 20) * unit_registry.hour - return xr.DataArray( - data=array, dims="x", coords={"x": ("x", x, {"units": "s"}), "u": ("x", u)}, - ) + return xr.DataArray(data=array, dims="x", coords={"x": ("x", x), "u": ("x", u)}) class TestQuantifyDataArray: From 61cc2e030e3faf056e6a6cc0b86974874d586a5b Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 15:30:13 +0200 Subject: [PATCH 18/36] check the attributes after dequantify more thoroughly --- pint_xarray/tests/test_accessors.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index 7d341d9c..65a25006 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -6,6 +6,7 @@ from pint.errors import UndefinedUnitError from xarray.testing import assert_equal +from .. import conversion from .utils import raises_regex pytestmark = [ @@ -18,6 +19,18 @@ Quantity = unit_registry.Quantity +def assert_all_str_or_none(mapping): + __tracebackhide__ = True + + compared = { + key: isinstance(value, str) or value is None for key, value in mapping.items() + } + not_passing = {key: value for key, value in mapping.items() if not compared[key]} + check = all(compared.values()) + + assert check, f"Not all values are str or None: {not_passing}" + + @pytest.fixture def example_unitless_da(): array = np.linspace(0, 10, 20) @@ -93,7 +106,12 @@ def test_strip_units(self, example_quantity_da): def test_attrs_reinstated(self, example_quantity_da): da = example_quantity_da result = da.pint.dequantify() - assert result.attrs["units"] == "meter" + + units = conversion.extract_units(da) + attrs = conversion.extract_unit_attributes(result) + + assert units == attrs + assert_all_str_or_none(attrs) def test_roundtrip_data(self, example_unitless_da): orig = example_unitless_da From 43e91f05da2ad3fee8b3ad1d8a3c9b109aaec4be Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 15:32:39 +0200 Subject: [PATCH 19/36] make sure the attributes are strings --- pint_xarray/accessors.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index a475cf02..393cc569 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -257,7 +257,10 @@ def dequantify(self): that was previously wrapped by `pint.Quantity`. """ - units = conversion.extract_units(self.da) + units = { + key: str(value) if isinstance(value, pint.Unit) else value + for key, value in conversion.extract_units(self.da).items() + } new_obj = conversion.attach_unit_attributes( conversion.strip_units(self.da), units, ) From c1c5e52ee53f9a55cc360bb9eedb530748649c1b Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 16:38:32 +0200 Subject: [PATCH 20/36] move the str conversion of pint.Unit to a util function --- pint_xarray/accessors.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 393cc569..3ccd0209 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -64,6 +64,13 @@ def zip_mappings(*mappings, fill_value=None): return zipped +def units_to_str_or_none(mapping): + return { + key: str(value) if isinstance(value, Unit) else value + for key, value in mapping.items() + } + + # based on xarray.core.utils.either_dict_or_kwargs # https://github.com/pydata/xarray/blob/v0.15.1/xarray/core/utils.py#L249-L268 def either_dict_or_kwargs(positional, keywords, method_name): @@ -257,10 +264,7 @@ def dequantify(self): that was previously wrapped by `pint.Quantity`. """ - units = { - key: str(value) if isinstance(value, pint.Unit) else value - for key, value in conversion.extract_units(self.da).items() - } + units = units_to_str_or_none(conversion.extract_units(self.da)) new_obj = conversion.attach_unit_attributes( conversion.strip_units(self.da), units, ) @@ -485,8 +489,10 @@ def quantify( return conversion.attach_units(self.ds, units) def dequantify(self): - units = conversion.extract_units(self.ds) - new_obj = conversion.attach_units(conversion.strip_units(self.ds), units) + units = units_to_str_or_none(conversion.extract_units(self.ds)) + new_obj = conversion.attach_unit_attributes( + conversion.strip_units(self.ds), units + ) return new_obj def to(self, units=None, **unit_kwargs): From 1ead7005993230916c9d9914fe2e645f06107a5f Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 16:41:37 +0200 Subject: [PATCH 21/36] update the dequantify docstrings --- pint_xarray/accessors.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 3ccd0209..6b7e07ef 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -252,9 +252,9 @@ def quantify( def dequantify(self): """ - Removes units from the DataArray and it's coordinates. + Removes units from the DataArray and its coordinates. - Will replace `.attrs['units']` on each variable with a string + Will replace ``.attrs['units']`` on each variable with a string representation of the `pint.Unit` instance. Returns @@ -489,6 +489,18 @@ def quantify( return conversion.attach_units(self.ds, units) def dequantify(self): + """ + Removes units from the Dataset and its coordinates. + + Will replace ``.attrs['units']`` on each variable with a string + representation of the :py:class:`pint.Unit` instance. + + Returns + ------- + dequantified : Dataset + Dataset whose data variables are unitless, and of the type + that was previously wrapped by :py:class:`pint.Quantity`. + """ units = units_to_str_or_none(conversion.extract_units(self.ds)) new_obj = conversion.attach_unit_attributes( conversion.strip_units(self.ds), units From a34c75304a932b0c991f19b3a07ef7c75ba10a4e Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 16:47:15 +0200 Subject: [PATCH 22/36] update the quantify docstrings --- pint_xarray/accessors.py | 61 ++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 6b7e07ef..c75b58ea 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -196,14 +196,16 @@ def quantify( Parameters ---------- - units : pint.Unit or str or mapping of hashable to , optional - Physical units to use for this DataArray. If not provided, will try + units : pint.Unit or str or mapping of hashable to pint.Unit or str, optional + Physical units to use for this DataArray: . If not provided, will try to read them from ``DataArray.attrs['units']`` using pint's parser. unit_registry : pint.UnitRegistry, optional Unit registry to be used for the units attached to this DataArray. If not given then a default registry will be created. registry_kwargs : dict, optional Keyword arguments to be passed to `pint.UnitRegistry`. + **unit_kwargs + Keyword argument form of units. Returns ------- @@ -213,6 +215,11 @@ def quantify( Examples -------- + >>> da = xr.DataArray( + ... data=[0.4, 0.9, 1.7, 4.8, 3.2, 9.1], + ... dims="frequency", + ... coords={"wavelength": [1e-4, 2e-4, 4e-4, 6e-4, 1e-3, 2e-3]}, + ... ) >>> da.pint.quantify(units='Hz') Quantity([ 0.4, 0.9, 1.7, 4.8, 3.2, 9.1], 'Hz') @@ -451,29 +458,59 @@ def quantify( """ Attaches units to each variable in the Dataset. - Units can be specified as a pint.Unit or as a string, which will - be parsed by the given unit registry. If no units are specified then - the units will be parsed from the `'units'` entry of the DataArray's - `.attrs`. Will raise a ValueError if any of the DataArrays already - contain a unit-aware array. + Units can be specified as a :py:class:`pint.Unit` or as a + string, which will be parsed by the given unit registry. If no + units are specified then the units will be parsed from the + ``"units"`` entry of the Dataset variable's ``.attrs``. Will + raise a ValueError if any of the variables already contain a + unit-aware array. Parameters ---------- units : mapping from variable names to pint.Unit or str, optional - Physical units to use for particular DataArrays in this Dataset. If - not provided, will try to read them from - `Dataset[var].attrs['units']` using pint's parser. + Physical units to use for particular DataArrays in this + Dataset. If not provided, will try to read them from + ``Dataset[var].attrs['units']`` using pint's parser. unit_registry : `pint.UnitRegistry`, optional Unit registry to be used for the units attached to each DataArray in this Dataset. If not given then a default registry will be created. registry_kwargs : dict, optional Keyword arguments to be passed to `pint.UnitRegistry`. + **unit_kwargs + Keyword argument form of ``units``. Returns ------- - quantified - Dataset whose variables will now contain Quantity - arrays with units. + quantified : Dataset + Dataset whose variables will now contain Quantity arrays + with units. + + Examples + -------- + >>> ds = xr.Dataset( + ... {"a": ("x", [0, 3, 2], {"units": "m"}), "b": ("x", 5, -2, 1)}, + ... coords={"x": [0, 1, 2], "u": ("x", [-1, 0, 1], {"units": "s"})}, + ... ) + + >>> ds.pint.quantify() + + Dimensions: (x: 3) + Coordinates: + * x (x) int64 0 1 2 + u (x) int64 + Data variables: + a (x) int64 + b (x) int64 5 -2 1 + >>> ds.pint.quantify({"b": "dm"}) + + Dimensions: (x: 3) + Coordinates: + * x (x) int64 0 1 2 + u (x) int64 + Data variables: + a (x) int64 + b (x) int64 """ units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") registry = _get_registry(unit_registry, registry_kwargs) From 66cfdbd1d7d4dcf01eeff9ddf08a8e9d8633fb46 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 17:06:49 +0200 Subject: [PATCH 23/36] add tests for Dataset.pint.dequantify --- pint_xarray/tests/test_accessors.py | 36 ++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index 65a25006..b1e11050 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -2,7 +2,7 @@ import pytest import xarray as xr from numpy.testing import assert_array_equal -from pint import UnitRegistry +from pint import Unit, UnitRegistry from pint.errors import UndefinedUnitError from xarray.testing import assert_equal @@ -203,9 +203,39 @@ def test_error_on_nonsense_units(self, example_unitless_ds): ds.pint.quantify(units={"users": "aecjhbav"}) -@pytest.mark.skip(reason="Not yet implemented") class TestDequantifyDataSet: - ... + def test_strip_units(self, example_quantity_ds): + result = example_quantity_ds.pint.dequantify() + + assert all( + isinstance(var.data, np.ndarray) for var in result.variables.values() + ) + + def test_attrs_reinstated(self, example_quantity_ds): + ds = example_quantity_ds + result = ds.pint.dequantify() + + units = conversion.extract_units(ds) + # workaround for Unit("dimensionless") != str(Unit("dimensionless")) + units = { + key: str(value) if isinstance(value, Unit) else value + for key, value in units.items() + } + + attrs = conversion.extract_unit_attributes(result) + + assert units == attrs + assert_all_str_or_none(attrs) + + def test_roundtrip_data(self, example_unitless_ds): + orig = example_unitless_ds + quantified = orig.pint.quantify() + + result = quantified.pint.dequantify() + assert_equal(result, orig) + + result = quantified.pint.dequantify().pint.quantify() + assert_equal(quantified, result) @pytest.mark.skip(reason="Not yet implemented") From ee036b3c4e6985e72cc55dd4e066e8bdb612f025 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 17:13:54 +0200 Subject: [PATCH 24/36] update the parameter spec of Dataset.pint.quantify --- pint_xarray/accessors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index c75b58ea..63aef3d5 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -467,7 +467,7 @@ def quantify( Parameters ---------- - units : mapping from variable names to pint.Unit or str, optional + units : mapping of hashable to pint.Unit or str, optional Physical units to use for particular DataArrays in this Dataset. If not provided, will try to read them from ``Dataset[var].attrs['units']`` using pint's parser. From 75d26ad86e67778c2ad4f310c80e348b0430742d Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 17:20:55 +0200 Subject: [PATCH 25/36] remove the old attach and quantify functions --- pint_xarray/accessors.py | 59 +--------------------------------------- 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 63aef3d5..209469be 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -1,14 +1,12 @@ # TODO is it possible to import pint-xarray from within xarray if pint is present? import itertools -import numpy as np import pint from pint.quantity import Quantity from pint.unit import Unit from xarray import ( DataArray, Dataset, - Variable, register_dataarray_accessor, register_dataset_accessor, ) @@ -89,49 +87,6 @@ def either_dict_or_kwargs(positional, keywords, method_name): return keywords -def _array_attach_units(data, unit, convert_from=None): - """ - Internal utility function for attaching units to a numpy-like array, - converting them, or throwing the correct error. - """ - - if isinstance(data, Quantity): - if not convert_from: - raise ValueError( - f"Cannot attach unit {unit} to quantity: data " - f"already has units {data.units}" - ) - elif isinstance(convert_from, Unit): - data = data.magnitude - elif convert_from is True: # intentionally accept exactly true - if data.check(unit): - convert_from = data.units - data = data.magnitude - else: - raise ValueError( - "Cannot convert quantity from {data.units} " "to {unit}" - ) - else: - raise ValueError("Cannot convert from invalid unit {convert_from}") - - # to make sure we also encounter the case of "equal if converted" - if convert_from is not None: - quantity = (data * convert_from).to( - unit if isinstance(unit, Unit) else unit.dimensionless - ) - else: - try: - quantity = data * unit - except np.core._exceptions.UFuncTypeError: - # from @keewis in xarray.tests.test_units - unsure what this checks? - if unit != 1: - raise - - quantity = data - - return quantity - - def _get_registry(unit_registry, registry_kwargs): if unit_registry is None: if registry_kwargs is None: @@ -159,18 +114,6 @@ def _decide_units(units, registry, unit_attribute): return units -def _quantify_variable(var, units): - new_data = _array_attach_units(var.data, units, convert_from=None) - new_var = Variable(dims=var.dims, data=new_data, attrs=var.attrs) - return new_var - - -def _dequantify_variable(var): - new_var = Variable(dims=var.dims, data=var.data.magnitude, attrs=var.attrs) - new_var.attrs["units"] = str(var.data.units) - return new_var - - @register_dataarray_accessor("pint") class PintDataArrayAccessor: """ @@ -288,7 +231,7 @@ def units(self): @units.setter def units(self, units): - quantity = _array_attach_units(self.da.data, units) + quantity = conversion.array_attach_units(self.da.data, units) self.da = DataArray( dim=self.da.dims, data=quantity, coords=self.da.coords, attrs=self.da.attrs ) From 72c6cb90a8c5946f4a5dc6c153077bab50717688 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 17:23:17 +0200 Subject: [PATCH 26/36] fix the template used to format accessor attributes --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 4bcb96aa..e902c069 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -21,7 +21,7 @@ DataArray --------- .. autosummary:: :toctree: generated/ - :template: autosummary/accessor_property.rst + :template: autosummary/accessor_attribute.rst DataArray.pint.magnitude DataArray.pint.units From bb38e642d2e9e4c1d3bf79b5deb1af428d39711a Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 17:34:44 +0200 Subject: [PATCH 27/36] remove the attrs on quantify --- pint_xarray/accessors.py | 6 ++---- pint_xarray/tests/test_accessors.py | 6 ++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 209469be..453e11a8 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -189,8 +189,7 @@ def quantify( registry = _get_registry(unit_registry, registry_kwargs) - # TODO should we (temporarily) remove the attrs here so that they don't become inconsistent? - unit_attrs = conversion.extract_unit_attributes(self.da, delete=False) + unit_attrs = conversion.extract_unit_attributes(self.da, delete=True) units = { name: _decide_units(unit, registry, unit_attribute) @@ -458,8 +457,7 @@ def quantify( units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") registry = _get_registry(unit_registry, registry_kwargs) - # TODO should we (temporarily) remove the attrs here so that they don't become inconsistent? - unit_attrs = conversion.extract_unit_attributes(self.ds, delete=False) + unit_attrs = conversion.extract_unit_attributes(self.ds, delete=True) units = { name: _decide_units(unit, registry, attr) for name, (unit, attr) in zip_mappings(units, unit_attrs).items() diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index b1e11050..648059fe 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -74,6 +74,9 @@ def test_attach_units_from_attrs(self, example_unitless_da): assert_array_equal(result.data.magnitude, orig.data) assert str(result.data.units) == "meter" + remaining_attrs = conversion.extract_unit_attributes(result) + assert {k: v for k, v in remaining_attrs.items() if v is not None} == {} + def test_attach_units_given_unit_objs(self, example_unitless_da): orig = example_unitless_da ureg = UnitRegistry(force_ndarray=True) @@ -185,6 +188,9 @@ def test_attach_units_from_attrs(self, example_unitless_ds): assert_array_equal(result["users"].data.magnitude, orig["users"].data) assert str(result["users"].data.units) == "dimensionless" + remaining_attrs = conversion.extract_unit_attributes(result) + assert {k: v for k, v in remaining_attrs.items() if v is not None} == {} + def test_attach_units_given_unit_objs(self, example_unitless_ds): orig = example_unitless_ds orig["users"].attrs.clear() From fc5d615553fce342af243001a99ee4b684920be2 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 17:39:11 +0200 Subject: [PATCH 28/36] make the description of the units kwargs easier to understand --- pint_xarray/accessors.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 453e11a8..1c5e644b 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -140,8 +140,12 @@ def quantify( Parameters ---------- units : pint.Unit or str or mapping of hashable to pint.Unit or str, optional - Physical units to use for this DataArray: . If not provided, will try - to read them from ``DataArray.attrs['units']`` using pint's parser. + Physical units to use for this DataArray. If a str or + pint.Unit, will be used as the DataArray's units. If a + dict-like, it should map a variable name to the desired + unit (use the DataArray's name to refer to its data). If + not provided, will try to read them from + ``DataArray.attrs['units']`` using pint's parser. unit_registry : pint.UnitRegistry, optional Unit registry to be used for the units attached to this DataArray. If not given then a default registry will be created. @@ -411,12 +415,13 @@ def quantify( ---------- units : mapping of hashable to pint.Unit or str, optional Physical units to use for particular DataArrays in this - Dataset. If not provided, will try to read them from + Dataset. It should map variable names to units. If not + provided, will try to read them from ``Dataset[var].attrs['units']`` using pint's parser. unit_registry : `pint.UnitRegistry`, optional - Unit registry to be used for the units attached to each DataArray - in this Dataset. If not given then a default registry will be - created. + Unit registry to be used for the units attached to each + DataArray in this Dataset. If not given then a default + registry will be created. registry_kwargs : dict, optional Keyword arguments to be passed to `pint.UnitRegistry`. **unit_kwargs From c4f74dbe3ea4d2e95953514b3e4542ad7445e5ad Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 17:42:52 +0200 Subject: [PATCH 29/36] mention the format of the units --- pint_xarray/accessors.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 1c5e644b..479f207c 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -150,7 +150,7 @@ def quantify( Unit registry to be used for the units attached to this DataArray. If not given then a default registry will be created. registry_kwargs : dict, optional - Keyword arguments to be passed to `pint.UnitRegistry`. + Keyword arguments to be passed to :py:class:`pint.UnitRegistry`. **unit_kwargs Keyword argument form of units. @@ -208,7 +208,7 @@ def dequantify(self): Removes units from the DataArray and its coordinates. Will replace ``.attrs['units']`` on each variable with a string - representation of the `pint.Unit` instance. + representation of the :py:class:`pint.Unit` instance. Returns ------- @@ -259,7 +259,7 @@ def to(self, units=None, **unit_kwargs): ---------- units : str or pint.Unit or mapping of hashable to str or pint.Unit, optional The units to convert to. If a unit name or - :py:class`pint.Unit` object, convert the DataArray's + :py:class:`pint.Unit` object, convert the DataArray's data. If a dict-like, it has to map a variable name to a unit name or :py:class:`pint.Unit` object. **unit_kwargs @@ -415,10 +415,11 @@ def quantify( ---------- units : mapping of hashable to pint.Unit or str, optional Physical units to use for particular DataArrays in this - Dataset. It should map variable names to units. If not - provided, will try to read them from - ``Dataset[var].attrs['units']`` using pint's parser. - unit_registry : `pint.UnitRegistry`, optional + Dataset. It should map variable names to units (unit names + or ``pint.Unit`` objects). If not provided, will try to + read them from ``Dataset[var].attrs['units']`` using + pint's parser. + unit_registry : pint.UnitRegistry, optional Unit registry to be used for the units attached to each DataArray in this Dataset. If not given then a default registry will be created. From ac63a01dff038e7077073ee091717f31123e17a7 Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 23:16:23 +0200 Subject: [PATCH 30/36] don't use rst references in the descriptions in docstrings --- pint_xarray/accessors.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 479f207c..6d70ce17 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -150,7 +150,7 @@ def quantify( Unit registry to be used for the units attached to this DataArray. If not given then a default registry will be created. registry_kwargs : dict, optional - Keyword arguments to be passed to :py:class:`pint.UnitRegistry`. + Keyword arguments to be passed to the unit registry. **unit_kwargs Keyword argument form of units. @@ -208,7 +208,7 @@ def dequantify(self): Removes units from the DataArray and its coordinates. Will replace ``.attrs['units']`` on each variable with a string - representation of the :py:class:`pint.Unit` instance. + representation of the ``pint.Unit`` instance. Returns ------- @@ -259,9 +259,9 @@ def to(self, units=None, **unit_kwargs): ---------- units : str or pint.Unit or mapping of hashable to str or pint.Unit, optional The units to convert to. If a unit name or - :py:class:`pint.Unit` object, convert the DataArray's + ``pint.Unit`` object, convert the DataArray's data. If a dict-like, it has to map a variable name to a - unit name or :py:class:`pint.Unit` object. + unit name or ``pint.Unit`` object. **unit_kwargs The kwargs form of ``units``. Can only be used for variable names that are strings and valid python identifiers. @@ -404,7 +404,7 @@ def quantify( """ Attaches units to each variable in the Dataset. - Units can be specified as a :py:class:`pint.Unit` or as a + Units can be specified as a ``pint.Unit`` or as a string, which will be parsed by the given unit registry. If no units are specified then the units will be parsed from the ``"units"`` entry of the Dataset variable's ``.attrs``. Will @@ -477,13 +477,13 @@ def dequantify(self): Removes units from the Dataset and its coordinates. Will replace ``.attrs['units']`` on each variable with a string - representation of the :py:class:`pint.Unit` instance. + representation of the ``pint.Unit`` instance. Returns ------- dequantified : Dataset Dataset whose data variables are unitless, and of the type - that was previously wrapped by :py:class:`pint.Quantity`. + that was previously wrapped by ``pint.Quantity``. """ units = units_to_str_or_none(conversion.extract_units(self.ds)) new_obj = conversion.attach_unit_attributes( From 7e169856d07c974f8109361dee9a0a95392e64ba Mon Sep 17 00:00:00 2001 From: Keewis Date: Tue, 14 Jul 2020 23:30:42 +0200 Subject: [PATCH 31/36] warn about loading data into memory while quantifying --- pint_xarray/accessors.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 6d70ce17..3c36f6ea 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -137,6 +137,11 @@ def quantify( `.attrs`. Will raise a ValueError if the DataArray already contains a unit-aware array. + .. note:: + Be aware that unless you're using ``dask`` this will load + the data into memory. To avoid that, consider converting + to ``dask`` first (e.g. using ``chunk``). + Parameters ---------- units : pint.Unit or str or mapping of hashable to pint.Unit or str, optional @@ -411,6 +416,11 @@ def quantify( raise a ValueError if any of the variables already contain a unit-aware array. + .. note:: + Be aware that unless you're using ``dask`` this will load + the data into memory. To avoid that, consider converting + to ``dask`` first (e.g. using ``chunk``). + Parameters ---------- units : mapping of hashable to pint.Unit or str, optional From 131403c21ab9ecd41e6280fb7d0711c8b970e260 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 15 Jul 2020 15:08:17 +0200 Subject: [PATCH 32/36] don't try to quantify indexes --- pint_xarray/accessors.py | 26 ++++++++++++++++++++++---- pint_xarray/tests/test_accessors.py | 15 ++++++++++----- pint_xarray/tests/utils.py | 2 +- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 3c36f6ea..050cb136 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -194,11 +194,14 @@ def quantify( unit_kwargs[self.da.name] = units units = None + # don't modify the original object + new_obj = self.da.copy() + units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") registry = _get_registry(unit_registry, registry_kwargs) - unit_attrs = conversion.extract_unit_attributes(self.da, delete=True) + unit_attrs = conversion.extract_unit_attributes(new_obj, delete=True) units = { name: _decide_units(unit, registry, unit_attribute) @@ -206,7 +209,13 @@ def quantify( if unit is not None or unit_attribute is not None } - return conversion.attach_units(self.da, units) + # TODO: remove once indexes support units + dim_units = {name: unit for name, unit in units.items() if name in self.da.dims} + for name in dim_units.keys(): + units.pop(name) + new_obj = conversion.attach_unit_attributes(new_obj, dim_units) + + return conversion.attach_units(new_obj, units) def dequantify(self): """ @@ -470,17 +479,26 @@ def quantify( a (x) int64 b (x) int64 """ + # don't modify the original object + new_obj = self.ds.copy() + units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") registry = _get_registry(unit_registry, registry_kwargs) - unit_attrs = conversion.extract_unit_attributes(self.ds, delete=True) + unit_attrs = conversion.extract_unit_attributes(new_obj, delete=True) units = { name: _decide_units(unit, registry, attr) for name, (unit, attr) in zip_mappings(units, unit_attrs).items() if unit is not None or attr is not None } - return conversion.attach_units(self.ds, units) + # TODO: remove once indexes support units + dim_units = {name: unit for name, unit in units.items() if name in new_obj.dims} + for name in dim_units.keys(): + units.pop(name) + new_obj = conversion.attach_unit_attributes(new_obj, dim_units) + + return conversion.attach_units(new_obj, units) def dequantify(self): """ diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index 648059fe..9a88490b 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -4,7 +4,7 @@ from numpy.testing import assert_array_equal from pint import Unit, UnitRegistry from pint.errors import UndefinedUnitError -from xarray.testing import assert_equal +from xarray.testing import assert_equal, assert_identical from .. import conversion from .utils import raises_regex @@ -55,11 +55,14 @@ def example_quantity_da(): class TestQuantifyDataArray: def test_attach_units_from_str(self, example_unitless_da): - orig = example_unitless_da - result = orig.pint.quantify("m") + obj = example_unitless_da + orig = obj.copy() + + result = obj.pint.quantify("m") assert_array_equal(result.data.magnitude, orig.data) # TODO better comparisons for when you can't access the unit_registry? assert str(result.data.units) == "meter" + assert_identical(orig, obj) def test_attach_units_given_registry(self, example_unitless_da): orig = example_unitless_da @@ -167,10 +170,12 @@ def test_units(self): class TestQuantifyDataSet: def test_attach_units_from_str(self, example_unitless_ds): - orig = example_unitless_ds - result = orig.pint.quantify() + obj = example_unitless_ds + orig = obj.copy() + result = obj.pint.quantify() assert_array_equal(result["users"].data.magnitude, orig["users"].data) assert str(result["users"].data.units) == "dimensionless" + assert_identical(orig, obj) def test_attach_units_given_registry(self, example_unitless_ds): orig = example_unitless_ds diff --git a/pint_xarray/tests/utils.py b/pint_xarray/tests/utils.py index f625d0ce..68057a89 100644 --- a/pint_xarray/tests/utils.py +++ b/pint_xarray/tests/utils.py @@ -5,7 +5,7 @@ import pytest import xarray as xr from pint.quantity import Quantity -from xarray.testing import assert_equal # noqa: F401 +from xarray.testing import assert_equal, assert_identical # noqa: F401 @contextmanager From f764586ea37ce19c58cf0c2ec127c6b93ba10133 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 15 Jul 2020 16:55:29 +0200 Subject: [PATCH 33/36] replace the delete parameter with a strip function which is important because modifying in-place makes it difficult to test. --- pint_xarray/accessors.py | 13 ++---- pint_xarray/conversion.py | 30 +++++++++--- pint_xarray/tests/test_accessors.py | 15 ++---- pint_xarray/tests/test_conversion.py | 70 +++++++++++++++++++--------- pint_xarray/tests/utils.py | 2 +- 5 files changed, 83 insertions(+), 47 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 050cb136..1c6f06af 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -194,14 +194,12 @@ def quantify( unit_kwargs[self.da.name] = units units = None - # don't modify the original object - new_obj = self.da.copy() - units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") registry = _get_registry(unit_registry, registry_kwargs) - unit_attrs = conversion.extract_unit_attributes(new_obj, delete=True) + unit_attrs = conversion.extract_unit_attributes(self.da) + new_obj = conversion.strip_unit_attributes(self.da) units = { name: _decide_units(unit, registry, unit_attribute) @@ -479,13 +477,12 @@ def quantify( a (x) int64 b (x) int64 """ - # don't modify the original object - new_obj = self.ds.copy() - units = either_dict_or_kwargs(units, unit_kwargs, ".quantify") registry = _get_registry(unit_registry, registry_kwargs) - unit_attrs = conversion.extract_unit_attributes(new_obj, delete=True) + unit_attrs = conversion.extract_unit_attributes(self.ds) + new_obj = conversion.strip_unit_attributes(self.ds) + units = { name: _decide_units(unit, registry, attr) for name, (unit, attr) in zip_mappings(units, unit_attrs).items() diff --git a/pint_xarray/conversion.py b/pint_xarray/conversion.py index 570871b2..98465eb7 100644 --- a/pint_xarray/conversion.py +++ b/pint_xarray/conversion.py @@ -222,17 +222,14 @@ def extract_units(obj): return units -def extract_unit_attributes(obj, delete=False, attr="units"): - method = dict.pop if delete else dict.get +def extract_unit_attributes(obj, attr="units"): if isinstance(obj, DataArray): variables = itertools.chain([(obj.name, obj)], obj.coords.items()) - units = {name: method(var.attrs, attr, None) for name, var in variables} + units = {name: var.attrs.get(attr, None) for name, var in variables} elif isinstance(obj, Dataset): - units = { - name: method(var.attrs, attr, None) for name, var in obj.variables.items() - } + units = {name: var.attrs.get(attr, None) for name, var in obj.variables.items()} elif isinstance(obj, Variable): - units = {None: method(obj.attrs, attr, None)} + units = {None: obj.attrs.get(attr, None)} else: raise ValueError( f"cannot retrieve unit attributes from unknown type: {type(obj)}" @@ -265,3 +262,22 @@ def strip_units(obj): raise ValueError("cannot strip units from {obj!r}: unknown type") return new_obj + + +def strip_unit_attributes(obj, attr="units"): + new_obj = obj.copy() + if isinstance(obj, DataArray): + variables = itertools.chain([(new_obj.name, new_obj)], new_obj.coords.items()) + for _, var in variables: + var.attrs.pop(attr, None) + elif isinstance(obj, Dataset): + for var in new_obj.variables.values(): + var.attrs.pop(attr, None) + elif isinstance(obj, Variable): + new_obj.attrs.pop(attr, None) + else: + raise ValueError( + f"cannot retrieve unit attributes from unknown type: {type(obj)}" + ) + + return new_obj diff --git a/pint_xarray/tests/test_accessors.py b/pint_xarray/tests/test_accessors.py index 9a88490b..648059fe 100644 --- a/pint_xarray/tests/test_accessors.py +++ b/pint_xarray/tests/test_accessors.py @@ -4,7 +4,7 @@ from numpy.testing import assert_array_equal from pint import Unit, UnitRegistry from pint.errors import UndefinedUnitError -from xarray.testing import assert_equal, assert_identical +from xarray.testing import assert_equal from .. import conversion from .utils import raises_regex @@ -55,14 +55,11 @@ def example_quantity_da(): class TestQuantifyDataArray: def test_attach_units_from_str(self, example_unitless_da): - obj = example_unitless_da - orig = obj.copy() - - result = obj.pint.quantify("m") + orig = example_unitless_da + result = orig.pint.quantify("m") assert_array_equal(result.data.magnitude, orig.data) # TODO better comparisons for when you can't access the unit_registry? assert str(result.data.units) == "meter" - assert_identical(orig, obj) def test_attach_units_given_registry(self, example_unitless_da): orig = example_unitless_da @@ -170,12 +167,10 @@ def test_units(self): class TestQuantifyDataSet: def test_attach_units_from_str(self, example_unitless_ds): - obj = example_unitless_ds - orig = obj.copy() - result = obj.pint.quantify() + orig = example_unitless_ds + result = orig.pint.quantify() assert_array_equal(result["users"].data.magnitude, orig["users"].data) assert str(result["users"].data.units) == "dimensionless" - assert_identical(orig, obj) def test_attach_units_given_registry(self, example_unitless_ds): orig = example_unitless_ds diff --git a/pint_xarray/tests/test_conversion.py b/pint_xarray/tests/test_conversion.py index 1f211f10..0115d1d1 100644 --- a/pint_xarray/tests/test_conversion.py +++ b/pint_xarray/tests/test_conversion.py @@ -12,6 +12,10 @@ pytestmark = pytest.mark.filterwarnings("error::pint.UnitStrippedWarning") +def filter_none_values(mapping): + return {k: v for k, v in mapping.items() if v is not None} + + class TestArrayFunctions: @pytest.mark.parametrize( "registry", @@ -197,12 +201,7 @@ def test_attach_units(self, obj, units): ) def test_attach_unit_attributes(self, obj, units): actual = conversion.attach_unit_attributes(obj, units) - actual_units = { - key: value - for key, value in conversion.extract_unit_attributes(actual).items() - if value is not None - } - assert units == actual_units + assert units == filter_none_values(conversion.extract_unit_attributes(actual)) @pytest.mark.parametrize( "variant", @@ -337,9 +336,6 @@ def test_extract_units(self, typename, units): assert conversion.extract_units(obj) == units - @pytest.mark.parametrize( - "delete", (pytest.param(False, id="keep"), pytest.param(True, id="delete")) - ) @pytest.mark.parametrize( ["obj", "expected"], ( @@ -374,20 +370,10 @@ def test_extract_units(self, typename, units): ), ), ) - def test_extract_unit_attributes(self, obj, expected, delete): - actual = conversion.extract_unit_attributes(obj, delete=delete) + def test_extract_unit_attributes(self, obj, expected): + actual = conversion.extract_unit_attributes(obj) assert expected == actual - if delete: - remaining_attributes = { - key: value - for key, value in conversion.extract_unit_attributes( - obj, delete=False - ).items() - if value is not None - } - assert remaining_attributes == {} - @pytest.mark.parametrize( "obj", ( @@ -423,3 +409,45 @@ def test_strip_units(self, obj): actual = conversion.strip_units(obj) assert conversion.extract_units(actual) == expected_units + + @pytest.mark.parametrize( + ["obj", "expected"], + ( + pytest.param( + DataArray( + coords={ + "x": ("x", [], {"units": "m"}), + "u": ("x", [], {"units": "s"}), + }, + attrs={"units": "hPa"}, + dims="x", + ), + {"x": "m", "u": "s", None: "hPa"}, + id="DataArray", + ), + pytest.param( + Dataset( + data_vars={ + "a": ("x", [], {"units": "degK"}), + "b": ("x", [], {"units": "hPa"}), + }, + coords={ + "x": ("x", [], {"units": "m"}), + "u": ("x", [], {"units": "s"}), + }, + ), + {"a": "degK", "b": "hPa", "x": "m", "u": "s"}, + id="Dataset", + ), + pytest.param( + Variable("x", [], {"units": "hPa"}), {None: "hPa"}, id="Variable", + ), + ), + ) + def test_strip_unit_attributes(self, obj, expected): + actual = conversion.strip_unit_attributes(obj) + expected = {} + + assert ( + filter_none_values(conversion.extract_unit_attributes(actual)) == expected + ) diff --git a/pint_xarray/tests/utils.py b/pint_xarray/tests/utils.py index 68057a89..f625d0ce 100644 --- a/pint_xarray/tests/utils.py +++ b/pint_xarray/tests/utils.py @@ -5,7 +5,7 @@ import pytest import xarray as xr from pint.quantity import Quantity -from xarray.testing import assert_equal, assert_identical # noqa: F401 +from xarray.testing import assert_equal # noqa: F401 @contextmanager From e7039095ad9b5c2c99179fdf4a4335bd764fefe6 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 15 Jul 2020 23:38:46 +0200 Subject: [PATCH 34/36] update the docstrings --- pint_xarray/accessors.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 1c6f06af..195cfabf 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -142,6 +142,10 @@ def quantify( the data into memory. To avoid that, consider converting to ``dask`` first (e.g. using ``chunk``). + As units in dimension coordinates are not supported until + ``xarray`` changes the way it implements indexes, these + units will be set as attributes. + Parameters ---------- units : pint.Unit or str or mapping of hashable to pint.Unit or str, optional @@ -163,7 +167,8 @@ def quantify( ------- quantified : DataArray DataArray whose wrapped array data will now be a Quantity - array with the specified units. + array with the specified units. Any ``"units"`` attributes + will be removed except for dimension coordinates. Examples -------- @@ -172,7 +177,7 @@ def quantify( ... dims="frequency", ... coords={"wavelength": [1e-4, 2e-4, 4e-4, 6e-4, 1e-3, 2e-3]}, ... ) - >>> da.pint.quantify(units='Hz') + >>> da.pint.quantify(units="Hz") Quantity([ 0.4, 0.9, 1.7, 4.8, 3.2, 9.1], 'Hz') Coordinates: @@ -428,6 +433,10 @@ def quantify( the data into memory. To avoid that, consider converting to ``dask`` first (e.g. using ``chunk``). + As units in dimension coordinates are not supported until + ``xarray`` changes the way it implements indexes, these + units will be set as attributes. + Parameters ---------- units : mapping of hashable to pint.Unit or str, optional @@ -449,7 +458,8 @@ def quantify( ------- quantified : Dataset Dataset whose variables will now contain Quantity arrays - with units. + with units. Any ``"units"`` attributes will be removed + except from dimension coordinates. Examples -------- From f6e0882e3d156d15e6a3bf2b0029b398f9529a05 Mon Sep 17 00:00:00 2001 From: Keewis Date: Thu, 16 Jul 2020 00:08:40 +0200 Subject: [PATCH 35/36] more docstring updates --- pint_xarray/accessors.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pint_xarray/accessors.py b/pint_xarray/accessors.py index 195cfabf..e86fcd13 100644 --- a/pint_xarray/accessors.py +++ b/pint_xarray/accessors.py @@ -154,7 +154,9 @@ def quantify( dict-like, it should map a variable name to the desired unit (use the DataArray's name to refer to its data). If not provided, will try to read them from - ``DataArray.attrs['units']`` using pint's parser. + ``DataArray.attrs['units']`` using pint's parser. The + ``"units"`` attribute will be removed from all variables + except from dimension coordinates. unit_registry : pint.UnitRegistry, optional Unit registry to be used for the units attached to this DataArray. If not given then a default registry will be created. @@ -167,8 +169,7 @@ def quantify( ------- quantified : DataArray DataArray whose wrapped array data will now be a Quantity - array with the specified units. Any ``"units"`` attributes - will be removed except for dimension coordinates. + array with the specified units. Examples -------- @@ -275,10 +276,10 @@ def to(self, units=None, **unit_kwargs): Parameters ---------- units : str or pint.Unit or mapping of hashable to str or pint.Unit, optional - The units to convert to. If a unit name or - ``pint.Unit`` object, convert the DataArray's - data. If a dict-like, it has to map a variable name to a - unit name or ``pint.Unit`` object. + The units to convert to. If a unit name or ``pint.Unit`` + object, convert the DataArray's data. If a dict-like, it + has to map a variable name to a unit name or ``pint.Unit`` + object. **unit_kwargs The kwargs form of ``units``. Can only be used for variable names that are strings and valid python identifiers. @@ -444,7 +445,8 @@ def quantify( Dataset. It should map variable names to units (unit names or ``pint.Unit`` objects). If not provided, will try to read them from ``Dataset[var].attrs['units']`` using - pint's parser. + pint's parser. The ``"units"`` attribute will be removed + from all variables except from dimension coordinates. unit_registry : pint.UnitRegistry, optional Unit registry to be used for the units attached to each DataArray in this Dataset. If not given then a default @@ -458,8 +460,7 @@ def quantify( ------- quantified : Dataset Dataset whose variables will now contain Quantity arrays - with units. Any ``"units"`` attributes will be removed - except from dimension coordinates. + with units. Examples -------- From 08b2c1e9507fd88e8b74750207ee79930269295b Mon Sep 17 00:00:00 2001 From: keewis Date: Fri, 17 Jul 2020 18:06:28 +0200 Subject: [PATCH 36/36] use K instead of degK Co-authored-by: Jon Thielen --- pint_xarray/tests/test_conversion.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pint_xarray/tests/test_conversion.py b/pint_xarray/tests/test_conversion.py index 0115d1d1..f79dc405 100644 --- a/pint_xarray/tests/test_conversion.py +++ b/pint_xarray/tests/test_conversion.py @@ -193,7 +193,7 @@ def test_attach_units(self, obj, units): data_vars={"a": ("x", []), "b": ("x", [])}, coords={"x": [], "u": ("x", [])}, ), - {"a": "degK", "b": "hPa", "u": "m"}, + {"a": "K", "b": "hPa", "u": "m"}, id="Dataset", ), pytest.param(Variable("x", []), {None: "hPa"}, id="Variable",), @@ -354,7 +354,7 @@ def test_extract_units(self, typename, units): pytest.param( Dataset( data_vars={ - "a": ("x", [], {"units": "degK"}), + "a": ("x", [], {"units": "K"}), "b": ("x", [], {"units": "hPa"}), }, coords={ @@ -362,7 +362,7 @@ def test_extract_units(self, typename, units): "u": ("x", [], {"units": "s"}), }, ), - {"a": "degK", "b": "hPa", "x": "m", "u": "s"}, + {"a": "K", "b": "hPa", "x": "m", "u": "s"}, id="Dataset", ), pytest.param( @@ -428,7 +428,7 @@ def test_strip_units(self, obj): pytest.param( Dataset( data_vars={ - "a": ("x", [], {"units": "degK"}), + "a": ("x", [], {"units": "K"}), "b": ("x", [], {"units": "hPa"}), }, coords={ @@ -436,7 +436,7 @@ def test_strip_units(self, obj): "u": ("x", [], {"units": "s"}), }, ), - {"a": "degK", "b": "hPa", "x": "m", "u": "s"}, + {"a": "K", "b": "hPa", "x": "m", "u": "s"}, id="Dataset", ), pytest.param(