Skip to content

Commit 36d4cc8

Browse files
return an ImpactFreqCurve object when inter- extrapolating
1 parent e66cb6c commit 36d4cc8

3 files changed

Lines changed: 45 additions & 176 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Code freeze date: YYYY-MM-DD
1313
### Added
1414

1515
- Better type hints and overloads signatures for ImpactFuncSet [#1250](https://github.com/CLIMADA-project/climada_python/pull/1250)
16-
- Add inter- and extrapolation options to `ImpactFreqCurve` with methods `extrapolate` and `plot_extrapolate` [#1252](https://github.com/CLIMADA-project/climada_python/pull/1252)
16+
- Add inter- and extrapolation options to `ImpactFreqCurve` with method `interpolate` [#1252](https://github.com/CLIMADA-project/climada_python/pull/1252)
1717

1818
### Changed
1919
- Updated Impact Calculation Tutorial (`doc.climada_engine_Impact.ipynb`) [#1095](https://github.com/CLIMADA-project/climada_python/pull/1095).
@@ -23,7 +23,7 @@ Code freeze date: YYYY-MM-DD
2323
- Fixed asset count in impact logging message [#1195](https://github.com/CLIMADA-project/climada_python/pull/1195).
2424

2525
### Deprecated
26-
- `Impact.calc_freq_curve()` should not be given the parameter `return_per`. Use the parameter `return_periods` in `Impact.calc_freq_curve().extrapolate()` instead.
26+
- `Impact.calc_freq_curve()` should not be given the parameter `return_per`. Use the parameter `return_periods` in `Impact.calc_freq_curve().interpolate()` instead.
2727

2828
### Removed
2929
- `climada.util.earth_engine.py` Google Earth Engine methods did not facilitate direct use of GEE data in CLIMADA. Code was relocated to [climada-snippets](https://github.com/CLIMADA-project/climada-snippets). [#1109](https://github.com/CLIMADA-project/climada_python/pull/1109)

climada/engine/impact.py

Lines changed: 31 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,7 @@ def calc_freq_curve(self, return_per=None):
788788
if return_per is not None:
789789
warnings.warn(
790790
"Calculating the frequency curve on user-specified return periods is deprecated. "
791-
"Use ImpactFreqCurve.extrapolate() instead.",
791+
"Use ImpactFreqCurve.calc_freq_curve().interpolate() instead.",
792792
DeprecationWarning,
793793
stacklevel=2,
794794
)
@@ -2287,19 +2287,28 @@ def plot(self, axis=None, log_frequency=False, **kwargs):
22872287
-------
22882288
matplotlib.axes.Axes
22892289
"""
2290-
2291-
return self._plot(
2292-
self.return_per,
2293-
self.impact,
2294-
axis,
2295-
log_frequency,
2296-
self.frequency_unit,
2297-
self.unit,
2298-
self.label,
2299-
**kwargs,
2290+
# check frequency unit
2291+
return_period_unit = u_dt.convert_frequency_unit_to_time_unit(
2292+
self.frequency_unit
23002293
)
23012294

2302-
def extrapolate(
2295+
if not axis:
2296+
_, axis = plt.subplots(1, 1)
2297+
axis.set_title(self.label)
2298+
axis.set_ylabel("Impact (" + self.unit + ")")
2299+
2300+
if log_frequency:
2301+
axis.set_xlabel(f"Exceedance frequency ({self.frequency_unit})")
2302+
axis.set_xscale("log")
2303+
axis.plot(self.return_per**-1, self.impact, **kwargs)
2304+
2305+
else:
2306+
axis.set_xlabel(f"Return period ({return_period_unit})")
2307+
axis.plot(self.return_per, self.impact, **kwargs)
2308+
2309+
return axis
2310+
2311+
def interpolate(
23032312
self,
23042313
return_periods,
23052314
*,
@@ -2310,7 +2319,7 @@ def extrapolate(
23102319
bin_decimals=None,
23112320
y_asymptotic=0.0,
23122321
):
2313-
"""Extrapolate impact frequency curve with different interpolation and extrapolation options.
2322+
"""Interpolate and extrapolate impact frequency curve using different methods.
23142323
23152324
Parameters
23162325
----------
@@ -2348,8 +2357,8 @@ def extrapolate(
23482357
23492358
Returns
23502359
-------
2351-
np.array
2352-
array of exceeded impacts at given return periods
2360+
ImpactFreqCurve
2361+
impact frequency curve with inter- and extrapolated values
23532362
23542363
See Also
23552364
--------
@@ -2363,7 +2372,7 @@ def extrapolate(
23632372
impacts = np.squeeze(np.array(self.impact)[sorted_idxs])
23642373
rps = np.asarray(self.return_per)[sorted_idxs]
23652374
frequency = np.diff(1 / np.array(rps)[::-1], prepend=0)[::-1]
2366-
return u_interp.preprocess_and_interpolate_ev(
2375+
impact_interpolated = u_interp.preprocess_and_interpolate_ev(
23672376
exceedance_frequency,
23682377
None,
23692378
frequency,
@@ -2376,154 +2385,10 @@ def extrapolate(
23762385
bin_decimals=bin_decimals,
23772386
)
23782387

2379-
def plot_extrapolate(
2380-
self,
2381-
*,
2382-
return_periods=None,
2383-
axis=None,
2384-
log_frequency=False,
2385-
kwargs_interp=None,
2386-
**kwargs,
2387-
):
2388-
"""Plot impact frequency curve with interpolation and extrapolation options.
2389-
2390-
Parameters
2391-
----------
2392-
return_periods : Iterable[float], optional
2393-
Return period values to plot, e.g. np.linspace(5, 500, 1000) to plot between 5 and
2394-
500 years. If None, the plot range defined between 0.5*min(data_return_periods) and
2395-
1.2*max(data_return_periods), where data_return_periods are the return period values
2396-
extracted from the impact object's data. Defaults to None.
2397-
axis : matplotlib.axes.Axes, optional
2398-
axis to use
2399-
log_frequency : boolean, optional
2400-
plot logarithmioc exceedance frequency on x-axis
2401-
kwargs_interp : dict, optional
2402-
dict with (key, value) pairs to handle inter- and extrapolation behaviour, e.g.
2403-
{"method": "extrapolate"}. Default is {"method": "interpolate", "log_frequency": True,
2404-
"log_impact": True, "min_impact": 0, "bin_decimals": None}. Available options are
2405-
method : str, optional
2406-
Method to interpolate to new return periods. Currently available are
2407-
"interpolate", "extrapolate", "extrapolate_constant" and "stepfunction". If set
2408-
to "interpolate", return periods outside the range of the Impact object's observed
2409-
return periods will be assigned NaN. If set to "extrapolate_constant" or
2410-
"stepfunction", return periods larger than the Impact object's observed return
2411-
periods will be assigned the largest impact, and return periods smaller than the
2412-
Impact object's observed return periods will be assigned 0. If set to
2413-
"extrapolate", exceedance impacts will be extrapolated (and interpolated). The
2414-
extrapolation to large return periods uses the two highest impacts of the centroid
2415-
and their return periods and extends the interpolation between these points to the
2416-
given return period (similar for small return periods). Defauls to "interpolate".
2417-
min_impact : float, optional
2418-
Minimum threshold to filter the impact. Defaults to 0.
2419-
log_frequency : bool, optional
2420-
If set to True, (cummulative) frequency values are converted to log scale before
2421-
inter- and extrapolation. Defaults to True.
2422-
log_impact : bool, optional
2423-
If set to True, impact values are converted to log scale before
2424-
inter- and extrapolation. Defaults to True.
2425-
bin_decimals : int, optional
2426-
Number of decimals to group and bin impact values. Binning results in smoother
2427-
(and coarser) interpolation and more stable extrapolation. For more details and
2428-
sensible values for bin_decimals, see Notes. If None, values are not binned.
2429-
Defaults to None.
2430-
y_asymptotic : float, optional
2431-
Has no effect if method is "interpolate". Else, if data size < 2 or if method
2432-
is set to "extrapolate_constant" or "stepfunction", it provides return value for
2433-
exceeded impact for return periods smaller than the data range. Defaults to 0.
2434-
kwargs : dict, optional
2435-
arguments for plot matplotlib function, e.g. color='b'
2436-
2437-
Returns
2438-
-------
2439-
matplotlib.axes.Axes
2440-
2441-
See Also
2442-
--------
2443-
climada.engine.impact.ImpactFreqCurve.extrapolate:
2444-
extrapolation method used in the plotting function
2445-
util.interpolation.preprocess_and_interpolate_ev :
2446-
inter- and extrapolation method
2447-
"""
2448-
if kwargs_interp is None:
2449-
kwargs_interp = {}
2450-
kwargs_interp = {
2451-
"method": "interpolate",
2452-
"log_frequency": True,
2453-
"log_impact": True,
2454-
"min_impact": 0.0,
2455-
"bin_decimals": None,
2456-
"y_asymptotic": 0.0,
2457-
} | kwargs_interp
2458-
2459-
if return_periods is None:
2460-
return_periods = np.linspace(
2461-
0.5 * min(self.return_per), 1.2 * max(self.return_per), 500
2462-
)
2463-
2464-
impacts = self.extrapolate(return_periods, **kwargs_interp)
2465-
2466-
# check frequency unit
2467-
return_period_unit = u_dt.convert_frequency_unit_to_time_unit(
2468-
self.frequency_unit
2388+
return ImpactFreqCurve(
2389+
return_per=return_periods,
2390+
impact=impact_interpolated,
2391+
unit=self.unit,
2392+
frequency_unit=self.frequency_unit,
2393+
label=self.label,
24692394
)
2470-
2471-
if not axis:
2472-
_, axis = plt.subplots(1, 1)
2473-
axis.set_title(self.label)
2474-
axis.set_ylabel("Impact (" + self.unit + ")")
2475-
if log_frequency:
2476-
axis.set_xlabel(f"Exceedance frequency ({self.frequency_unit})")
2477-
axis.set_xscale("log")
2478-
x_vals = return_periods**-1
2479-
else:
2480-
axis.set_xlabel(f"Return period ({return_period_unit})")
2481-
x_vals = return_periods
2482-
2483-
# plot extrapolated data as dashed line
2484-
if kwargs_interp["method"] == "interpolate":
2485-
axis.plot(x_vals, impacts, linestyle="-", **kwargs)
2486-
else:
2487-
kwargs_copy = kwargs.copy()
2488-
color = kwargs_copy.pop("color", None)
2489-
min_rp = min(self.return_per)
2490-
max_rp = max(self.return_per)
2491-
mask = (return_periods >= min_rp) & (return_periods <= max_rp)
2492-
2493-
line_data = axis.plot(
2494-
x_vals[mask], impacts[mask], linestyle="-", color=color, **kwargs_copy
2495-
)[0]
2496-
color = line_data.get_color()
2497-
axis.plot(x_vals, impacts, linestyle="--", color=color, **kwargs_copy)
2498-
2499-
return axis
2500-
2501-
@staticmethod
2502-
def _plot(
2503-
return_per,
2504-
impact,
2505-
axis,
2506-
log_frequency,
2507-
frequency_unit,
2508-
unit,
2509-
title,
2510-
**kwargs,
2511-
):
2512-
"""
2513-
private function to plot an impact's exceedance frequency curve
2514-
"""
2515-
# check frequency unit
2516-
return_period_unit = u_dt.convert_frequency_unit_to_time_unit(frequency_unit)
2517-
2518-
if not axis:
2519-
_, axis = plt.subplots(1, 1)
2520-
axis.set_title(title)
2521-
axis.set_ylabel("Impact (" + unit + ")")
2522-
if log_frequency:
2523-
axis.set_xlabel(f"Exceedance frequency ({frequency_unit})")
2524-
axis.set_xscale("log")
2525-
axis.plot(return_per**-1, impact, **kwargs)
2526-
else:
2527-
axis.set_xlabel(f"Return period ({return_period_unit})")
2528-
axis.plot(return_per, impact, **kwargs)
2529-
return axis

climada/engine/test/test_impact.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ def test_ref_value_rp_pass(self):
321321
self.assertEqual("USD", ifc.unit)
322322
self.assertEqual("1/week", ifc.frequency_unit)
323323

324-
def test_extrapolate_freq_curve(self):
325-
"""Test extrapolate method of freq curve"""
324+
def test_interpolate_freq_curve(self):
325+
"""Test inter- and extrapolate method of freq curve"""
326326
imp = Impact()
327327
imp.frequency = np.array([0.2, 0.1, 0.1, 0.1])
328328
imp.at_event = np.array([0.0, 100.0, 50.0, 110.0])
@@ -337,23 +337,27 @@ def test_extrapolate_freq_curve(self):
337337

338338
# stepfunction assigns zero return periods below data and max(impact) for those above
339339
npt.assert_array_almost_equal(
340-
ifc.extrapolate([1, 5, 20], method="stepfunction"), [0.0, 100.0, 110.0]
340+
ifc.interpolate([1, 5, 20], method="stepfunction").impact,
341+
[0.0, 100.0, 110.0],
341342
)
342343

343344
# interpolate assigns nan to return periods outside of data
344345
npt.assert_array_almost_equal(
345-
ifc.extrapolate([1, 5, 20], method="interpolate"), [np.nan, 100.0, np.nan]
346+
ifc.interpolate([1, 5, 20], method="interpolate").impact,
347+
[np.nan, 100.0, np.nan],
346348
)
347349

348350
# extrapolate_constant assigns zero return periods below data and max(impact) for those above
349351
npt.assert_array_almost_equal(
350-
ifc.extrapolate([1, 5, 20], method="extrapolate_constant"),
352+
ifc.interpolate([1, 5, 20], method="extrapolate_constant").impact,
351353
[0.0, 100.0, 110.0],
352354
)
353355

354356
# by binning the last two digits, 100 and 110 are rounded to 100
355357
npt.assert_array_almost_equal(
356-
ifc.extrapolate([1, 5, 20], method="extrapolate_constant", bin_decimals=-2),
358+
ifc.interpolate(
359+
[1, 5, 20], method="extrapolate_constant", bin_decimals=-2
360+
).impact,
357361
[0.0, 100.0, 100.0],
358362
)
359363

@@ -363,12 +367,12 @@ def test_extrapolate_freq_curve(self):
363367
# rp=4: extrapolate impacts [50, 100] and ex_freqs [.3, .2] to ex_freq=0.25 --> 75
364368
# rp=1: extrapolate impacts [100, 110] and ex_freqs [.2, .1] to ex_freq=0.05 --> 115
365369
npt.assert_array_almost_equal(
366-
ifc.extrapolate(
370+
ifc.interpolate(
367371
[1.0, 2.5, 4, 20],
368372
method="extrapolate",
369373
log_frequency=False,
370374
log_impact=False,
371-
),
375+
).impact,
372376
[-300.0, 0.0, 75.0, 115.0],
373377
)
374378

0 commit comments

Comments
 (0)