Skip to content

Commit d73e881

Browse files
authored
make sure re-quantifying dimension coordinates works (#174)
* pass through Unit objects extracted from the attrs * add a test to verify this actually works * also check that quantifying a quantified dimension coordinate fails * raise a error if we try to quantify already quantified variables This only changes behavior for dimension coordinates (previously it would have been possible to overwrite the units). * also add tests for Dataset.pint.quantify * update whats-new.rst
1 parent dc3b7f5 commit d73e881

File tree

3 files changed

+86
-1
lines changed

3 files changed

+86
-1
lines changed

docs/whats-new.rst

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ What's new
1010
By `Justus Magin <https://github.com/keewis>`_.
1111
- preserve :py:class:`pandas.MultiIndex` objects (:issue:`164`, :pull:`168`).
1212
By `Justus Magin <https://github.com/keewis>`_.
13+
- fix "quantifying" dimension coordinates (:issue:`105`, :pull:`174`).
14+
By `Justus Magin <https://github.com/keewis>`_.
1315

1416
0.2.1 (26 Jul 2021)
1517
-------------------

pint_xarray/accessors.py

+54-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,10 @@ def _decide_units(units, registry, unit_attribute):
142142
elif units is _default:
143143
if unit_attribute in no_unit_values:
144144
return unit_attribute
145-
units = registry.parse_units(unit_attribute)
145+
if isinstance(unit_attribute, Unit):
146+
units = unit_attribute
147+
else:
148+
units = registry.parse_units(unit_attribute)
146149
else:
147150
units = registry.parse_units(units)
148151
return units
@@ -360,6 +363,31 @@ def quantify(self, units=_default, unit_registry=None, **unit_kwargs):
360363
if invalid_units:
361364
raise ValueError(format_error_message(invalid_units, "parse"))
362365

366+
existing_units = {
367+
name: unit
368+
for name, unit in conversion.extract_units(self.da).items()
369+
if isinstance(unit, Unit)
370+
}
371+
overwritten_units = {
372+
name: (old, new)
373+
for name, (old, new) in zip_mappings(
374+
existing_units, new_units, fill_value=_default
375+
).items()
376+
if old is not _default and new is not _default
377+
}
378+
if overwritten_units:
379+
errors = {
380+
name: (
381+
new,
382+
ValueError(
383+
f"Cannot attach unit {repr(new)} to quantity: data "
384+
f"already has units {repr(old)}"
385+
),
386+
)
387+
for name, (old, new) in overwritten_units.items()
388+
}
389+
raise ValueError(format_error_message(errors, "attach"))
390+
363391
return self.da.pipe(conversion.strip_unit_attributes).pipe(
364392
conversion.attach_units, new_units
365393
)
@@ -1050,6 +1078,31 @@ def quantify(self, units=_default, unit_registry=None, **unit_kwargs):
10501078
if invalid_units:
10511079
raise ValueError(format_error_message(invalid_units, "parse"))
10521080

1081+
existing_units = {
1082+
name: unit
1083+
for name, unit in conversion.extract_units(self.ds).items()
1084+
if isinstance(unit, Unit)
1085+
}
1086+
overwritten_units = {
1087+
name: (old, new)
1088+
for name, (old, new) in zip_mappings(
1089+
existing_units, new_units, fill_value=_default
1090+
).items()
1091+
if old is not _default and new is not _default
1092+
}
1093+
if overwritten_units:
1094+
errors = {
1095+
name: (
1096+
new,
1097+
ValueError(
1098+
f"Cannot attach unit {repr(new)} to quantity: data "
1099+
f"already has units {repr(old)}"
1100+
),
1101+
)
1102+
for name, (old, new) in overwritten_units.items()
1103+
}
1104+
raise ValueError(format_error_message(errors, "attach"))
1105+
10531106
return self.ds.pipe(conversion.strip_unit_attributes).pipe(
10541107
conversion.attach_units, new_units
10551108
)

pint_xarray/tests/test_accessors.py

+30
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,22 @@ def test_parse_integer_inverse(self):
135135
result = da.pint.quantify()
136136
assert result.pint.units == Unit("1 / meter")
137137

138+
def test_dimension_coordinate(self):
139+
ds = xr.Dataset(coords={"x": ("x", [10], {"units": "m"})})
140+
arr = ds.x
141+
142+
# does not actually quantify because `arr` wraps a IndexVariable
143+
# but we still get a `Unit` in the attrs
144+
q = arr.pint.quantify()
145+
assert isinstance(q.attrs["units"], Unit)
146+
147+
def test_dimension_coordinate_already_quantified(self):
148+
ds = xr.Dataset(coords={"x": ("x", [10], {"units": unit_registry.Unit("m")})})
149+
arr = ds.x
150+
151+
with pytest.raises(ValueError):
152+
arr.pint.quantify({"x": "s"})
153+
138154

139155
@pytest.mark.parametrize("formatter", ("", "P", "C"))
140156
@pytest.mark.parametrize("modifier", ("", "~"))
@@ -313,6 +329,20 @@ def test_error_indicates_problematic_variable(self, example_unitless_ds):
313329
with pytest.raises(ValueError, match="'users'"):
314330
ds.pint.quantify(units={"users": "aecjhbav"})
315331

332+
def test_existing_units(self, example_quantity_ds):
333+
ds = example_quantity_ds.copy()
334+
ds.t.attrs["units"] = unit_registry.Unit("m")
335+
336+
with pytest.raises(ValueError, match="Cannot attach"):
337+
ds.pint.quantify({"funds": "kg"})
338+
339+
def test_existing_units_dimension(self, example_quantity_ds):
340+
ds = example_quantity_ds.copy()
341+
ds.t.attrs["units"] = unit_registry.Unit("m")
342+
343+
with pytest.raises(ValueError, match="Cannot attach"):
344+
ds.pint.quantify({"t": "s"})
345+
316346

317347
class TestDequantifyDataSet:
318348
def test_strip_units(self, example_quantity_ds):

0 commit comments

Comments
 (0)