Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
Version NEXTVERSION
--------------

**2025-??-??**

* New methods to allow changing units in a chain: `cf.Field.to_units`,
`cf.Data.to_units`
(https://github.com/NCAS-CMS/cf-python/issues/874)

----

Version 3.18.0
--------------

Expand Down
90 changes: 78 additions & 12 deletions cf/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5651,13 +5651,21 @@ def change_calendar(self, calendar, inplace=False, i=False):
@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
@_inplace_enabled(default=False)
def override_units(self, units, inplace=False, i=False):
"""Override the data array units.
"""Override the units.

Not to be confused with setting the `Units` attribute to units
which are equivalent to the original units. This is different
because in this case the new units need not be equivalent to the
original ones and the data array elements will not be changed to
reflect the new units.
The new units need not be equivalent to the original ones, and
the data array elements will not be changed to reflect the new
units. Therefore, this method should only be used when it is
known that the data array values are correct but the units
have incorrectly encoded.

Not to be confused with changing to equivalent units with the
`to_units` method or the `Units`, `units`, or `calendar`
attributes. These approaches also convert the data to have the
new units.

.. seealso:: `override_calendar`, `to_units`, `Units`,
`units`, `calendar`

:Parameters:

Expand Down Expand Up @@ -5695,13 +5703,20 @@ def override_units(self, units, inplace=False, i=False):
@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
@_inplace_enabled(default=False)
def override_calendar(self, calendar, inplace=False, i=False):
"""Override the calendar of the data array elements.
"""Override the calendar of date-time units.

The new calendar need not be equivalent to the original one,
and the data array elements will not be changed to reflect the
new calendar. Therefore, this method should only be used when
it is known that the data array values are correct but the
calendar has been incorrectly encoded.

Not to be confused with changing to an equivalent calendar
with the `to_units` method or the `Units` or `calendar`
attributes.

Not to be confused with using the `change_calendar` method or
setting the `d.Units.calendar`. `override_calendar` is different
because the new calendar need not be equivalent to the original
ones and the data array elements will not be changed to reflect
the new units.
.. seealso:: `override_units`, `to_units`, `Units`, `units`,
`calendar`

:Parameters:

Expand Down Expand Up @@ -7365,6 +7380,57 @@ def to_memory(self):
"Consider using 'Data.persist' instead."
)

@_inplace_enabled(default=False)
def to_units(self, units, inplace=False):
"""Change the data array units.

Changing the units causes the data values to be changed
to match the new units, therefore the new units must be
equivalent to the existing ones.

Not to be confused with overriding the units with
`override_units`

.. versionadded:: NEXTVERSION

.. seealso:: `override_units`, `override_calendar`, `Units`,
`units`, `calendar`

:Parameters:

units: `str` or `Units`
The new units for the data array.

{{inplace: `bool`, optional}}

:Returns:

`Data` or `None`
The new data, or `None` if the operation was in-place.

**Examples**

>>> d = cf.Data([1, 2], 'km')
>>> e = d.to_units('metre')
>>> print(e.Units)
'metre'
>>> print(e.array)
[1000. 2000.]
>>> e.to_units('miles', inplace=True)
>>> print(e.Units)
'miles'
>>> print(e.array)
[0.62137119 1.24274238]
>>> e.to_units('degC')
Traceback (most recent call last)
...
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.

"""
d = _inplace_enabled_define_and_cleanup(self)
d.Units = self._Units_class(units)
return d

@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
@_inplace_enabled(default=False)
def trunc(self, inplace=False, i=False):
Expand Down
105 changes: 90 additions & 15 deletions cf/mixin/propertiesdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4431,6 +4431,71 @@ def to_dask_array(self):

return data.to_dask_array()

@_inplace_enabled(default=False)
def to_units(self, units, inplace=False):
"""Change the data array units.

Changing the units causes the data values to be changed
to match the new units, therefore the new units must be
equivalent to the existing ones.

Not to be confused with overriding the units with
`override_units`

.. versionadded:: NEXTVERSION

.. seealso:: `override_units`, `override_calendar`, `Units`,
`units`, `calendar`

:Parameters:

units: `str` or `Units`
The new units for the data array.

{{inplace: `bool`, optional}}

:Returns:

`{{class}}` or `None`
The construct with data converted to the new units, or
`None` if the operation was in-place.

**Examples**

>>> print(f.Units)
'km'
>>> print(f.array)
[1 2]
>>> g = f.to_units('metre')
>>> print(g.Units)
'metre'
>>> print(g.array)
[1000. 2000.]
>>> g.to_units('miles', inplace=True)
>>> print(g.array)
[0.62137119 1.24274238]
>>> g.to_units('degC')
Traceback (most recent call last)
...
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.

"""
f = _inplace_enabled_define_and_cleanup(self)

data = f.get_data(None)
if data is not None:
data.to_units(units, inplace=True)
f.set_data(data, copy=False)
else:
f._custom["Units"] = Units(units)

# Change the Units on the period
period = f.period()
if period is not None:
f.period(period=period.to_units(units))

return f

@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
@_inplace_enabled(default=False)
def trunc(self, inplace=False, i=False):
Expand Down Expand Up @@ -4954,15 +5019,16 @@ def override_calendar(self, calendar, inplace=False, i=False):

The new calendar need not be equivalent to the original one,
and the data array elements will not be changed to reflect the
new units. Therefore, this method should only be used when it
is known that the data array values are correct but the
new calendar. Therefore, this method should only be used when
it is known that the data array values are correct but the
calendar has been incorrectly encoded.

Not to be confused with setting the `calendar` or `Units`
attributes to a calendar which is equivalent to the original
calendar
Not to be confused with changing to an equivalent calendar
with the `to_units` method or the `Units` or `calendar`
attributes.

.. seealso:: `calendar`, `override_units`, `units`, `Units`
.. seealso:: `override_units`, `to_units`, `units`, `Units`,
`calendar`

:Parameters:

Expand All @@ -4976,13 +5042,20 @@ def override_calendar(self, calendar, inplace=False, i=False):
:Returns:

`{{class}}` or `None`
TODO
The construct with data converted to the new units, or
`None` if the operation was in-place.

**Examples**

TODO

>>> f.Units
<Units: days since 2020-02-28>
>>> print(f.array)
1
>>> g = f.override_calendar('noleap')
>>> g.Units
<Units: days since 2020-02-28 noleap>
>>> print(f.array)
1

"""
v = _inplace_enabled_define_and_cleanup(self)
Expand Down Expand Up @@ -5015,11 +5088,13 @@ def override_units(self, units, inplace=False, i=False):
known that the data array values are correct but the units
have incorrectly encoded.

Not to be confused with setting the `units` or `Units`
attributes to units which are equivalent to the original
units.
Not to be confused with changing to equivalent units with the
`to_units` method or the `Units`, `units`, or `calendar`
attributes. These approaches also convert the data to have the
new units.

.. seealso:: `calendar`, `override_calendar`, `units`, `Units`
.. seealso:: `override_calendar`, `to_units`, `units`,
`Units`,`calendar`

:Parameters:

Expand All @@ -5033,8 +5108,8 @@ def override_units(self, units, inplace=False, i=False):
:Returns:

`{{class}}` or `None`

TODO
The construct with data converted to the new units, or
`None` if the operation was in-place.

**Examples**

Expand Down
Loading