def test_convert_calendar(source, target, target_as_str, freq): src = xr.DataArray( date_range("2004-01-01", "2004-12-31", freq=freq, calendar=source), dims=("time",), name="time", ) da_src = xr.DataArray( np.linspace(0, 1, src.size), dims=("time",), coords={"time": src} ) tgt = xr.DataArray( date_range("2004-01-01", "2004-12-31", freq=freq, calendar=target), dims=("time",), name="time", ) conv = convert_calendar(da_src, target if target_as_str else tgt) assert get_calendar(conv) == target if target_as_str and max_doy[source] < max_doy[target]: assert conv.size == src.size elif not target_as_str: assert conv.size == tgt.size assert conv.isnull().sum() == max(max_doy[target] - max_doy[source], 0)
def test_calendars(self): # generate test DataArray time_std = date_range("1991-07-01", "1993-06-30", freq="D", calendar="standard") time_365 = date_range("1991-07-01", "1993-06-30", freq="D", calendar="noleap") data_std = xr.DataArray( np.ones((time_std.size, 4)), dims=("time", "lon"), coords={"time": time_std, "lon": [-72, -71, -70, -69]}, ) # generate test start and end dates start_v = [[200, 200, np.nan, np.nan], [200, 200, 60, 60]] end_v = [[200, np.nan, 60, np.nan], [360, 60, 360, 80]] start_std = xr.DataArray( start_v, dims=("time", "lon"), coords={"time": [time_std[0], time_std[366]], "lon": data_std.lon}, attrs={"calendar": "standard", "is_dayofyear": 1}, ) end_std = xr.DataArray( end_v, dims=("time", "lon"), coords={"time": [time_std[0], time_std[366]], "lon": data_std.lon}, attrs={"calendar": "standard", "is_dayofyear": 1}, ) end_noleap = xr.DataArray( end_v, dims=("time", "lon"), coords={"time": [time_365[0], time_365[365]], "lon": data_std.lon}, attrs={"calendar": "noleap", "is_dayofyear": 1}, ) out = generic.aggregate_between_dates( data_std, start_std, end_std, op="sum", freq="AS-JUL" ) # expected output s = doy_to_days_since(start_std) e = doy_to_days_since(end_std) expected = e - s expected = xr.where(((s > e) | (s.isnull()) | (e.isnull())), np.nan, expected) np.testing.assert_allclose(out, expected) # check calendar convertion out_noleap = generic.aggregate_between_dates( data_std, start_std, end_noleap, op="sum", freq="AS-JUL" ) np.testing.assert_allclose(out, out_noleap)
def test_multiple_lats(self): time_data = date_range( "1992-12-01", "1994-01-01", freq="D", calendar="standard" ) data = xr.DataArray( np.ones((time_data.size, 7)), dims=("time", "lat"), coords={"time": time_data, "lat": [-60, -45, -30, 0, 30, 45, 60]}, ) dl = generic.day_lengths(dates=data.time, lat=data.lat) events = dict( solstice=[ ["1992-12-21", [[18.49, 15.43, 13.93, 12.0, 10.07, 8.57, 5.51]]], ["1993-06-21", [[5.51, 8.57, 10.07, 12.0, 13.93, 15.43, 18.49]]], ["1993-12-21", [[18.49, 15.43, 13.93, 12.0, 10.07, 8.57, 5.51]]], ], equinox=[ ["1993-03-20", [[12] * 7]] ], # True equinox on 1993-03-20 at 14:41 GMT. Some relative tolerance is needed. ) for event, evaluations in events.items(): for e in evaluations: if event == "solstice": np.testing.assert_array_almost_equal( dl.sel(time=e[0]).transpose(), np.array(e[1]), 2 ) elif event == "equinox": np.testing.assert_allclose( dl.sel(time=e[0]).transpose(), np.array(e[1]), rtol=2e-1 )
def test_day_of_year_strings(self): # generate test DataArray time_data = date_range( "1990-08-01", "1995-06-01", freq="D", calendar="standard" ) data = xr.DataArray( np.ones(time_data.size), dims="time", coords={"time": time_data}, ) # set start and end dates start = "02-01" end = "10-31" out = generic.aggregate_between_dates(data, start, end, op="sum", freq="YS") np.testing.assert_allclose(out, np.array([np.nan, 272, 273, 272, 272, np.nan])) # given no freq and only strings for start and end dates with pytest.raises(ValueError): generic.aggregate_between_dates(data, start, end, op="sum") # given a malformed date string bad_start = "02-31" with pytest.raises(ValueError): generic.aggregate_between_dates(data, bad_start, end, op="sum", freq="YS")
def test_convert_calendar_360_days(source, target, freq, align_on): src = xr.DataArray( date_range("2004-01-01", "2004-12-30", freq=freq, calendar=source), dims=("time",), name="time", ) da_src = xr.DataArray( np.linspace(0, 1, src.size), dims=("time",), coords={"time": src} ) conv = convert_calendar(da_src, target, align_on=align_on) assert get_calendar(conv) == target if align_on == "date": np.testing.assert_array_equal( conv.time.resample(time="M").last().dt.day, [30, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], ) elif target == "360_day": np.testing.assert_array_equal( conv.time.resample(time="M").last().dt.day, [30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 29], ) else: np.testing.assert_array_equal( conv.time.resample(time="M").last().dt.day, [30, 29, 30, 30, 31, 30, 30, 31, 30, 31, 29, 31], ) if source == "360_day" and align_on == "year": assert conv.size == 360 if freq == "D" else 360 * 4 else: assert conv.size == 359 if freq == "D" else 359 * 4
def test_time_length(self): # generate test DataArray time_data = date_range( "1991-01-01", "1993-12-31", freq="D", calendar="standard" ) time_start = date_range( "1990-01-01", "1992-12-31", freq="D", calendar="standard" ) time_end = date_range("1991-01-01", "1993-12-31", freq="D", calendar="standard") data = xr.DataArray( np.ones((time_data.size, 4)), dims=("time", "lon"), coords={"time": time_data, "lon": [-72, -71, -70, -69]}, ) # generate test start and end dates start_v = [[200, 200, np.nan, np.nan], [200, 200, 60, 60], [150, 100, 40, 10]] end_v = [[200, np.nan, 60, np.nan], [360, 60, 360, 80], [200, 200, 60, 50]] start = xr.DataArray( start_v, dims=("time", "lon"), coords={ "time": [time_start[0], time_start[365], time_start[730]], "lon": data.lon, }, attrs={"calendar": "standard", "is_dayofyear": 1}, ) end = xr.DataArray( end_v, dims=("time", "lon"), coords={ "time": [time_end[0], time_end[365], time_end[731]], "lon": data.lon, }, attrs={"calendar": "standard", "is_dayofyear": 1}, ) out = generic.aggregate_between_dates(data, start, end, op="sum", freq="YS") # expected output s = doy_to_days_since(start) e = doy_to_days_since(end) expected = e - s expected[1, 1] = np.nan np.testing.assert_allclose(out[0:2], expected) np.testing.assert_allclose(out[2], np.array([np.nan, np.nan, np.nan, np.nan]))
def test_datetime_to_decimal_year(source_cal, exp180): times = xr.DataArray( date_range( "2004-01-01", "2004-12-30", freq="D", calendar=source_cal or "default" ), dims=("time",), name="time", ) decy = datetime_to_decimal_year(times, calendar=source_cal) np.testing.assert_almost_equal(decy[180] - 2004, exp180)
def test_interp_calendar(source, target): src = xr.DataArray( date_range("2004-01-01", "2004-07-30", freq="D", calendar=source), dims=("time",), name="time", ) tgt = xr.DataArray( date_range("2004-01-01", "2004-07-30", freq="D", calendar=target), dims=("time",), name="time", ) da_src = xr.DataArray( np.linspace(0, 1, src.size), dims=("time",), coords={"time": src} ) conv = interp_calendar(da_src, tgt) assert conv.size == tgt.size assert get_calendar(conv) == target np.testing.assert_almost_equal(conv.max(), 1, 2) assert conv.min() == 0
def test_convert_calendar_360_days_random(): da_std = xr.DataArray( np.linspace(0, 1, 366 * 2), dims=("time",), coords={ "time": date_range( "2004-01-01", "2004-12-31T23:59:59", freq="12H", calendar="default" ) }, ) da_360 = xr.DataArray( np.linspace(0, 1, 360 * 2), dims=("time",), coords={ "time": date_range( "2004-01-01", "2004-12-30T23:59:59", freq="12H", calendar="360_day" ) }, ) conv = convert_calendar(da_std, "360_day", align_on="random") assert get_calendar(conv) == "360_day" assert conv.size == 720 conv2 = convert_calendar(da_std, "360_day", align_on="random") assert (conv != conv2).any() conv = convert_calendar(da_360, "default", align_on="random") assert get_calendar(conv) == "default" assert conv.size == 720 assert np.datetime64("2004-02-29") not in conv.time conv2 = convert_calendar(da_360, "default", align_on="random") assert (conv2 != conv).any() conv = convert_calendar(da_360, "noleap", align_on="random", missing=np.NaN) conv = conv.where(conv.isnull(), drop=True) nandoys = conv.time.dt.dayofyear[::2] assert all(nandoys < np.array([74, 147, 220, 293, 366])) assert all(nandoys > np.array([0, 73, 146, 219, 292]))
def test_convert_calendar_missing(source, target, freq): src = xr.DataArray( date_range( "2004-01-01", "2004-12-31" if source != "360_day" else "2004-12-30", freq=freq, calendar=source, ), dims=("time",), name="time", ) da_src = xr.DataArray( np.linspace(0, 1, src.size), dims=("time",), coords={"time": src} ) out = convert_calendar(da_src, target, missing=np.nan, align_on="date") assert xr.infer_freq(out.time) == freq if source == "360_day": assert out.time[-1].dt.day == 31
def prepare(self, da, freq, src_timestep, **indexer): """Prepare arrays to be fed to the `is_missing` function. Parameters ---------- da : xr.DataArray Input data. freq : str Resampling frequency defining the periods defined in http://pandas.pydata.org/pandas-docs/stable/timeseries.html#resampling. src_timestep : {"D", "H"} Expected input frequency. **indexer : {dim: indexer, }, optional Time attribute and values over which to subset the array. For example, use season='DJF' to select winter values, month=1 to select January, or month=[6,7,8] to select summer months. If not indexer is given, all values are considered. Returns ------- xr.DataArray, xr.DataArray Boolean array indicating which values are null, array of expected number of valid values. Notes ----- If `freq=None` and an indexer is given, then missing values during period at the start or end of array won't be flagged. """ # This function can probably be made simpler once CFPeriodIndex is implemented. null = self.is_null(da, freq, **indexer) pfreq, anchor = self.split_freq(freq) c = null.sum(dim="time") # Otherwise simply use the start and end dates to find the expected number of days. if pfreq.endswith("S"): start_time = c.indexes["time"] end_time = start_time.shift(1, freq=freq) elif pfreq: end_time = c.indexes["time"] start_time = end_time.shift(-1, freq=freq) else: i = da.time.to_index() start_time = i[:1] end_time = i[-1:] if indexer: # Create a full synthetic time series and compare the number of days with the original series. t = date_range( start_time[0], end_time[-1], freq=src_timestep, calendar=get_calendar(da), ) sda = xr.DataArray(data=np.ones(len(t)), coords={"time": t}, dims=("time", )) st = generic.select_time(sda, **indexer) if freq: count = st.notnull().resample(time=freq).sum(dim="time") else: count = st.notnull().sum(dim="time") else: delta = end_time - start_time n = delta.astype(_np_timedelta64[src_timestep]) if freq: count = xr.DataArray(n.values, coords={"time": c.time}, dims="time") else: count = xr.DataArray(n.values[0] + 1) return null, count
def series(start, end, calendar): time = date_range(start, end, calendar=calendar) return xr.DataArray([1] * time.size, dims=("time",), coords={"time": time})
def test_frequency(self): # generate test DataArray time_data = date_range( "1991-01-01", "1992-05-31", freq="D", calendar="standard" ) data = xr.DataArray( np.ones((time_data.size, 2)), dims=("time", "lon"), coords={"time": time_data, "lon": [-70, -69]}, ) # generate test start and end dates start_v = [[70, 100], [200, 200], [270, 300], [35, 35], [80, 80]] end_v = [[130, 70], [200, np.nan], [330, 270], [35, np.nan], [150, 150]] end_m_v = [[20, 20], [40, 40], [80, 80], [100, 100], [130, 130]] start = xr.DataArray( start_v, dims=("time", "lon"), coords={ "time": [ time_data[59], time_data[151], time_data[243], time_data[334], time_data[425], ], "lon": data.lon, }, attrs={"calendar": "standard", "is_dayofyear": 1}, ) end = xr.DataArray( end_v, dims=("time", "lon"), coords={ "time": [ time_data[59], time_data[151], time_data[243], time_data[334], time_data[425], ], "lon": data.lon, }, attrs={"calendar": "standard", "is_dayofyear": 1}, ) end_m = xr.DataArray( end_m_v, dims=("time", "lon"), coords={ "time": [ time_data[0], time_data[31], time_data[59], time_data[90], time_data[120], ], "lon": data.lon, }, attrs={"calendar": "standard", "is_dayofyear": 1}, ) out = generic.aggregate_between_dates(data, start, end, op="sum", freq="QS-DEC") # expected output s = doy_to_days_since(start) e = doy_to_days_since(end) expected = e - s expected = xr.where(expected < 0, np.nan, expected) np.testing.assert_allclose(out[0], np.array([np.nan, np.nan])) np.testing.assert_allclose(out[1:6], expected) with pytest.raises(ValueError): generic.aggregate_between_dates(data, start, end_m)
def test_doy_to_days_since(): # simple test time = date_range("2020-07-01", "2022-07-01", freq="AS-JUL") da = xr.DataArray( [190, 360, 3], dims=("time",), coords={"time": time}, attrs={"is_dayofyear": 1, "calendar": "default"}, ) out = doy_to_days_since(da) np.testing.assert_array_equal(out, [7, 178, 186]) assert out.attrs["units"] == "days after 07-01" assert "is_dayofyear" not in out.attrs da2 = days_since_to_doy(out) xr.testing.assert_identical(da, da2) out = doy_to_days_since(da, start="07-01") np.testing.assert_array_equal(out, [7, 178, 186]) # other calendar out = doy_to_days_since(da, calendar="noleap") assert out.attrs["calendar"] == "noleap" np.testing.assert_array_equal(out, [8, 178, 186]) da2 = days_since_to_doy(out) # calendar read from attribute da2.attrs.pop("calendar") # drop for identicality da.attrs.pop("calendar") # drop for identicality xr.testing.assert_identical(da, da2) # with start time = date_range("2020-12-31", "2022-12-31", freq="Y") da = xr.DataArray( [190, 360, 3], dims=("time",), coords={"time": time}, name="da", attrs={"is_dayofyear": 1, "calendar": "default"}, ) out = doy_to_days_since(da, start="01-02") np.testing.assert_array_equal(out, [188, 358, 1]) da2 = days_since_to_doy(out) # start read from attribute assert da2.name == da.name xr.testing.assert_identical(da, da2) # finer freq time = date_range("2020-01-01", "2020-03-01", freq="MS") da = xr.DataArray( [15, 33, 66], dims=("time",), coords={"time": time}, name="da", attrs={"is_dayofyear": 1, "calendar": "default"}, ) out = doy_to_days_since(da) assert out.attrs["units"] == "days after time coordinate" np.testing.assert_array_equal(out, [14, 1, 5]) da2 = days_since_to_doy(out) # start read from attribute xr.testing.assert_identical(da, da2)
) conv = interp_calendar(da_src, tgt) assert conv.size == tgt.size assert get_calendar(conv) == target np.testing.assert_almost_equal(conv.max(), 1, 2) assert conv.min() == 0 @pytest.mark.parametrize( "inp,calout", [ ( xr.DataArray( date_range("2004-01-01", "2004-01-10", freq="D"), dims=("time",), name="time", ), "standard", ), (date_range("2004-01-01", "2004-01-10", freq="D"), "standard"), ( xr.DataArray(date_range("2004-01-01", "2004-01-10", freq="D")).values, "standard", ), (date_range("2004-01-01", "2004-01-10", freq="D").values, "standard"), (date_range("2004-01-01", "2004-01-10", freq="D", calendar="julian"), "julian"), ], ) def test_ensure_cftime_array(inp, calout):
def potential_evapotranspiration( tasmin: Optional[xr.DataArray] = None, tasmax: Optional[xr.DataArray] = None, tas: Optional[xr.DataArray] = None, method: str = "BR65", peta: Optional[float] = 0.00516409319477, petb: Optional[float] = 0.0874972822289, ) -> xr.DataArray: """Potential evapotranspiration. The potential for water evaporation from soil and transpiration by plants if the water supply is sufficient, according to a given method. Parameters ---------- tasmin : xarray.DataArray Minimum daily temperature. tasmax : xarray.DataArray Maximum daily temperature. tas : xarray.DataArray Mean daily temperature. method : {"baierrobertson65", "BR65", "hargreaves85", "HG85", "thornthwaite48", "TW48", "mcguinnessbordne05", "MB05"} Which method to use, see notes. peta : float Used only with method MB05 as :math:`a` for calculation of PET, see Notes section. Default value resulted from calibration of PET over the UK. petb : float Used only with method MB05 as :math:`b` for calculation of PET, see Notes section. Default value resulted from calibration of PET over the UK. Returns ------- xarray.DataArray Notes ----- Available methods are: - "baierrobertson65" or "BR65", based on [baierrobertson65]_. Requires tasmin and tasmax, daily [D] freq. - "hargreaves85" or "HG85", based on [hargreaves85]_. Requires tasmin and tasmax, daily [D] freq. (optional: tas can be given in addition of tasmin and tasmax). - "mcguinnessbordne05" or "MB05", based on [tanguy2018]_. Requires tas, daily [D] freq, with latitudes 'lat'. - "thornthwaite48" or "TW48", based on [thornthwaite48]_. Requires tasmin and tasmax, monthly [MS] or daily [D] freq. (optional: tas can be given instead of tasmin and tasmax). The McGuinness-Bordne [McGuinness1972]_ equation is: .. math:: PET[mm day^{-1}] = a * \frac{S_0}{\\lambda}T_a + b *\frsc{S_0}{\\lambda} where :math:`a` and :math:`b` are empirical parameters; :math:`S_0` is the extraterrestrial radiation [MJ m-2 day-1]; :math:`\\lambda` is the latent heat of vaporisation [MJ kg-1] and :math:`T_a` is the air temperature [°C]. The equation was originally derived for the USA, with :math:`a=0.0147` and :math:`b=0.07353`. The default parameters used here are calibrated for the UK, using the method described in [Tanguy2018]_. References ---------- .. [baierrobertson65] Baier, W., & Robertson, G. W. (1965). Estimation of latent evaporation from simple weather observations. Canadian journal of plant science, 45(3), 276-284. .. [hargreaves85] Hargreaves, G. H., & Samani, Z. A. (1985). Reference crop evapotranspiration from temperature. Applied engineering in agriculture, 1(2), 96-99. .. [tanguy2018] Tanguy, M., Prudhomme, C., Smith, K., & Hannaford, J. (2018). Historical gridded reconstruction of potential evapotranspiration for the UK. Earth System Science Data, 10(2), 951-968. .. [McGuinness1972] McGuinness, J. L., & Bordne, E. F. (1972). A comparison of lysimeter-derived potential evapotranspiration with computed values (No. 1452). US Department of Agriculture. .. [thornthwaite48] Thornthwaite, C. W. (1948). An approach toward a rational classification of climate. Geographical review, 38(1), 55-94. """ if method in ["baierrobertson65", "BR65"]: tasmin = convert_units_to(tasmin, "degF") tasmax = convert_units_to(tasmax, "degF") latr = (tasmin.lat * np.pi) / 180 gsc = 0.082 # MJ/m2/min # julian day fraction jd_frac = (datetime_to_decimal_year(tasmin.time) % 1) * 2 * np.pi ds = 0.409 * np.sin(jd_frac - 1.39) dr = 1 + 0.033 * np.cos(jd_frac) omega = np.arccos(-np.tan(latr) * np.tan(ds)) re = ((24 * 60 / np.pi) * gsc * dr * (omega * np.sin(latr) * np.sin(ds) + np.cos(latr) * np.cos(ds) * np.sin(omega))) # MJ/m2/day re = re / 4.1864e-2 # cal/cm2/day # Baier et Robertson(1965) formula out = 0.094 * (-87.03 + 0.928 * tasmax + 0.933 * (tasmax - tasmin) + 0.0486 * re) out = out.clip(0) elif method in ["hargreaves85", "HG85"]: tasmin = convert_units_to(tasmin, "degC") tasmax = convert_units_to(tasmax, "degC") if tas is None: tas = (tasmin + tasmax) / 2 else: tas = convert_units_to(tas, "degC") latr = (tasmin.lat * np.pi) / 180 gsc = 0.082 # MJ/m2/min lv = 2.5 # MJ/kg # julian day fraction jd_frac = (datetime_to_decimal_year(tasmin.time) % 1) * 2 * np.pi ds = 0.409 * np.sin(jd_frac - 1.39) dr = 1 + 0.033 * np.cos(jd_frac) omega = np.arccos(-np.tan(latr) * np.tan(ds)) ra = ((24 * 60 / np.pi) * gsc * dr * (omega * np.sin(latr) * np.sin(ds) + np.cos(latr) * np.cos(ds) * np.sin(omega))) # MJ/m2/day # Hargreaves and Samani(1985) formula out = (0.0023 * ra * (tas + 17.8) * (tasmax - tasmin)**0.5) / lv out = out.clip(0) elif method in ["mcguinnessbordne05", "MB05"]: if tas is None: tasmin = convert_units_to(tasmin, "degC") tasmax = convert_units_to(tasmax, "degC") tas = (tasmin + tasmax) / 2 tas.attrs["units"] = "degC" tas = convert_units_to(tas, "degC") tasK = convert_units_to(tas, "K") latr = (tas.lat * np.pi) / 180 jd_frac = (datetime_to_decimal_year(tas.time) % 1) * 2 * np.pi S = 1367.0 # Set solar constant [W/m2] ds = 0.409 * np.sin(jd_frac - 1.39) # solar declination ds [radians] omega = np.arccos(-np.tan(latr) * np.tan(ds)) # sunset hour angle [radians] dr = 1.0 + 0.03344 * np.cos( jd_frac - 0.048869) # Calculate relative distance to sun ext_rad = (S * 86400 / np.pi * dr * (omega * np.sin(ds) * np.sin(latr) + np.sin(omega) * np.cos(ds) * np.cos(latr))) latentH = 4185.5 * (751.78 - 0.5655 * tasK) radDIVlat = ext_rad / latentH # parameters from calibration provided by Dr Maliko Tanguy @ CEH # (calibrated for PET over the UK) a = peta b = petb out = radDIVlat * a * tas + radDIVlat * b elif method in ["thornthwaite48", "TW48"]: if tas is None: tasmin = convert_units_to(tasmin, "degC") tasmax = convert_units_to(tasmax, "degC") tas = (tasmin + tasmax) / 2 else: tas = convert_units_to(tas, "degC") tas = tas.clip(0) tas = tas.resample(time="MS").mean(dim="time") latr = (tas.lat * np.pi) / 180 # rad start = "-".join([ str(tas.time[0].dt.year.values), f"{tas.time[0].dt.month.values:02d}", "01", ]) end = "-".join([ str(tas.time[-1].dt.year.values), f"{tas.time[-1].dt.month.values:02d}", str(tas.time[-1].dt.daysinmonth.values), ]) time_v = xr.DataArray( date_range(start, end, freq="D", calendar="standard"), dims="time", name="time", ) # julian day fraction jd_frac = (datetime_to_decimal_year(time_v) % 1) * 2 * np.pi ds = 0.409 * np.sin(jd_frac - 1.39) omega = np.arccos(-np.tan(latr) * np.tan(ds)) * 180 / np.pi # degrees # monthly-mean daytime length (multiples of 12 hours) dl = 2 * omega / (15 * 12) dl_m = dl.resample(time="MS").mean(dim="time") # annual heat index id_m = (tas / 5)**1.514 id_y = id_m.resample(time="YS").sum(dim="time") tas_idy_a = [] for base_time, indexes in tas.resample(time="YS").groups.items(): tas_y = tas.isel(time=indexes) id_v = id_y.sel(time=base_time) a = 6.75e-7 * id_v**3 - 7.71e-5 * id_v**2 + 0.01791 * id_v + 0.49239 frac = (10 * tas_y / id_v)**a tas_idy_a.append(frac) tas_idy_a = xr.concat(tas_idy_a, dim="time") # Thornthwaite(1948) formula out = 1.6 * dl_m * tas_idy_a # cm/month out = 10 * out # mm/month else: raise NotImplementedError(f"'{method}' method is not implemented.") out.attrs["units"] = "mm" return amount2rate(out, out_units="kg m-2 s-1")