Skip to content

Commit 3202623

Browse files
authored
Merge branch 'main' into improve_rename_like
2 parents 06b9207 + 24bf4d3 commit 3202623

13 files changed

+220
-22
lines changed

.binder/environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ dependencies:
77
- netcdf4
88
- pip
99
- xarray
10+
- pooch
1011
- pip:
1112
- git+https://github.com/xarray-contrib/cf-xarray

.github/workflows/ci.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,29 @@ jobs:
4848
name: codecov-umbrella
4949
fail_ci_if_error: false
5050

51+
no-optional-deps:
52+
name: no-optional-deps
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v2
56+
- uses: conda-incubator/setup-miniconda@v2
57+
with:
58+
channels: conda-forge
59+
mamba-version: "*"
60+
activate-environment: cf_xarray_test
61+
auto-update-conda: false
62+
python-version: ${{ matrix.python-version }}
63+
- name: Set up conda environment
64+
shell: bash -l {0}
65+
run: |
66+
mamba env update -f ci/environment-no-optional-deps.yml
67+
python -m pip install -e .
68+
conda list
69+
- name: Run Tests
70+
shell: bash -l {0}
71+
run: |
72+
pytest -n 2
73+
5174
upstream-dev:
5275
name: upstream-dev
5376
runs-on: ubuntu-latest

README.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ A lightweight convenience wrapper for using CF attributes on xarray objects.
3434

3535
For example you can use ``.cf.mean("latitude")`` instead of ``.mean("lat")`` if appropriate attributes are set! This allows you to write code that does not require knowledge of specific dimension or coordinate names particular to a dataset.
3636

37-
See more in the introductory notebook `here <https://cf-xarray.readthedocs.io/en/latest/examples/introduction.html>`_.
37+
See more in the `introductory notebook <https://cf-xarray.readthedocs.io/en/latest/examples/introduction.html>`_.
38+
39+
Try out our Earthcube 2021 Annual Meeting notebook `submission <https://binder.pangeo.io/v2/gh/malmans2/cf-xarray-earthcube/main?filepath=DC_01_cf-xarray.ipynb>`_.

cf_xarray/accessor.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,15 +1615,15 @@ def get_bounds_dim_name(self, key: str) -> str:
16151615
assert self._obj.sizes[bounds_dim] in [2, 4]
16161616
return bounds_dim
16171617

1618-
def add_bounds(self, dims: Union[Hashable, Iterable[Hashable]]):
1618+
def add_bounds(self, keys: Union[str, Iterable[str]]):
16191619
"""
16201620
Returns a new object with bounds variables. The bounds values are guessed assuming
16211621
equal spacing on either side of a coordinate label.
16221622
16231623
Parameters
16241624
----------
1625-
dims : Hashable or Iterable[Hashable]
1626-
Either a single dimension name or a list of dimension names.
1625+
keys : str or Iterable[str]
1626+
Either a single key or a list of keys corresponding to dimensions.
16271627
16281628
Returns
16291629
-------
@@ -1638,12 +1638,16 @@ def add_bounds(self, dims: Union[Hashable, Iterable[Hashable]]):
16381638
The bounds variables are automatically named f"{dim}_bounds" where ``dim``
16391639
is a dimension name.
16401640
"""
1641-
if isinstance(dims, Hashable):
1642-
dimensions = (dims,)
1643-
else:
1644-
dimensions = dims
1641+
if isinstance(keys, str):
1642+
keys = [keys]
1643+
1644+
dimensions = set()
1645+
for key in keys:
1646+
dimensions.update(
1647+
apply_mapper(_get_dims, self._obj, key, error=False, default=[key])
1648+
)
16451649

1646-
bad_dims: Set[Hashable] = set(dimensions) - set(self._obj.dims)
1650+
bad_dims: Set[str] = dimensions - set(self._obj.dims)
16471651
if bad_dims:
16481652
raise ValueError(
16491653
f"{bad_dims!r} are not dimensions in the underlying object."

cf_xarray/criteria.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77

8+
import copy
89
import re
910
from typing import MutableMapping, Tuple
1011

@@ -74,19 +75,20 @@
7475
}
7576

