Skip to content

Commit 017cec7

Browse files
authored
Allow add_season_year() to optionally send spans backwards (#5573)
* New preceding year functionality for add_season_year(). * Modernise test_coord_categorisation. * New test for backwards behaviour. * What's New entry. * Rename to use_year_at_season_start. * Use np.zeros.
1 parent ce813ff commit 017cec7

File tree

4 files changed

+307
-226
lines changed

4 files changed

+307
-226
lines changed

docs/src/whatsnew/latest.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ This document explains the changes made to Iris for this release
3838
and :class:`~iris.coord_systems.RotatedMercator` coordinate systems,
3939
complete with NetCDF loading and saving. (:pull:`5548`)
4040

41+
#. `@trexfeathers`_ added the ``use_year_at_season_start`` parameter to
42+
:func:`iris.coord_categorisation.add_season_year`. When
43+
``use_year_at_season_start==True``: seasons spanning the year boundary (e.g.
44+
Winter - December to February) will be assigned to the preceding year (e.g.
45+
the year of December) instead of the following year (the default behaviour).
46+
(:pull:`5573`)
47+
4148

4249
🐛 Bugs Fixed
4350
=============

lib/iris/coord_categorisation.py

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -271,20 +271,33 @@ def _validate_seasons(seasons):
271271
return
272272

273273

274-
def _month_year_adjusts(seasons):
275-
"""Compute the year adjustments required for each month.
274+
def _month_year_adjusts(seasons, use_year_at_season_start=False):
275+
"""
276+
Compute the year adjustments required for each month.
276277
277-
These determine whether the month belongs to a season in the same
278-
year or is in the start of a season that counts towards the next
279-
year.
278+
These adjustments ensure that no season spans two years by assigning months
279+
to the **next** year (use_year_at_season_start is False) or the
280+
**previous** year (use_year_at_season_start is True). E.g. Winter - djf:
281+
either assign Dec to the next year, or Jan and Feb to the previous year.
280282
281283
"""
282-
month_year_adjusts = [None, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
284+
# 1 'slot' for each month, with an extra leading 'slot' because months
285+
# are 1-indexed - January is 1, therefore corresponding to the 2nd
286+
# array index.
287+
month_year_adjusts = np.zeros(13, dtype=int)
288+
283289
for season in seasons:
284-
months = _months_in_season(season)
285-
for month in months:
286-
if month > months[-1]:
287-
month_year_adjusts[month] = 1
290+
months = np.array(_months_in_season(season))
291+
if use_year_at_season_start:
292+
months_to_shift = months < months[0]
293+
year_shift = -1
294+
else:
295+
# Sending forwards.
296+
months_to_shift = months > months[-1]
297+
year_shift = 1
298+
indices_to_shift = months[np.flatnonzero(months_to_shift)]
299+
month_year_adjusts[indices_to_shift] = year_shift
300+
288301
return month_year_adjusts
289302

290303

@@ -383,34 +396,40 @@ def _season_number(coord, value):
383396

384397

385398
def add_season_year(
386-
cube, coord, name="season_year", seasons=("djf", "mam", "jja", "son")
399+
cube,
400+
coord,
401+
name="season_year",
402+
seasons=("djf", "mam", "jja", "son"),
403+
use_year_at_season_start=False,
387404
):
388405
"""
389-
Add a categorical year-of-season coordinate, with user specified
390-
seasons.
391-
392-
Args:
393-
394-
* cube (:class:`iris.cube.Cube`):
395-
The cube containing 'coord'. The new coord will be added into
396-
it.
397-
* coord (:class:`iris.coords.Coord` or string):
398-
Coordinate in 'cube', or its name, representing time.
399-
400-
Kwargs:
401-
402-
* name (string):
403-
Name of the created coordinate. Defaults to "season_year".
404-
* seasons (:class:`list` of strings):
406+
Add a categorical year-of-season coordinate, with user specified seasons.
407+
408+
Parameters
409+
----------
410+
cube : :class:`iris.cube.Cube`
411+
The cube containing `coord`. The new coord will be added into it.
412+
coord : :class:`iris.coords.Coord` or str
413+
Coordinate in `cube`, or its name, representing time.
414+
name : str, default="season_year"
415+
Name of the created coordinate.
416+
seasons : tuple of str, default=("djf", "mam", "jja", "son")
405417
List of seasons defined by month abbreviations. Each month must
406418
appear once and only once. Defaults to standard meteorological
407-
seasons ('djf', 'mam', 'jja', 'son').
419+
seasons (``djf``, ``mam``, ``jja``, ``son``).
420+
use_year_at_season_start: bool, default=False
421+
Seasons spanning the year boundary (e.g. Winter ``djf``) will belong
422+
fully to the following year by default (e.g. the year of Jan and Feb).
423+
Set to ``True`` for spanning seasons to belong to the preceding
424+
year (e.g. the year of Dec) instead.
408425
409426
"""
410427
# Check that the seasons are valid.
411428
_validate_seasons(seasons)
412429
# Define the adjustments to be made to the year.
413-
month_year_adjusts = _month_year_adjusts(seasons)
430+
month_year_adjusts = _month_year_adjusts(
431+
seasons, use_year_at_season_start=use_year_at_season_start
432+
)
414433

415434
# Define a categorisation function.
416435
def _season_year(coord, value):

lib/iris/tests/test_coord_categorisation.py

Lines changed: 0 additions & 197 deletions
This file was deleted.

0 commit comments

Comments
 (0)