Skip to content

Commit 2db4483

Browse files
committed
collapse sum
1 parent 70bec02 commit 2db4483

3 files changed

Lines changed: 131 additions & 58 deletions

File tree

cf/field.py

Lines changed: 38 additions & 14 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
)
@@ -6194,7 +6195,7 @@ def collapse(
61946195
group_span=None,
61956196
group_contiguous=1,
61966197
measure=False,
6197-
scale=1,
6198+
scale=None,
61986199
radius="earth",
61996200
great_circle=False,
62006201
verbose=None,
@@ -6719,6 +6720,11 @@ def collapse(
67196720
.. note:: By default *weights* is `None`, resulting in
67206721
**unweighted calculations**.
67216722

6723+
.. note:: Unless *measure* is True, the units of
6724+
weights are ignored default *weights* is
6725+
`None`, resulting in **unweighted
6726+
calculations**.
6727+
67226728
If the alternative form of providing the collapse method
67236729
and axes combined as a CF cell methods-like string via the
67246730
*method* parameter has been used, then the *axes*
@@ -6765,7 +6771,7 @@ def collapse(
67656771
Create weights which are cell measures, i.e. which
67666772
describe actual cell sizes (e.g. cell area) with
67676773
appropriate units (e.g. metres squared). By default the
6768-
weights are normalised and have arbitrary units.
6774+
weights units are ignored.
67696775

67706776
Cell measures can be created for any combination of
67716777
axes. For example, cell measures for a time axis are the
@@ -6801,18 +6807,17 @@ def collapse(
68016807
scale: number or `None`, optional
68026808
If set to a positive number then scale the weights so
68036809
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.
6810+
set to `None`, the default, then the weights are not
6811+
scaled.
68096812

68106813
*Parameter example:*
68116814
To scale all weights so that they lie between 0 and
6812-
10 ``scale=10``.
6815+
1 ``scale=1``.
68136816

68146817
.. versionadded:: 3.0.2
68156818

6819+
.. versionchanged:: 3.16.0 Default changed to `None`
6820+
68166821
radius: optional
68176822
Specify the radius used for calculating the areas of
68186823
cells defined in spherical polar coordinates. The
@@ -7698,6 +7703,12 @@ def collapse(
76987703
# ------------------------------------------------------------
76997704
# Parse the methods and axes
77007705
# ------------------------------------------------------------
7706+
if measure and scale is not None:
7707+
raise ValueError(
7708+
"'scale' must be None when 'measure' is True. "
7709+
f"Got: scale={scale!r}"
7710+
)
7711+
77017712
if ":" in method:
77027713
# Convert a cell methods string (such as 'area: mean dim3:
77037714
# dim2: max T: minimum height: variance') to a CellMethod
@@ -7916,8 +7927,15 @@ def collapse(
79167927
g_weights = None
79177928
else:
79187929
if measure:
7919-
# Never scale weights that are cell measures
7920-
scale = None
7930+
if method not in (
7931+
"integral",
7932+
"sum_of_weights",
7933+
"sum_of_weights2",
7934+
):
7935+
raise ValueError(
7936+
f"Can't set measure=True for {method!r} "
7937+
"collapses"
7938+
)
79217939
elif method == "integral":
79227940
raise ValueError(
79237941
f"Must set measure=True for {method!r} collapses"
@@ -8006,8 +8024,14 @@ def collapse(
80068024
d_kwargs = {}
80078025
if weights is not None:
80088026
if measure:
8009-
# Never scale weights that are cell measures
8010-
scale = None
8027+
if method not in (
8028+
"integral",
8029+
"sum_of_weights",
8030+
"sum_of_weights2",
8031+
):
8032+
raise ValueError(
8033+
f"Can't set measure=True for {method!r} collapses"
8034+
)
80118035
elif method == "integral":
80128036
raise ValueError(
80138037
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:

cf/test/test_collapse.py

Lines changed: 90 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import datetime
22
import faulthandler
3-
import inspect
43
import os
54
import unittest
65

@@ -17,17 +16,7 @@ def setUp(self):
1716
os.path.dirname(os.path.abspath(__file__)), "test_file2.nc"
1817
)
1918

20-
self.test_only = []
21-
22-
# self.test_only = ['nought']
23-
# self.test_only = ['test_Field_collapse']
24-
# self.test_only = ['test_Field_collapse_CLIMATOLOGICAL_TIME']
25-
# self.test_only = ['test_Field_collapse_GROUPS']
26-
2719
def test_Field_collapse_CLIMATOLOGICAL_TIME(self):
28-
if self.test_only and inspect.stack()[0][3] not in self.test_only:
29-
return
30-
3120
verbose = False
3221

3322
f = cf.example_field(2)
@@ -258,9 +247,6 @@ def test_Field_collapse_CLIMATOLOGICAL_TIME(self):
258247
self.assertEqual(list(g.shape), expected_shape)
259248

260249
def test_Field_collapse(self):
261-
if self.test_only and inspect.stack()[0][3] not in self.test_only:
262-
return
263-
264250
verbose = False
265251

266252
f = cf.read(self.filename2)[0]
@@ -380,22 +366,7 @@ def test_Field_collapse(self):
380366
f"{bound.day}!={group.offset.day}, group={group}",
381367
)
382368

383-
# for group in (cf.D(30),
384-
# cf.D(30, month=12),
385-
# cf.D(30, day=16),
386-
# cf.D(30, month=11, day=27)):
387-
# g = f.collapse('T: mean', group=group)
388-
# bound = g.coord('T').bounds.datetime_array[0, 1]
389-
# self.assertEqual(
390-
# bound.day, group.offset.day,
391-
# "{}!={}, bound={}, group={}".format(
392-
# bound.day, group.offset.day, bound, group)
393-
# )
394-
395369
def test_Field_collapse_WEIGHTS(self):
396-
if self.test_only and inspect.stack()[0][3] not in self.test_only:
397-
return
398-
399370
verbose = False
400371

401372
f = cf.example_field(2)
@@ -415,9 +386,6 @@ def test_Field_collapse_WEIGHTS(self):
415386
i.collapse("area: maximum")
416387

417388
def test_Field_collapse_GROUPS(self):
418-
if self.test_only and inspect.stack()[0][3] not in self.test_only:
419-
return
420-
421389
verbose = False
422390

423391
f = cf.example_field(2)
@@ -695,15 +663,96 @@ def test_Field_collapse_GROUPS(self):
695663
print(g.constructs)
696664
self.assertEqual(list(g.shape), expected_shape, g.shape)
697665

698-
699-
# g = f[::2].collapse('T: mean', group=cf.M(5, month=12),
700-
# group_span=cf.M(5),group_contiguous=1)
701-
# g = f.collapse('T: mean', group=cf.M(5, month= 3),
702-
# group_contiguous=1)
703-
# g = f.collapse('T: mean', group=cf.M(5, month=12),
704-
# group_contiguous=2)
705-
# g = f.collapse('T: mean', group=cf.M(5, month= 3),
706-
# group_contiguous=2)
666+
def test_Field_collapse_sum(self):
667+
f = cf.example_field(0)
668+
w = f.weights("area", measure=True)
669+
a = f.array
670+
wa = w.array
671+
ws = a * wa
672+
673+
g = f.collapse("area: sum")
674+
self.assertTrue((g.array == a.sum()).all())
675+
676+
g = f.collapse("area: sum", weights=w)
677+
self.assertTrue((g.array == ws.sum()).all())
678+
self.assertEqual(g.Units, cf.Units("1"))
679+
680+
g = f.collapse("area: sum", weights=w, scale=1)
681+
self.assertTrue((g.array == (ws / wa.max()).sum()).all())
682+
self.assertEqual(g.Units, cf.Units("1"))
683+
684+
g = f.collapse("area: sum", weights=w)
685+
self.assertTrue((g.array == ws.sum()).all())
686+
self.assertEqual(g.Units, cf.Units("1"))
687+
688+
# Can't set measure=True for 'sum' collapses
689+
with self.assertRaises(ValueError):
690+
g = f.collapse("area: sum", weights=w, measure=True)
691+
692+
def test_Field_collapse_integral(self):
693+
f = cf.example_field(0)
694+
w = f.weights("area", measure=True)
695+
a = f.array
696+
wa = w.array
697+
ws = a * wa
698+
699+
g = f.collapse("area: integral", weights=w, measure=True)
700+
self.assertTrue((g.array == ws.sum()).all())
701+
self.assertEqual(g.Units, cf.Units("m2"))
702+
703+
# Must set the 'weights' parameter for 'integral' collapses
704+
with self.assertRaises(ValueError):
705+
g = f.collapse("area: integral")
706+
707+
# Must set measure=True for 'integral' collapses
708+
with self.assertRaises(ValueError):
709+
g = f.collapse("area: integral", weights=w)
710+
711+
# 'scale' must be None when 'measure' is True
712+
with self.assertRaises(ValueError):
713+
g = f.collapse("area: integral", weights=w, measure=True, scale=1)
714+
715+
def test_Field_collapse_sum_weights(self):
716+
f = cf.example_field(0)
717+
w = f.weights("area", measure=True)
718+
wa = w.array
719+
720+
g = f.collapse("area: sum_of_weights")
721+
self.assertTrue((g.array == 40).all())
722+
self.assertEqual(g.Units, cf.Units())
723+
724+
g = f.collapse("area: sum_of_weights", weights=w)
725+
self.assertTrue((g.array == wa.sum()).all())
726+
self.assertEqual(g.Units, cf.Units("m2"))
727+
728+
g = f.collapse("area: sum_of_weights", weights=w, measure=True)
729+
self.assertTrue((g.array == wa.sum()).all())
730+
self.assertEqual(g.Units, cf.Units("m2"))
731+
732+
g = f.collapse("area: sum_of_weights", weights=w, scale=1)
733+
self.assertTrue((g.array == (wa / wa.max()).sum()).all())
734+
self.assertEqual(g.Units, cf.Units("1"))
735+
736+
def test_Field_collapse_sum_weights2(self):
737+
f = cf.example_field(0)
738+
w = f.weights("area", measure=True)
739+
wa = w.array**2
740+
741+
g = f.collapse("area: sum_of_weights2")
742+
self.assertTrue((g.array == 40).all())
743+
self.assertEqual(g.Units, cf.Units())
744+
745+
g = f.collapse("area: sum_of_weights2", weights=w)
746+
self.assertTrue((g.array == wa.sum()).all())
747+
self.assertEqual(g.Units, cf.Units("m4"))
748+
749+
g = f.collapse("area: sum_of_weights2", weights=w, measure=True)
750+
self.assertTrue((g.array == wa.sum()).all())
751+
self.assertEqual(g.Units, cf.Units("m4"))
752+
753+
g = f.collapse("area: sum_of_weights2", weights=w, scale=1)
754+
self.assertTrue((g.array == (wa / wa.max()).sum()).all())
755+
self.assertEqual(g.Units, cf.Units("1"))
707756

708757

709758
if __name__ == "__main__":

0 commit comments

Comments
 (0)