7677
# "long_name" and "standard_name" criteria are the same. For convenience.
77-
coordinate_criteria["long_name"] = coordinate_criteria["standard_name"]
78+
coordinate_criteria["long_name"] = copy.deepcopy(coordinate_criteria["standard_name"])
79+
coordinate_criteria["long_name"]["X"] += ("cell index along first dimension",)
80+
coordinate_criteria["long_name"]["Y"] += ("cell index along second dimension",)
7881

7982
#: regular expressions for guess_coord_axis
8083
regex = {
8184
"time": re.compile("\\bt\\b|(time|min|hour|day|week|month|year)[0-9]*"),
82-
"vertical": re.compile(
83-
"(z|nav_lev|gdep|lv_|bottom_top|sigma|h(ei)?ght|altitude|depth|"
85+
"Z": re.compile(
86+
"(z|nav_lev|gdep|lv_|[o]*lev|bottom_top|sigma|h(ei)?ght|altitude|depth|"
8487
"isobaric|pres|isotherm)[a-z_]*[0-9]*"
8588
),
86-
"Y": re.compile("y"),
89+
"Y": re.compile("y|j|nlat|nj"),
8790
"latitude": re.compile("y?(nav_lat|lat|gphi)[a-z0-9]*"),
88-
"X": re.compile("x"),
91+
"X": re.compile("x|i|nlon|ni"),
8992
"longitude": re.compile("x?(nav_lon|lon|glam)[a-z0-9]*"),
9093
}
91-
regex["Z"] = regex["vertical"]
9294
regex["T"] = regex["time"]

cf_xarray/tests/test_accessor.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,10 @@ def test_add_bounds(obj, dims):
648648
assert added[dim].attrs["bounds"] == name
649649
assert_allclose(added[name].reset_coords(drop=True), expected[dim])
650650

651+
# Test multiple dimensions
652+
assert not {"x1_bounds", "x2_bounds"} <= set(multiple.variables)
653+
assert {"x1_bounds", "x2_bounds"} <= set(multiple.cf.add_bounds("X").variables)
654+
651655

652656
def test_bounds():
653657
ds = airds.copy(deep=True).cf.add_bounds("lat")
@@ -790,9 +794,9 @@ def _make_names(prefixes):
790794
"nav_lev",
791795
]
792796
)
793-
_X_NAMES = _make_names(["x"])
794-
_Y_NAMES = _make_names(["y"])
795-
_Z_NAMES = _VERTICAL_NAMES
797+
_X_NAMES = _make_names(["x", "nlon", "i", "ni"])
798+
_Y_NAMES = _make_names(["y", "nlat", "j", "nj"])
799+
_Z_NAMES = _VERTICAL_NAMES + ["olevel", "level", "zlevel"]
796800
_LATITUDE_NAMES = _make_names(["lat", "latitude", "gphi", "nav_lat"])
797801
_LONGITUDE_NAMES = _make_names(["lon", "longitude", "glam", "nav_lon"])
798802

@@ -1203,3 +1207,24 @@ def test_differentiate_positive_upward(obj):
12031207
obj.z.attrs["positive"] = "zzz"
12041208
with pytest.raises(ValueError):
12051209
obj.cf.differentiate("z", positive_upward=True)
1210+
1211+
1212+
def test_cmip6_attrs():
1213+
da = xr.DataArray(
1214+
np.ones((10, 10)),
1215+
dims=("nlon", "nlat"),
1216+
coords={
1217+
"nlon": (
1218+
"nlon",
1219+
np.arange(10),
1220+
{"long_name": "cell index along first dimension"},
1221+
),
1222+
"nlat": (
1223+
"nlat",
1224+
np.arange(10),
1225+
{"long_name": "cell index along second dimension"},
1226+
),
1227+
},
1228+
)
1229+
assert da.cf.axes["X"] == ["nlon"]
1230+
assert da.cf.axes["Y"] == ["nlat"]

