-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Drop leap days based on CF calendar type to calculate daily climatologies and departures #350
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @lee1043, @msahn, and @pochedls (optional if you want to review), this PR is ready for review. I commented areas of interest, and you can ignore other changes because they are mostly refactoring.
The climatology and departures notebook has been fixed to use hourly data for hourly calculations. The changes can be previewed here:
https://xcdat.readthedocs.io/en/feature-337-climo-highfreq/examples/climatology-and-departures.html#
Thanks!
def _drop_leap_days(self, ds: xr.Dataset): | ||
"""Drop leap days from time coordinates. | ||
|
||
This method concatenates the strings in each sublist to form a | ||
a flat list of custom season strings | ||
This method is used to drop 2/29 from leap years (if present) before | ||
calculating climatology/departures for high frequency time series data | ||
to avoid `cftime` breaking (`ValueError: invalid day number provided | ||
in cftime.DatetimeProlepticGregorian(1, 2, 29, 0, 0, 0, 0, | ||
has_year_zero=True`). | ||
|
||
Parameters | ||
---------- | ||
custom_seasons : List[List[str]] | ||
List of sublists containing month strings, with each sublist | ||
representing a custom season. | ||
ds : xr.Dataset | ||
The dataset. | ||
|
||
Returns | ||
------- | ||
Dict[str, List[str]] | ||
A dictionary with the keys being the custom season and the | ||
values being the corresponding list of months. | ||
|
||
Raises | ||
------ | ||
ValueError | ||
If exactly 12 months are not passed in the list of custom seasons. | ||
ValueError | ||
If a duplicate month(s) were found in the list of custom seasons. | ||
ValueError | ||
If a month string(s) is not supported. | ||
xr.Dataset | ||
""" | ||
predefined_months = list(MONTH_INT_TO_STR.values()) | ||
input_months = list(chain.from_iterable(custom_seasons)) | ||
|
||
if len(input_months) != len(predefined_months): | ||
raise ValueError( | ||
"Exactly 12 months were not passed in the list of custom seasons." | ||
) | ||
if len(input_months) != len(set(input_months)): | ||
raise ValueError( | ||
"Duplicate month(s) were found in the list of custom seasons." | ||
) | ||
|
||
for month in input_months: | ||
if month not in predefined_months: | ||
raise ValueError( | ||
f"The following month is not supported: '{month}'. " | ||
f"Supported months include: {predefined_months}." | ||
) | ||
|
||
c_seasons = {"".join(months): months for months in custom_seasons} | ||
|
||
return c_seasons | ||
ds = ds.sel( # type: ignore | ||
**{self._dim: ~((ds.time.dt.month == 2) & (ds.time.dt.day == 29))} | ||
) | ||
return ds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the new method to handle dropping leap years when calculating daily climatology/departures.
if ( | ||
self._freq == "day" | ||
and self._mode in ["climatology", "departures"] | ||
and self.calendar in ["gregorian", "proleptic_gregorian", "standard"] | ||
): | ||
ds = self._drop_leap_days(ds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to check whether or not to drop leap years.
* "day": groups by (month, day) for the daily cycle departures. Leap | ||
days (if present) are dropped if the CF calendar type is | ||
``"gregorian"``, ``"proleptic_gregorian"``, or ``"standard"`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstring update to inform users when leap days are dropped.
@@ -784,6 +790,88 @@ def _set_obj_attrs( | |||
else: | |||
self._season_config["custom_seasons"] = self._form_seasons(custom_seasons) | |||
|
|||
def _form_seasons(self, custom_seasons: List[List[str]]) -> Dict[str, List[str]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_form_seasons
was simply reordered, no changes were made.
@@ -1382,6 +1383,97 @@ def test_weighted_daily_climatology(self): | |||
|
|||
assert result.identical(expected) | |||
|
|||
def test_weighted_daily_climatology_drops_leap_days_with_matching_calendar(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New test for daily climatology.
@@ -1606,6 +1698,126 @@ def test_unweighted_seasonal_departures_with_JFD(self): | |||
|
|||
assert result.identical(expected) | |||
|
|||
def test_weighted_daily_departures_drops_leap_days_with_matching_calendar(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New test for daily departures.
@@ -1654,7 +1866,7 @@ def test_weights_for_yearly_averages(self): | |||
) | |||
|
|||
# Compare result of the method against the expected. | |||
result = ds.temporal._get_weights() | |||
result = ds.temporal._get_weights(ds.time_bnds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore these changes to _get_weights()
(result of refactoring).
- Organize methods based on stack call - Move `self._labeled_time` instantiation to `departures()` and `_group_average()` to avoid regenerating when calculating weights and fix issue where it was not being regenerated with seasonal climo and custom seasonal climo calls in succession
47ae66e
to
298c7bc
Compare
Codecov ReportBase: 100.00% // Head: 100.00% // No change to project coverage 👍
Additional details and impacted files@@ Coverage Diff @@
## main #350 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 14 14
Lines 1202 1210 +8
=========================================
+ Hits 1202 1210 +8
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR can be merged. @tomvothecoder thank you for the update!
Description
Summary of Changes
Checklist
If applicable: