Skip to content

Commit 1668e0b

Browse files
authored
Merge pull request NCAS-CMS#702 from davidhassell/weighted-sum
`cf.Field.collapse`: Fix "sum", "sum_of_weights" and "sum_of_weights2" when weighted
2 parents 70bec02 + 4188259 commit 1668e0b

File tree

4 files changed

+169
-82
lines changed

4 files changed

+169
-82
lines changed

Changelog.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
version 3.16.0
2+
--------------
3+
4+
**2023-??-??**
5+
6+
* Fix bug that caused `cf.Field.collapse` to give incorrect results
7+
for the "sum", "sum_of_weights" and "sum_of_weights2" methods, only
8+
in the case that weights have been requested
9+
(https://github.com/NCAS-CMS/cf-python/issues/701)
10+
111
version 3.15.4
212
--------------
313

cf/field.py

Lines changed: 66 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@
162162
"average",
163163
"sd",
164164
"standard_deviation",
165+
"sum",
165166
"var",
166167
"variance",
167-
# 'sum_of_weights',
168-
# 'sum_of_weights2',
168+
"sum_of_weights",
169+
"sum_of_weights2",
169170
"integral",
170171
"root_mean_square",
171172
)
@@ -5002,6 +5003,9 @@ def weights(
50025003
comp[key] = self._weights_scale(w, scale)
50035004

50045005
for w in comp.values():
5006+
if not measure:
5007+
w.override_units("1", inplace=True)
5008+
50055009
mn = w.minimum()
50065010
if mn <= 0:
50075011
raise ValueError(
@@ -6194,7 +6198,7 @@ def collapse(
61946198
group_span=None,
61956199
group_contiguous=1,
61966200
measure=False,
6197-
scale=1,
6201+
scale=None,
61986202
radius="earth",
61996203
great_circle=False,
62006204
verbose=None,
@@ -6719,6 +6723,10 @@ def collapse(
67196723
.. note:: By default *weights* is `None`, resulting in
67206724
**unweighted calculations**.
67216725

6726+
.. note:: Unless the *method* is ``'integral'``, the
6727+
units of the weights are not combined with
6728+
the field's units in the collapsed field.
6729+
67226730
If the alternative form of providing the collapse method
67236731
and axes combined as a CF cell methods-like string via the
67246732
*method* parameter has been used, then the *axes*
@@ -6762,57 +6770,58 @@ def collapse(
67626770
time you could set ``weights=('area', 'T')``.
67636771

67646772
measure: `bool`, optional
6765-
Create weights which are cell measures, i.e. which
6766-
describe actual cell sizes (e.g. cell area) with
6767-
appropriate units (e.g. metres squared). By default the
6768-
weights are normalised and have arbitrary units.
6773+
If True, and *weights* is not `None`, create weights
6774+
which are cell measures, i.e. which describe actual
6775+
cell sizes (e.g. cell area) with appropriate units
6776+
(e.g. metres squared). By default the weights units
6777+
are ignored.
67696778

67706779
Cell measures can be created for any combination of
6771-
axes. For example, cell measures for a time axis are the
6772-
time span for each cell with canonical units of seconds;
6773-
cell measures for the combination of four axes
6774-
representing time and three dimensional space could have
6775-
canonical units of metres cubed seconds.
6780+
axes. For example, cell measures for a time axis are
6781+
the time span for each cell with canonical units of
6782+
seconds; cell measures for the combination of four
6783+
axes representing time and three dimensional space
6784+
could have canonical units of metres cubed seconds.
67766785

6777-
When collapsing with the ``'integral'`` method, *measure*
6778-
must be True, and the units of the weights are
6779-
incorporated into the units of the returned field
6786+
When collapsing with the ``'integral'`` method,
6787+
*measure* must be True, and the units of the weights
6788+
are incorporated into the units of the returned field
67806789
construct.
67816790

67826791
.. note:: Specifying cell volume weights via
67836792
``weights=['X', 'Y', 'Z']`` or
6784-
``weights=['area', 'Z']`` (or other equivalents)
6785-
will produce **an incorrect result if the
6786-
vertical dimension coordinates do not define the
6787-
actual height or depth thickness of every cell
6788-
in the domain**. In this case,
6789-
``weights='volume'`` should be used instead,
6790-
which requires the field construct to have a
6791-
"volume" cell measure construct.
6793+
``weights=['area', 'Z']`` (or other
6794+
equivalents) will produce **an incorrect
6795+
result if the vertical dimension coordinates
6796+
do not define the actual height or depth
6797+
thickness of every cell in the domain**. In
6798+
this case, ``weights='volume'`` should be
6799+
used instead, which requires the field
6800+
construct to have a "volume" cell measure
6801+
construct.
67926802

6793-
If ``weights=True`` then care also needs to be
6794-
taken, as a "volume" cell measure construct will
6795-
be used if present, otherwise the cell volumes
6796-
will be calculated using the size of the
6797-
vertical coordinate cells.
6803+
If ``weights=True`` then care also needs to
6804+
be taken, as a "volume" cell measure
6805+
construct will be used if present, otherwise
6806+
the cell volumes will be calculated using
6807+
the size of the vertical coordinate cells.
67986808

67996809
.. versionadded:: 3.0.2
68006810

68016811
scale: number or `None`, optional
68026812
If set to a positive number then scale the weights so
68036813
that they are less than or equal to that number. If
6804-
set to `None` the weights are not scaled. In general
6805-
the default is for weights to be scaled to lie between
6806-
0 and 1; however if *measure* is True then the weights
6807-
are never scaled and the value of *scale* is taken as
6808-
`None`, regardless of its setting.
6814+
set to `None`, the default, then the weights are not
6815+
scaled.
68096816

68106817
*Parameter example:*
68116818
To scale all weights so that they lie between 0 and
6812-
10 ``scale=10``.
6819+
1 ``scale=1``.
68136820

68146821
.. versionadded:: 3.0.2
68156822

6823+
.. versionchanged:: 3.16.0 Default changed to `None`
6824+
68166825
radius: optional
68176826
Specify the radius used for calculating the areas of
68186827
cells defined in spherical polar coordinates. The
@@ -7698,6 +7707,12 @@ def collapse(
76987707
# ------------------------------------------------------------
76997708
# Parse the methods and axes
77007709
# ------------------------------------------------------------
7710+
if measure and scale is not None:
7711+
raise ValueError(
7712+
"'scale' must be None when 'measure' is True. "
7713+
f"Got: scale={scale!r}"
7714+
)
7715+
77017716
if ":" in method:
77027717
# Convert a cell methods string (such as 'area: mean dim3:
77037718
# dim2: max T: minimum height: variance') to a CellMethod
@@ -7916,8 +7931,15 @@ def collapse(
79167931
g_weights = None
79177932
else:
79187933
if measure:
7919-
# Never scale weights that are cell measures
7920-
scale = None
7934+
if method not in (
7935+
"integral",
7936+
"sum_of_weights",
7937+
"sum_of_weights2",
7938+
):
7939+
raise ValueError(
7940+
f"Can't set measure=True for {method!r} "
7941+
"collapses"
7942+
)
79217943
elif method == "integral":
79227944
raise ValueError(
79237945
f"Must set measure=True for {method!r} collapses"
@@ -8006,8 +8028,14 @@ def collapse(
80068028
d_kwargs = {}
80078029
if weights is not None:
80088030
if measure:
8009-
# Never scale weights that are cell measures
8010-
scale = None
8031+
if method not in (
8032+
"integral",
8033+
"sum_of_weights",
8034+
"sum_of_weights2",
8035+
):
8036+
raise ValueError(
8037+
f"Can't set measure=True for {method!r} collapses"
8038+
)
80118039
elif method == "integral":
80128040
raise ValueError(
80138041
f"Must set measure=True for {method!r} collapses"

cf/test/test_Field.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,6 @@ def test_Field_collapse(self):
441441

442442
for axes in axes_combinations(f):
443443
for method in (
444-
"sum",
445444
"min",
446445
"max",
447446
"minimum_absolute_value",
@@ -451,8 +450,6 @@ def test_Field_collapse(self):
451450
"sample_size",
452451
"sum_of_squares",
453452
"median",
454-
"sum_of_weights",
455-
"sum_of_weights2",
456453
):
457454
for weights in (None, "area"):
458455
a = f.collapse(method, axes=axes, weights=weights).data
@@ -462,10 +459,13 @@ def test_Field_collapse(self):
462459
)
463460

464461
for method in (
462+
"sum",
465463
"mean",
466464
"mean_absolute_value",
467465
"mean_of_upper_decile",
468466
"root_mean_square",
467+
"sum_of_weights",
468+
"sum_of_weights2",
469469
):
470470
for weights in (None, "area"):
471471
if weights is not None:

0 commit comments

Comments
 (0)