cf_xarray/tests/test_units.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
r"""Tests the operation of cf_xarray's ported unit support code.
2+
3+
Reused with modification from MetPy under the terms of the BSD 3-Clause License.
4+
Copyright (c) 2017 MetPy Developers.
5+
"""
6+
7+
import pytest
8+
9+
pytest.importorskip("pint")
10+
11+
from ..units import units
12+
13+
14+
def test_added_degrees_units():
15+
"""Test that our added degrees units are present in the registry."""
16+
# Test equivalence of abbreviations/aliases to our defined names
17+
assert str(units("degrees_N").units) == "degrees_north"
18+
assert str(units("degreesN").units) == "degrees_north"
19+
assert str(units("degree_north").units) == "degrees_north"
20+
assert str(units("degree_N").units) == "degrees_north"
21+
assert str(units("degreeN").units) == "degrees_north"
22+
assert str(units("degrees_E").units) == "degrees_east"
23+
assert str(units("degreesE").units) == "degrees_east"
24+
assert str(units("degree_east").units) == "degrees_east"
25+
assert str(units("degree_E").units) == "degrees_east"
26+
assert str(units("degreeE").units) == "degrees_east"
27+
28+
# Test equivalence of our defined units to base units
29+
assert units("degrees_north") == units("degrees")
30+
assert units("degrees_north").to_base_units().units == units.radian
31+
assert units("degrees_east") == units("degrees")
32+
assert units("degrees_east").to_base_units().units == units.radian
33+
34+
35+
def test_gpm_unit():
36+
"""Test that the gpm unit does alias to meters."""
37+
x = 1 * units("gpm")
38+
assert str(x.units) == "meter"
39+
40+
41+
def test_psu_unit():
42+
"""Test that the psu unit are present in the registry."""
43+
x = 1 * units("psu")
44+
assert str(x.units) == "practical_salinity_unit"
45+
46+
47+
def test_percent_units():
48+
"""Test that percent sign units are properly parsed and interpreted."""
49+
assert str(units("%").units) == "percent"
50+
51+
52+
@pytest.mark.xfail(reason="not supported by pint, yet: hgrecco/pint#1295")
53+
def test_udunits_power_syntax():
54+
"""Test that UDUNITS style powers are properly parsed and interpreted."""
55+
assert units("m2 s-2").units == units.m ** 2 / units.s ** 2
56+
57+
58+
def test_udunits_power_syntax_parse_units():
59+
"""Test that UDUNITS style powers are properly parsed and interpreted."""
60+
assert units.parse_units("m2 s-2") == units.m ** 2 / units.s ** 2

cf_xarray/units.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
r"""Module to provide unit support via pint approximating UDUNITS/CF.
2+
3+
Reused with modification from MetPy under the terms of the BSD 3-Clause License.
4+
Copyright (c) 2015,2017,2019 MetPy Developers.
5+
"""
6+
import functools
7+
import re
8+
import warnings
9+
10+
import pint
11+
from pint import ( # noqa: F401
12+
DimensionalityError,
13+
UndefinedUnitError,
14+
UnitStrippedWarning,
15+
)
16+
17+
# Create registry, with preprocessors for UDUNITS-style powers (m2 s-2) and percent signs
18+
units = pint.UnitRegistry(
19+
autoconvert_offset_to_baseunit=True,
20+
preprocessors=[
21+
functools.partial(
22+
re.compile(
23+
r"(?<=[A-Za-z])(?![A-Za-z])(?<![0-9\-][eE])(?<![0-9\-])(?=[0-9\-])"
24+
).sub,
25+
"**",
26+
),
27+
lambda string: string.replace("%", "percent"),
28+
],
29+
force_ndarray_like=True,
30+
)
31+
32+
units.define(
33+
pint.unit.UnitDefinition("percent", "%", (), pint.converters.ScaleConverter(0.01))
34+
)
35+
36+
# Define commonly encoutered units (both CF and non-CF) not defined by pint
37+
units.define(
38+
"degrees_north = degree = degrees_N = degreesN = degree_north = degree_N = degreeN"
39+
)
40+
units.define(
41+
"degrees_east = degree = degrees_E = degreesE = degree_east = degree_E = degreeE"
42+
)
43+
units.define("@alias meter = gpm")
44+
units.define("practical_salinity_unit = [] = psu")
45+
46+
# Enable pint's built-in matplotlib support
47+
try:
48+
units.setup_matplotlib()
49+
except ImportError:
50+
warnings.warn(
51+
"Import(s) unavailable to set up matplotlib support...skipping this portion "
52+
"of the setup."
53+
)
54+
55+
# Set as application registry
56+
pint.set_application_registry(units)
57+
58+
del pint

