Skip to content

Commit e86a848

Browse files
authored
Merge pull request #875 from davidhassell/to-units
New `to_units` methods to allow changing units in a chain
2 parents 8886055 + 94b8da4 commit e86a848

20 files changed

+342
-48
lines changed

Changelog.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
Version NEXTVERSION
2+
--------------
3+
4+
**2025-??-??**
5+
6+
* New methods to allow changing units in a chain: `cf.Field.to_units`,
7+
`cf.Data.to_units`
8+
(https://github.com/NCAS-CMS/cf-python/issues/874)
9+
10+
----
11+
112
Version 3.18.0
213
--------------
314

cf/data/data.py

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5651,13 +5651,21 @@ def change_calendar(self, calendar, inplace=False, i=False):
56515651
@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
56525652
@_inplace_enabled(default=False)
56535653
def override_units(self, units, inplace=False, i=False):
5654-
"""Override the data array units.
5654+
"""Override the units.
56555655
5656-
Not to be confused with setting the `Units` attribute to units
5657-
which are equivalent to the original units. This is different
5658-
because in this case the new units need not be equivalent to the
5659-
original ones and the data array elements will not be changed to
5660-
reflect the new units.
5656+
The new units need not be equivalent to the original ones, and
5657+
the data array elements will not be changed to reflect the new
5658+
units. Therefore, this method should only be used when it is
5659+
known that the data array values are correct but the units
5660+
have incorrectly encoded.
5661+
5662+
Not to be confused with changing to equivalent units with the
5663+
`to_units` method or the `Units`, `units`, or `calendar`
5664+
attributes. These approaches also convert the data to have the
5665+
new units.
5666+
5667+
.. seealso:: `override_calendar`, `to_units`, `Units`,
5668+
`units`, `calendar`
56615669
56625670
:Parameters:
56635671
@@ -5695,13 +5703,20 @@ def override_units(self, units, inplace=False, i=False):
56955703
@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
56965704
@_inplace_enabled(default=False)
56975705
def override_calendar(self, calendar, inplace=False, i=False):
5698-
"""Override the calendar of the data array elements.
5706+
"""Override the calendar of date-time units.
5707+
5708+
The new calendar need not be equivalent to the original one,
5709+
and the data array elements will not be changed to reflect the
5710+
new calendar. Therefore, this method should only be used when
5711+
it is known that the data array values are correct but the
5712+
calendar has been incorrectly encoded.
5713+
5714+
Not to be confused with changing to an equivalent calendar
5715+
with the `to_units` method or the `Units` or `calendar`
5716+
attributes.
56995717
5700-
Not to be confused with using the `change_calendar` method or
5701-
setting the `d.Units.calendar`. `override_calendar` is different
5702-
because the new calendar need not be equivalent to the original
5703-
ones and the data array elements will not be changed to reflect
5704-
the new units.
5718+
.. seealso:: `override_units`, `to_units`, `Units`, `units`,
5719+
`calendar`
57055720
57065721
:Parameters:
57075722
@@ -7365,6 +7380,57 @@ def to_memory(self):
73657380
"Consider using 'Data.persist' instead."
73667381
)
73677382

7383+
@_inplace_enabled(default=False)
7384+
def to_units(self, units, inplace=False):
7385+
"""Change the data array units.
7386+
7387+
Changing the units causes the data values to be changed
7388+
to match the new units, therefore the new units must be
7389+
equivalent to the existing ones.
7390+
7391+
Not to be confused with overriding the units with
7392+
`override_units`
7393+
7394+
.. versionadded:: NEXTVERSION
7395+
7396+
.. seealso:: `override_units`, `override_calendar`, `Units`,
7397+
`units`, `calendar`
7398+
7399+
:Parameters:
7400+
7401+
units: `str` or `Units`
7402+
The new units for the data array.
7403+
7404+
{{inplace: `bool`, optional}}
7405+
7406+
:Returns:
7407+
7408+
`Data` or `None`
7409+
The new data, or `None` if the operation was in-place.
7410+
7411+
**Examples**
7412+
7413+
>>> d = cf.Data([1, 2], 'km')
7414+
>>> e = d.to_units('metre')
7415+
>>> print(e.Units)
7416+
'metre'
7417+
>>> print(e.array)
7418+
[1000. 2000.]
7419+
>>> e.to_units('miles', inplace=True)
7420+
>>> print(e.Units)
7421+
'miles'
7422+
>>> print(e.array)
7423+
[0.62137119 1.24274238]
7424+
>>> e.to_units('degC')
7425+
Traceback (most recent call last)
7426+
...
7427+
ValueError: Can't set Units to <Units: degC> that are not equivalent to the current units <Units: miles>. Consider using the override_units method instead.
7428+
7429+
"""
7430+
d = _inplace_enabled_define_and_cleanup(self)
7431+
d.Units = self._Units_class(units)
7432+
return d
7433+
73687434
@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
73697435
@_inplace_enabled(default=False)
73707436
def trunc(self, inplace=False, i=False):

cf/mixin/propertiesdata.py

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4431,6 +4431,71 @@ def to_dask_array(self):
44314431

44324432
return data.to_dask_array()
44334433

