Skip to content

Commit 9537914

Browse files
dcherianmalmans2
andauthored
Add differentiate with positive_upward flag. (#198)
* Add differentiate with follow_positive flag. xref #190 * fix test * Switch to 'positive_upward' + better docstring * Also test dataset * [skip-ci] fix whats-new * fix docstring * Update cf_xarray/accessor.py Co-authored-by: Mattia Almansi <[email protected]> Co-authored-by: Mattia Almansi <[email protected]>
1 parent b5ffbe5 commit 9537914

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

cf_xarray/accessor.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,46 @@ def stack(self, dimensions=None, **dimensions_kwargs):
14821482
dimensions.update({key: tuple(itertools.chain(*updates))})
14831483
return self._obj.stack(dimensions)
14841484

1485+
def differentiate(
1486+
self, coord, *xr_args, positive_upward: bool = False, **xr_kwargs
1487+
):
1488+
"""
1489+
Parameters
1490+
----------
1491+
xr_args, xr_kwargs are passed directly to the underlying xarray function.
1492+
The following are added by cf_xarray:
1493+
1494+
positive_upward: optional, bool
1495+
Change sign of the derivative based on the ``"positive"`` attribute of ``coord``
1496+
so that positive values indicate increasing upward.
1497+
If ``positive=="down"``, then multiplied by -1.
1498+
1499+
See Also
1500+
--------
1501+
DataArray.cf.differentiate
1502+
Dataset.cf.differentiate
1503+
xarray.DataArray.differentiate: underlying xarray function
1504+
xarray.Dataset.differentiate: underlying xarray function
1505+
"""
1506+
coord = apply_mapper(
1507+
(_single(_get_coords),), self._obj, coord, error=False, default=[coord]
1508+
)[0]
1509+
result = self._obj.differentiate(coord, *xr_args, **xr_kwargs)
1510+
if positive_upward:
1511+
coord = self._obj[coord]
1512+
attrs = coord.attrs
1513+
if "positive" not in attrs:
1514+
raise ValueError(
1515+
f"positive_upward=True and 'positive' attribute not present on {coord.name}"
1516+
)
1517+
if attrs["positive"] not in ["up", "down"]:
1518+
raise ValueError(
1519+
f"positive_upward=True and received attrs['positive']={attrs['positive']}. Expected one of ['up', 'down'] "
1520+
)
1521+
if attrs["positive"] == "down":
1522+
result *= -1
1523+
return result
1524+
14851525

14861526
@xr.register_dataset_accessor("cf")
14871527
class CFDatasetAccessor(CFAccessor):

cf_xarray/tests/test_accessor.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,3 +1091,54 @@ def test_stack(obj):
10911091

10921092
actual = obj.cf.stack({"latlon": ["latitude", "longitude"]})
10931093
assert_identical(expected, actual)
1094+
1095+
1096+
da = xr.DataArray(
1097+
np.arange(10)[::-1], # like ocean temperature
1098+
dims="z",
1099+
coords={"z": ("z", np.arange(10))},
1100+
name="test",
1101+
)
1102+
1103+
1104+
@pytest.mark.parametrize("obj", [da, da.to_dataset()])
1105+
def test_differentiate_positive_upward(obj):
1106+
obj.z.attrs["positive"] = "down"
1107+
expected = obj.differentiate("z", 2)
1108+
actual = obj.cf.differentiate("z", 2)
1109+
assert_identical(expected, actual)
1110+
1111+
obj.z.attrs["positive"] = "up"
1112+
expected = obj.differentiate("z", 2)
1113+
actual = obj.cf.differentiate("z", 2, positive_upward=True)
1114+
assert_identical(expected, actual)
1115+
1116+
obj.z.attrs["positive"] = "down"
1117+
expected = -1 * obj.differentiate("z", 2)
1118+
actual = obj.cf.differentiate("z", 2, positive_upward=True)
1119+
assert_identical(expected, actual)
1120+
1121+
obj = obj.isel(z=slice(None, None, -1))
1122+
expected = -1 * obj.differentiate("z", 2)
1123+
actual = obj.cf.differentiate("z", 2, positive_upward=True)
1124+
assert_identical(expected, actual)
1125+
obj = obj.isel(z=slice(None, None, -1))
1126+
1127+
with xr.set_options(keep_attrs=True):
1128+
da["z"] = obj.z * -1
1129+
expected = -1 * obj.differentiate("z", 2)
1130+
actual = obj.cf.differentiate("z", 2, positive_upward=True)
1131+
assert_identical(expected, actual)
1132+
1133+
obj = obj.isel(z=slice(None, None, -1))
1134+
expected = -1 * obj.differentiate("z", 2)
1135+
actual = obj.cf.differentiate("z", 2, positive_upward=True)
1136+
assert_identical(expected, actual)
1137+
1138+
del obj.z.attrs["positive"]
1139+
with pytest.raises(ValueError):
1140+
obj.cf.differentiate("z", positive_upward=True)
1141+
1142+
obj.z.attrs["positive"] = "zzz"
1143+
with pytest.raises(ValueError):
1144+
obj.cf.differentiate("z", positive_upward=True)

doc/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Methods
4545

4646
DataArray.cf.__getitem__
4747
DataArray.cf.__repr__
48+
DataArray.cf.differentiate
4849
DataArray.cf.guess_coord_axis
4950
DataArray.cf.keys
5051
DataArray.cf.rename_like
@@ -80,6 +81,7 @@ Methods
8081
Dataset.cf.add_bounds
8182
Dataset.cf.bounds_to_vertices
8283
Dataset.cf.decode_vertical_coords
84+
Dataset.cf.differentiate
8385
Dataset.cf.get_bounds
8486
Dataset.cf.get_bounds_dim_name
8587
Dataset.cf.guess_coord_axis

doc/whats-new.rst

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

6+
v0.5.2 (unreleased)
7+
===================
8+
9+
- :py:meth:`DataArray.cf.differentiate` and :py:meth:`Dataset.cf.differentiate` can optionally correct
10+
sign of the derivative by interpreting the ``"positive"`` attribute. By `Deepak Cherian`_.
11+
612
v0.5.1 (Feb 24, 2021)
713
=====================
814

0 commit comments

Comments
 (0)