ci/environment-no-optional-deps.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: cf_xarray_test
2+
channels:
3+
- conda-forge
4+
dependencies:
5+
- pytest-cov
6+
- pytest
7+
- pytest-xdist
8+
- dask
9+
- matplotlib-base
10+
- netcdf4
11+
- pandas
12+
- pooch
13+
- xarray

ci/environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ dependencies:
99
- matplotlib-base
1010
- netcdf4
1111
- pandas
12+
- pint
1213
- pooch
1314
- xarray

ci/upstream-dev-env.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ dependencies:
1212
- pooch
1313
- pip:
1414
- git+https://github.com/pydata/xarray
15+
- git+https://github.com/hgrecco/pint

doc/whats-new.rst

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
What's New
44
----------
55

6-
v0.5.2 (unreleased)
6+
v0.5.3 (unreleased)
77
===================
8-
8+
- Begin adding support for units with a unit registry for pint arrays. :pr:`197`.
9+
By `Jon Thielen`_ and `Justus Magin`_.
910
- :py:meth:`Dataset.cf.rename_like` also updates the ``bounds`` and ``cell_measures`` attributes. By `Mattia Almansi`_.
10-
- Added :py:attr:`DataArray.cf.formula_terms` and :py:attr:`Dataset.cf.formula_terms`.
11+
12+
v0.5.2 (May 11, 2021)
13+
=====================
14+
15+
- Add some explicit support for CMIP6 output. By `Deepak Cherian`_.
16+
- Replace the ``dims`` argument of :py:meth:`Dataset.cf.add_bounds` with ``keys``, allowing to use CF keys. By `Mattia Almansi`_.
1117
- Added :py:attr:`DataArray.cf.formula_terms` and :py:attr:`Dataset.cf.formula_terms`.
1218
By `Deepak Cherian`_.
1319
- Added :py:attr:`Dataset.cf.bounds` to return a dictionary mapping valid keys to the variable names of their bounds. By `Mattia Almansi`_.
@@ -102,6 +108,8 @@ v0.1.3
102108
- Support expanding key to multiple dimension names.
103109

104110
.. _`Mattia Almansi`: https://github.com/malmans2
111+
.. _`Justus Magin`: https://github.com/keewis
112+
.. _`Jon Thielen`: https://github.com/jthielen
105113
.. _`Anderson Banihirwe`: https://github.com/andersy005
106114
.. _`Pascal Bourgault`: https://github.com/aulemahal
107115
.. _`Deepak Cherian`: https://github.com/dcherian

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ skip_gitignore = true
4141
force_to_top = true
4242
default_section = THIRDPARTY
4343
known_first_party = cf_xarray
44-
known_third_party = dask,matplotlib,numpy,pandas,pkg_resources,pytest,setuptools,sphinx_autosummary_accessors,xarray
44+
known_third_party = dask,matplotlib,numpy,pandas,pint,pkg_resources,pytest,setuptools,sphinx_autosummary_accessors,xarray
4545

4646
# Most of the numerical computing stack doesn't have type annotations yet.
4747
[mypy-affine.*]

0 commit comments

Comments
 (0)