4434+
@_inplace_enabled(default=False)
4435+
def to_units(self, units, inplace=False):
4436+
"""Change the data array units.
4437+
4438+
Changing the units causes the data values to be changed
4439+
to match the new units, therefore the new units must be
4440+
equivalent to the existing ones.
4441+
4442+
Not to be confused with overriding the units with
4443+
`override_units`
4444+
4445+
.. versionadded:: NEXTVERSION
4446+
4447+
.. seealso:: `override_units`, `override_calendar`, `Units`,
4448+
`units`, `calendar`
4449+
4450+
:Parameters:
4451+
4452+
units: `str` or `Units`
4453+
The new units for the data array.
4454+
4455+
{{inplace: `bool`, optional}}
4456+
4457+
:Returns:
4458+
4459+
`{{class}}` or `None`
4460+
The construct with data converted to the new units, or
4461+
`None` if the operation was in-place.
4462+
4463+
**Examples**
4464+
4465+
>>> print(f.Units)
4466+
'km'
4467+
>>> print(f.array)
4468+
[1 2]
4469+
>>> g = f.to_units('metre')
4470+
>>> print(g.Units)
4471+
'metre'
4472+
>>> print(g.array)
4473+
[1000. 2000.]
4474+
>>> g.to_units('miles', inplace=True)
4475+
>>> print(g.array)
4476+
[0.62137119 1.24274238]
4477+
>>> g.to_units('degC')
4478+
Traceback (most recent call last)
4479+
...
4480+
ValueError: Can't set Units to <Units: degC> that are not equivalent to the current units <Units: miles>. Consider using the override_units method instead.
4481+
4482+
"""
4483+
f = _inplace_enabled_define_and_cleanup(self)
4484+
4485+
data = f.get_data(None)
4486+
if data is not None:
4487+
data.to_units(units, inplace=True)
4488+
f.set_data(data, copy=False)
4489+
else:
4490+
f._custom["Units"] = Units(units)
4491+
4492+
# Change the Units on the period
4493+
period = f.period()
4494+
if period is not None:
4495+
f.period(period=period.to_units(units))
4496+
4497+
return f
4498+
44344499
@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
44354500
@_inplace_enabled(default=False)
44364501
def trunc(self, inplace=False, i=False):
@@ -4954,15 +5019,16 @@ def override_calendar(self, calendar, inplace=False, i=False):
49545019
49555020
The new calendar need not be equivalent to the original one,
49565021
and the data array elements will not be changed to reflect the
4957-
new units. Therefore, this method should only be used when it
4958-
is known that the data array values are correct but the
5022+
new calendar. Therefore, this method should only be used when
5023+
it is known that the data array values are correct but the
49595024
calendar has been incorrectly encoded.
49605025
4961-
Not to be confused with setting the `calendar` or `Units`
4962-
attributes to a calendar which is equivalent to the original
4963-
calendar
5026+
Not to be confused with changing to an equivalent calendar
5027+
with the `to_units` method or the `Units` or `calendar`
5028+
attributes.
49645029
4965-
.. seealso:: `calendar`, `override_units`, `units`, `Units`
5030+
.. seealso:: `override_units`, `to_units`, `units`, `Units`,
5031+
`calendar`
49665032
49675033
:Parameters:
49685034
@@ -4976,13 +5042,20 @@ def override_calendar(self, calendar, inplace=False, i=False):
49765042
:Returns:
49775043
49785044
`{{class}}` or `None`
4979-
TODO
5045+
The construct with data converted to the new units, or
5046+
`None` if the operation was in-place.
49805047
49815048
**Examples**
49825049
4983-
TODO
4984-
5050+
>>> f.Units
5051+
<Units: days since 2020-02-28>
5052+
>>> print(f.array)
5053+
1
49855054
>>> g = f.override_calendar('noleap')
5055+
>>> g.Units
5056+
<Units: days since 2020-02-28 noleap>
5057+
>>> print(f.array)
5058+
1
49865059
49875060
"""
49885061
v = _inplace_enabled_define_and_cleanup(self)
@@ -5015,11 +5088,13 @@ def override_units(self, units, inplace=False, i=False):
50155088
known that the data array values are correct but the units
50165089
have incorrectly encoded.
50175090
5018-
Not to be confused with setting the `units` or `Units`
5019-
attributes to units which are equivalent to the original
5020-
units.
5091+
Not to be confused with changing to equivalent units with the
5092+
`to_units` method or the `Units`, `units`, or `calendar`
5093+
attributes. These approaches also convert the data to have the
5094+
new units.
50215095
5022-
.. seealso:: `calendar`, `override_calendar`, `units`, `Units`
5096+
.. seealso:: `override_calendar`, `to_units`, `units`,
5097+
`Units`,`calendar`
50235098
50245099
:Parameters:
50255100
@@ -5033,8 +5108,8 @@ def override_units(self, units, inplace=False, i=False):
50335108
:Returns:
50345109
50355110
`{{class}}` or `None`
5036-
5037-
TODO
5111+
The construct with data converted to the new units, or
5112+
`None` if the operation was in-place.
50385113
50395114
**Examples**
50405115

0 commit comments

Comments
 (0)