Skip to content

Commit f756f47

Browse files
Merge pull request #971 from CLIMADA-project/feature/rp_impact
Add local_return_period method to Impact objects and new tutorial
2 parents b8cc3c4 + f4111b0 commit f756f47

File tree

5 files changed

+943
-3
lines changed

5 files changed

+943
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Code freeze date: YYYY-MM-DD
1212

1313
### Added
1414

15+
- `climada.engine.impact.Impact.local_return_period` method [#971](https://github.com/CLIMADA-project/climada_python/pull/971)
16+
- `doc.tutorial.climada_util_local_exceedance_values.ipynb` tutorial explaining `Hazard.local_exceedance_intensity`, `Hazard.local_return_period`, `Impact.local_exceedance_impact`, and `Impact.local_return_period` methods [#971](https://github.com/CLIMADA-project/climada_python/pull/971)
1517
- `Hazard.local_exceedance_intensity`, `Hazard.local_return_period` and `Impact.local_exceedance_impact`, that all use the `climada.util.interpolation` module [#918](https://github.com/CLIMADA-project/climada_python/pull/918)
1618
- `climada.util.interpolation` module for inter- and extrapolation util functions used in local exceedance intensity and return period functions [#930](https://github.com/CLIMADA-project/climada_python/pull/930)
1719
- `climada.exposures.exposures.Exposures.geometry` property

climada/engine/impact.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
from climada.entity import Exposures
5555
from climada.util.constants import CMAP_IMPACT, DEF_CRS, DEF_FREQ_UNIT
5656
from climada.util.select import get_attributes_with_matching_dimension
57+
from climada.util.value_representation import safe_divide
5758

5859
LOGGER = logging.getLogger(__name__)
5960

@@ -499,7 +500,7 @@ def local_exceedance_impact(
499500
):
500501
"""Compute local exceedance impact for given return periods. The default method
501502
is fitting the ordered impacts per centroid to the corresponding cummulated
502-
frequency with by linear interpolation on log-log scale.
503+
frequency with linear interpolation on log-log scale.
503504
504505
Parameters
505506
----------
@@ -610,6 +611,113 @@ def local_exceedance_imp(self, return_periods=(25, 50, 100, 250)):
610611
.T.astype(float)
611612
)
612613

614+
def local_return_period(
615+
self,
616+
threshold_impact=(1000.0, 10000.0),
617+
method="interpolate",
618+
min_impact=0,
619+
log_frequency=True,
620+
log_impact=True,
621+
):
622+
"""Compute local return periods for given threshold impacts. The default method
623+
is fitting the ordered impacts per centroid to the corresponding cummulated
624+
frequency with linear interpolation on log-log scale.
625+
626+
Parameters
627+
----------
628+
threshold_impact : array_like
629+
User-specified impact values for which the return period should be calculated
630+
locally (at each centroid). Defaults to (1000, 10000)
631+
method : str
632+
Method to interpolate to new threshold impacts. Currently available are
633+
"interpolate", "extrapolate", "extrapolate_constant" and "stepfunction". If set to
634+
"interpolate", threshold impacts outside the range of the Impact object's local
635+
impacts will be assigned NaN. If set to "extrapolate_constant" or
636+
"stepfunction", threshold impacts larger than the Impacts object's local
637+
impacts will be assigned NaN, and threshold impacts smaller than the Impact
638+
object's local impacts will be assigned the smallest observed local return period.
639+
If set to "extrapolate", local return periods will be extrapolated (and interpolated).
640+
Defaults to "interpolate".
641+
min_impacts : float, optional
642+
Minimum threshold to filter the impact. Defaults to 0.
643+
log_frequency : bool, optional
644+
This parameter is only used if method is set to "interpolate". If set to True,
645+
(cummulative) frequency values are converted to log scale before inter- and
646+
extrapolation. Defaults to True.
647+
log_impact : bool, optional
648+
This parameter is only used if method is set to "interpolate". If set to True,
649+
impact values are converted to log scale before inter- and extrapolation.
650+
Defaults to True.
651+
652+
Returns
653+
-------
654+
gdf : gpd.GeoDataFrame
655+
GeoDataFrame containing return periods for given threshold impacts. Each column
656+
corresponds to a threshold_impact value, each row corresponds to a centroid. Values
657+
in the gdf correspond to the return period for the given centroid and
658+
threshold_impact value
659+
label : str
660+
GeoDataFrame label, for reporting and plotting
661+
column_label : function
662+
Column-label-generating function, for reporting and plotting
663+
"""
664+
665+
LOGGER.info("Computing return period map for impacts: %s", threshold_impact)
666+
if self.imp_mat.size == 0:
667+
raise ValueError(
668+
"Attribute imp_mat is empty. Recalculate Impact"
669+
"instance with parameter save_mat=True"
670+
)
671+
672+
# check frequency unit
673+
return_period_unit = u_dt.convert_frequency_unit_to_time_unit(
674+
self.frequency_unit
675+
)
676+
677+
# check method
678+
if method not in [
679+
"interpolate",
680+
"extrapolate",
681+
"extrapolate_constant",
682+
"stepfunction",
683+
]:
684+
raise ValueError(f"Unknown method: {method}")
685+
686+
# calculate local return periods
687+
return_periods = np.array(
688+
[
689+
u_interp.preprocess_and_interpolate_ev(
690+
None,
691+
np.array(threshold_impact),
692+
self.frequency,
693+
self.imp_mat.getcol(i_centroid).toarray().flatten(),
694+
log_frequency=log_frequency,
695+
log_values=log_impact,
696+
value_threshold=min_impact,
697+
method=method,
698+
y_asymptotic=np.nan,
699+
)
700+
for i_centroid in range(self.imp_mat.shape[1])
701+
]
702+
)
703+
return_periods = safe_divide(1.0, return_periods)
704+
705+
# create the output GeoDataFrame
706+
gdf = gpd.GeoDataFrame(
707+
geometry=gpd.points_from_xy(self.coord_exp[:, 1], self.coord_exp[:, 0]),
708+
crs=self.crs,
709+
)
710+
col_names = [f"{thresh_impact}" for thresh_impact in threshold_impact]
711+
gdf[col_names] = return_periods
712+
713+
# create label and column_label
714+
label = f"Return Periods ({return_period_unit})"
715+
column_label = lambda column_names: [
716+
f"Impact: {col} {self.unit}" for col in column_names
717+
]
718+
719+
return gdf, label, column_label
720+
613721
def calc_freq_curve(self, return_per=None):
614722
"""Compute impact exceedance frequency curve.
615723

climada/engine/test/test_impact.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,120 @@ def test_local_exceedance_impact_methods(self):
642642
rtol=0.8,
643643
)
644644

645+
def test_local_return_period(self):
646+
"""Test local return periods with lin lin interpolation"""
647+
648+
impact = dummy_impact()
649+
impact.coord_exp = np.array([np.arange(4), np.arange(4)]).T
650+
impact.imp_mat = sparse.csr_matrix(
651+
np.array([[0, 0, 1, 2], [0, 0, 4, 4], [0, 0, 1, 1], [0, 1, 1, 3]])
652+
)
653+
impact.frequency = np.ones(4)
654+
# first centroid has impacts None with cum frequencies None
655+
# second centroid has impacts 1 with cum frequencies 1
656+
# third centroid has impacts 1, 4 with cum frequencies 4, 1
657+
# fourth centroid has impacts 1,2,3,4 with cum frequencies 4,3,2,1
658+
# testing at threshold impacts (0.5, 2.5, 5)
659+
return_stats, _, _ = impact.local_return_period(
660+
threshold_impact=(0.5, 2.5, 5),
661+
method="extrapolate",
662+
log_frequency=False,
663+
log_impact=False,
664+
)
665+
666+
np.testing.assert_allclose(
667+
return_stats[return_stats.columns[1:]].values,
668+
np.array(
669+
[
670+
[np.nan, np.nan, np.nan],
671+
[1.0, np.nan, np.nan],
672+
[1 / 4.5, 1 / 2.5, np.nan],
673+
[1 / 4.5, 1 / 2.5, np.nan],
674+
]
675+
),
676+
)
677+
678+
def test_local_return_period_methods(self):
679+
"""Test local return periods different methods"""
680+
impact = dummy_impact()
681+
impact.coord_exp = np.array([np.arange(4), np.arange(4)]).T
682+
impact.imp_mat = sparse.csr_matrix(
683+
np.array([[0, 0, 0, 1e1], [0, 0, 1e1, 1e2], [0, 1e3, 1e3, 1e3]])
684+
)
685+
impact.frequency = np.array([1.0, 0.1, 0.01])
686+
# first centroid has impacts None with cum frequencies None
687+
# second centroid has impacts 1e3 with frequencies .01, cum freq .01
688+
# third centroid has impacts 1e1, 1e3 with cum frequencies .1, .01, cum freq .11, .01
689+
# fourth centroid has impacts 1e1, 1e2, 1e3 with cum frequencies 1., .1, .01, cum freq 1.11, .11, .01
690+
# testing at threshold impacts .1, 300, 1e5
691+
692+
# test stepfunction
693+
return_stats, _, _ = impact.local_return_period(
694+
threshold_impact=(0.1, 300, 1e5), method="stepfunction"
695+
)
696+
np.testing.assert_allclose(
697+
return_stats.values[:, 1:].astype(float),
698+
np.array(
699+
[
700+
[np.nan, np.nan, np.nan],
701+
[100, 100, np.nan],
702+
[1 / 0.11, 100, np.nan],
703+
[1 / 1.11, 100, np.nan],
704+
]
705+
),
706+
)
707+
708+
# test log log extrapolation
709+
return_stats, _, _ = impact.local_return_period(
710+
threshold_impact=(0.1, 300, 1e5), method="extrapolate"
711+
)
712+
np.testing.assert_allclose(
713+
return_stats.values[:, 1:].astype(float),
714+
np.array(
715+
[
716+
[np.nan, np.nan, np.nan],
717+
[100, 100, np.nan],
718+
[1.0, 30, 1e3],
719+
[0.01, 30, 1e4],
720+
]
721+
),
722+
rtol=0.8,
723+
)
724+
725+
# test log log interpolation and extrapolation with constant
726+
return_stats, _, _ = impact.local_return_period(
727+
threshold_impact=(0.1, 300, 1e5), method="extrapolate_constant"
728+
)
729+
np.testing.assert_allclose(
730+
return_stats.values[:, 1:].astype(float),
731+
np.array(
732+
[
733+
[np.nan, np.nan, np.nan],
734+
[100, 100, np.nan],
735+
[1 / 0.11, 30, np.nan],
736+
[1 / 1.11, 30, np.nan],
737+
]
738+
),
739+
rtol=0.8,
740+
)
741+
742+
# test log log interpolation and no extrapolation
743+
return_stats, _, _ = impact.local_return_period(
744+
threshold_impact=(0.1, 300, 1e5)
745+
)
746+
np.testing.assert_allclose(
747+
return_stats.values[:, 1:].astype(float),
748+
np.array(
749+
[
750+
[np.nan, np.nan, np.nan],
751+
[np.nan, np.nan, np.nan],
752+
[np.nan, 30, np.nan],
753+
[np.nan, 30, np.nan],
754+
]
755+
),
756+
rtol=0.8,
757+
)
758+
645759

646760
class TestImpactReg(unittest.TestCase):
647761
"""Test impact aggregation per aggregation region or admin 0"""

climada/hazard/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ def local_exceedance_intensity(
494494
):
495495
"""Compute local exceedance intensity for given return periods. The default method
496496
is fitting the ordered intensitites per centroid to the corresponding cummulated
497-
frequency with by linear interpolation on log-log scale.
497+
frequency with linear interpolation on log-log scale.
498498
499499
Parameters
500500
----------
@@ -615,7 +615,7 @@ def local_return_period(
615615
):
616616
"""Compute local return periods for given hazard intensities. The default method
617617
is fitting the ordered intensitites per centroid to the corresponding cummulated
618-
frequency with by linear interpolation on log-log scale.
618+
frequency with linear interpolation on log-log scale.
619619
620620
Parameters
621621
----------

doc/tutorial/climada_util_local_exceedance_values.ipynb

Lines changed: 716 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)