Skip to content

Commit 7803112

Browse files
committed
Merge remote-tracking branch 'origin/develop' into feature/option-appraisal-dataclasses
2 parents 0423a6f + 3133447 commit 7803112

14 files changed

Lines changed: 761 additions & 127 deletions

File tree

.gitignore

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,11 @@ coverage.xml
5151
.hypothesis/
5252

5353
# Temporary data generated by unit tests
54-
climada/engine/test/data/supplychain/*
55-
climada/engine/test/data/test.csv
56-
climada/engine/test/data/test_imp_mat.npz
57-
climada/engine/test/data/test.xlsx
58-
climada/entity/impact_funcs/test/data/test_write.xlsx
59-
climada/hazard/test/data/tc_tracks_nc/*
60-
climada/hazard/test/data/tc_tracks.h5
61-
climada/hazard/test/data/test_centr.h5
62-
climada/hazard/test/data/test_haz.h5
63-
climada/hazard/test/data/test_unsupported.h5
64-
climada/test/data/1988234N13299.nc
65-
climada/test/data/exposure_buildings_default_47_7.h5
66-
climada/test/data/exposure_high_47_8.h5
67-
climada/test/data/test_write_hazard.tif
68-
climada/util/test/data/save_test.pkl
69-
climada/util/test/data/test_write_raster.tif
54+
climada/engine/test/data/
55+
climada/entity/impact_funcs/test/data/
56+
climada/hazard/test/data/
57+
climada/test/data/
58+
climada/util/test/data/
7059

7160
# Translations
7261
*.mo
@@ -128,51 +117,6 @@ venv.bak/
128117
# mac finder files
129118
.DS_Store
130119

131-
# climada system data files
132-
# they get downloaded separately from the repo
133-
data/system/global_coast*
134-
data/system/global_country*
135-
data/system/tmp_elevation.tif
136-
137-
# climada system data files: Nightlight and population data
138-
data/system/BlackMarble*tif
139-
data/system/*stable_lights*.*
140-
data/system/gpw_v4_population_count_rev11_2015_30_sec.tif
141-
142-
# climada system data files: IBTrACS data
143-
data/system/IBTrACS.ALL.v04r00.nc
144-
data/system/data_master_sl_short/
145-
146-
# climada system data files: SPEI data
147-
data/system/spei06.nc
148-
149-
# climada system data files: World Bank data
150-
data/system/OGHIST.xls
151-
data/system/Wealth-Accounts_CSV/
152-
153-
# climada system data files: Credit Suisse Research institute data:
154-
data/system/GDP2Asset_factors_CRI_2016
155-
156-
# climada system data files: SPAM data
157-
data/system/cell5m_allockey_xy.csv
158-
data/system/LicenseV*.pdf
159-
data/system/readme_global_v3r2.txt
160-
data/system/spam*csv
161-
162-
# climada system data files: GPW data
163-
data/system/gpw-v*
164-
data/system/gpw_v*.tif
165-
166-
# climada system data files: NASA distance to coast
167-
data/system/GMT_intermediate_coast*
168-
169-
# climada system data files: folders for hazard and exposure data
170-
data/system/hazard/
171-
data/system/litpop/
172-
data/system/litpop_2014/
173-
174-
# climada data: ISIMIP crop data folder:
175-
data/ISIMIP_crop/
176120

177121
# climada data results folder:
178122
results/
@@ -183,5 +127,3 @@ results/
183127
!.pylintrc
184128
!.readthedocs.yml
185129
!.github
186-
climada_petals/
187-
climada_python/

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Code freeze date: YYYY-MM-DD
1414

1515
- Better type hints and overloads signatures for ImpactFuncSet [#1250](https://github.com/CLIMADA-project/climada_python/pull/1250)
1616
- Adds `MeasureConfig` and related dataclasses for new `Measure` object retrocompatibility and (de)serialization capabilities [#1276](https://github.com/CLIMADA-project/climada_python/pull/1276)
17+
- Add inter- and extrapolation options to `ImpactFreqCurve` with method `interpolate` [#1252](https://github.com/CLIMADA-project/climada_python/pull/1252)
1718

1819
### Changed
1920
- Updated Impact Calculation Tutorial (`doc.climada_engine_Impact.ipynb`) [#1095](https://github.com/CLIMADA-project/climada_python/pull/1095).
@@ -22,8 +23,10 @@ Code freeze date: YYYY-MM-DD
2223
### Fixed
2324

2425
- Fixed asset count in impact logging message [#1195](https://github.com/CLIMADA-project/climada_python/pull/1195).
26+
- `Hazard.from_raster_xarray` now returns a sparse matrix instead of a sparse array [#1261](https://github.com/CLIMADA-project/climada_python/pull/1261).
2527

2628
### Deprecated
29+
- `Impact.calc_freq_curve()` should not be given the parameter `return_per`. Use the parameter `return_periods` in `Impact.calc_freq_curve().interpolate()` instead.
2730

2831
### Removed
2932
- `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: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -576,15 +576,6 @@ def local_exceedance_impact(
576576
self.frequency_unit
577577
)
578578

579-
# check method
580-
if method not in [
581-
"interpolate",
582-
"extrapolate",
583-
"extrapolate_constant",
584-
"stepfunction",
585-
]:
586-
raise ValueError(f"Unknown method: {method}")
587-
588579
# calculate local exceedance impact
589580
test_frequency = 1 / np.array(return_periods)
590581

@@ -732,15 +723,6 @@ def local_return_period(
732723
self.frequency_unit
733724
)
734725

735-
# check method
736-
if method not in [
737-
"interpolate",
738-
"extrapolate",
739-
"extrapolate_constant",
740-
"stepfunction",
741-
]:
742-
raise ValueError(f"Unknown method: {method}")
743-
744726
return_periods = np.full((self.imp_mat.shape[1], len(threshold_impact)), np.nan)
745727

746728
nonzero_centroids = np.where(self.imp_mat.getnnz(axis=0) > 0)[0]
@@ -804,6 +786,12 @@ def calc_freq_curve(self, return_per=None):
804786
ifc_impact = self.at_event[sort_idxs][::-1]
805787

806788
if return_per is not None:
789+
warnings.warn(
790+
"Calculating the frequency curve on user-specified return periods is deprecated. "
791+
"Use ImpactFreqCurve.calc_freq_curve().interpolate() instead.",
792+
DeprecationWarning,
793+
stacklevel=2,
794+
)
807795
interp_imp = np.interp(return_per, ifc_return_per, ifc_impact)
808796
ifc_return_per = return_per
809797
ifc_impact = interp_imp
@@ -2299,15 +2287,108 @@ def plot(self, axis=None, log_frequency=False, **kwargs):
22992287
-------
23002288
matplotlib.axes.Axes
23012289
"""
2290+
# check frequency unit
2291+
return_period_unit = u_dt.convert_frequency_unit_to_time_unit(
2292+
self.frequency_unit
2293+
)
2294+
23022295
if not axis:
23032296
_, axis = plt.subplots(1, 1)
23042297
axis.set_title(self.label)
23052298
axis.set_ylabel("Impact (" + self.unit + ")")
2299+
23062300
if log_frequency:
23072301
axis.set_xlabel(f"Exceedance frequency ({self.frequency_unit})")
23082302
axis.set_xscale("log")
23092303
axis.plot(self.return_per**-1, self.impact, **kwargs)
2304+
23102305
else:
2311-
axis.set_xlabel("Return period (year)")
2306+
axis.set_xlabel(f"Return period ({return_period_unit})")
23122307
axis.plot(self.return_per, self.impact, **kwargs)
2308+
23132309
return axis
2310+
2311+
def interpolate(
2312+
self,
2313+
return_periods,
2314+
*,
2315+
method="interpolate",
2316+
log_frequency=True,
2317+
log_impact=True,
2318+
min_impact=0,
2319+
bin_decimals=None,
2320+
y_asymptotic=0.0,
2321+
):
2322+
"""Interpolate and extrapolate impact frequency curve using different methods.
2323+
2324+
Parameters
2325+
----------
2326+
return_periods : Iterable[float]
2327+
return periods for which to evaluate the impact frequency curve
2328+
2329+
method : str, optional
2330+
Method to interpolate to new return periods. Currently available are "interpolate",
2331+
"extrapolate", "extrapolate_constant" and "stepfunction". If set to "interpolate",
2332+
return periods outside the range of the Impact object's observed return periods
2333+
will be assigned NaN. If set to "extrapolate_constant" or "stepfunction",
2334+
return periods larger than the Impact object's observed return periods will be
2335+
assigned the largest impact, and return periods smaller than the Impact object's
2336+
observed return periods will be assigned 0. If set to "extrapolate",
2337+
exceedance impacts will be extrapolated (and interpolated). The extrapolation to
2338+
large return periods uses the two highest impacts of the centroid and their return
2339+
periods and extends the interpolation between these points to the given return period
2340+
(similar for small return periods). Defauls to "interpolate".
2341+
min_impact : float, optional
2342+
Minimum threshold to filter the impact. Defaults to 0.
2343+
log_frequency : bool, optional
2344+
If set to True, (cummulative) frequency values are converted to log scale before
2345+
inter- and extrapolation. Defaults to True.
2346+
log_impact : bool, optional
2347+
If set to True, impact values are converted to log scale before
2348+
inter- and extrapolation. Defaults to True.
2349+
bin_decimals : int, optional
2350+
Number of decimals to group and bin impact values. Binning results in smoother (and
2351+
coarser) interpolation and more stable extrapolation. For more details and sensible
2352+
values for bin_decimals, see Notes. If None, values are not binned. Defaults to None.
2353+
y_asymptotic : float, optional
2354+
Has no effect if method is "interpolate". Else, if data size < 2 or if method
2355+
is set to "extrapolate_constant" or "stepfunction", it provides return value for
2356+
exceeded impact for return periods smaller than the data range. Defaults to 0.
2357+
2358+
Returns
2359+
-------
2360+
ImpactFreqCurve
2361+
impact frequency curve with inter- and extrapolated values
2362+
2363+
See Also
2364+
--------
2365+
util.interpolation.preprocess_and_interpolate_ev :
2366+
inter- and extrapolation method
2367+
"""
2368+
exceedance_frequency = 1 / np.array(return_periods)
2369+
2370+
# sort return periods of ImpactFreqCurve
2371+
sorted_idxs = np.argsort(self.return_per)
2372+
impacts = np.squeeze(np.array(self.impact)[sorted_idxs])
2373+
rps = np.asarray(self.return_per)[sorted_idxs]
2374+
frequency = np.diff(1 / np.array(rps)[::-1], prepend=0)[::-1]
2375+
impact_interpolated = u_interp.preprocess_and_interpolate_ev(
2376+
exceedance_frequency,
2377+
None,
2378+
frequency,
2379+
impacts,
2380+
log_frequency=log_frequency,
2381+
log_values=log_impact,
2382+
value_threshold=min_impact,
2383+
method=method,
2384+
y_asymptotic=y_asymptotic,
2385+
bin_decimals=bin_decimals,
2386+
)
2387+
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,
2394+
)

climada/engine/test/test_impact.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,61 @@ 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_interpolate_freq_curve(self):
325+
"""Test inter- and extrapolate method of freq curve"""
326+
imp = Impact()
327+
imp.frequency = np.array([0.2, 0.1, 0.1, 0.1])
328+
imp.at_event = np.array([0.0, 100.0, 50.0, 110.0])
329+
imp.unit = "USD"
330+
imp.frequency_unit = "1/year"
331+
332+
ifc = imp.calc_freq_curve()
333+
# the ifc has values
334+
# impacts [0, 50, 100, 110]
335+
# exceedance frequencies [.5, .3, .2, .1]
336+
# return periods [2, 3.3, 5, 10]
337+
338+
# stepfunction assigns zero return periods below data and max(impact) for those above
339+
npt.assert_array_almost_equal(
340+
ifc.interpolate([1, 5, 20], method="stepfunction").impact,
341+
[0.0, 100.0, 110.0],
342+
)
343+
344+
# interpolate assigns nan to return periods outside of data
345+
npt.assert_array_almost_equal(
346+
ifc.interpolate([1, 5, 20], method="interpolate").impact,
347+
[np.nan, 100.0, np.nan],
348+
)
349+
350+
# extrapolate_constant assigns zero return periods below data and max(impact) for those above
351+
npt.assert_array_almost_equal(
352+
ifc.interpolate([1, 5, 20], method="extrapolate_constant").impact,
353+
[0.0, 100.0, 110.0],
354+
)
355+
356+
# by binning the last two digits, 100 and 110 are rounded to 100
357+
npt.assert_array_almost_equal(
358+
ifc.interpolate(
359+
[1, 5, 20], method="extrapolate_constant", bin_decimals=-2
360+
).impact,
361+
[0.0, 100.0, 100.0],
362+
)
363+
364+
# extrapolation is done by neglecting 0 impacts (min_impact=0)
365+
# rp=1: extrapolate impacts [50, 100] and ex_freqs [.3, .2] to ex_freq=1 --> 0
366+
# rp=2.5: extrapolate impacts [50, 100] and ex_freqs [.3, .2] to ex_freq=0.4 --> 0
367+
# rp=4: extrapolate impacts [50, 100] and ex_freqs [.3, .2] to ex_freq=0.25 --> 75
368+
# rp=1: extrapolate impacts [100, 110] and ex_freqs [.2, .1] to ex_freq=0.05 --> 115
369+
npt.assert_array_almost_equal(
370+
ifc.interpolate(
371+
[1.0, 2.5, 4, 20],
372+
method="extrapolate",
373+
log_frequency=False,
374+
log_impact=False,
375+
).impact,
376+
[-300.0, 0.0, 75.0, 115.0],
377+
)
378+
324379

325380
class TestImpactPerYear(unittest.TestCase):
326381
"""Test calc_impact_year_set method"""

climada/entity/impact_funcs/test/test_tc.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
import numpy as np
2525
import pandas as pd
2626

27-
from climada.entity.impact_funcs.trop_cyclone import ImpfSetTropCyclone, ImpfTropCyclone
27+
from climada.entity.impact_funcs.trop_cyclone import (
28+
CountryCode,
29+
ImpfSetTropCyclone,
30+
ImpfTropCyclone,
31+
)
2832

2933

3034
class TestEmanuelFormula(unittest.TestCase):
@@ -168,6 +172,16 @@ def test_get_countries_per_region(self):
168172
self.assertListEqual(out[2], [124, 840])
169173
self.assertListEqual(out[3], ["CAN", "USA"])
170174

175+
def test_get_countries_per_region_all_or_none(self):
176+
ifs = ImpfSetTropCyclone()
177+
out = ifs.get_countries_per_region()
178+
out2 = ifs.get_countries_per_region("all")
179+
self.assertEqual(out, out2)
180+
for reg in CountryCode.REGION_NAME.value:
181+
out_reg = ifs.get_countries_per_region(reg)
182+
for i in range(4):
183+
self.assertEqual(out[i][reg], out_reg[i])
184+
171185
def test_get_imf_id_regions_per_countries(self):
172186
"""Test get_impf_id_regions_per_countries()"""
173187
ifs = ImpfSetTropCyclone()

0 commit comments

Comments
 (0)