Skip to content

Improve efficiency of local exceedance intensitry/impact and local return periods functions #1012

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

Merged
merged 30 commits into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a9e4e1d
update nonzero centroid selection
ValentinGebhart Jan 30, 2025
93016aa
add n_sig_dig to Hazard.local...
ValentinGebhart Feb 12, 2025
6b01ad7
add n_sig_digits
ValentinGebhart Feb 12, 2025
4370351
adapt further functions to nonzero centroids
ValentinGebhart Feb 13, 2025
1b33a5a
adapt plot_rp_intensity functions
ValentinGebhart Feb 13, 2025
9597363
adapt impact.plot_rp_intensity
ValentinGebhart Feb 13, 2025
b39c65e
corrected error in rounding numebrs
ValentinGebhart Feb 14, 2025
0457286
adapt number of significant digits to 3 for rounding
ValentinGebhart Feb 14, 2025
30bd733
adapted docstring for extrapoaltion
ValentinGebhart Feb 14, 2025
5c219b1
Merge branch 'develop' into feature/improve_local_exceedance
ValentinGebhart Feb 14, 2025
2ee82ff
fixed error in testing
ValentinGebhart Feb 14, 2025
672051b
adapted docstrings of plot_rp_.. funcs
ValentinGebhart Feb 21, 2025
827ec99
adapted docstrings for binning explanations and some error messages
ValentinGebhart Feb 27, 2025
b3c1f13
change error handling
ValentinGebhart Feb 28, 2025
e0ba070
adapted docstrings
ValentinGebhart Feb 28, 2025
f858c0a
removed pylint issues
ValentinGebhart Mar 2, 2025
463367e
change to decimals in binning
ValentinGebhart Mar 12, 2025
b61d005
fixed test
ValentinGebhart Mar 12, 2025
e5da2c0
fixed tests in test_impact
ValentinGebhart Mar 12, 2025
f378a09
adapted docstrings
ValentinGebhart Mar 13, 2025
4efbc16
Merge branch 'develop' into feature/improve_local_exceedance
ValentinGebhart Mar 13, 2025
3fa1380
make binning possible for all methods
ValentinGebhart Mar 13, 2025
84bdfcd
Apply suggestions from code review Chahan
ValentinGebhart Mar 13, 2025
58d62ee
add kwargs to plot_rp_intensity and plot_rp_imp
ValentinGebhart Mar 14, 2025
adf2f7c
adapt frequency of hazard in tutorial
ValentinGebhart Mar 14, 2025
ff2b01f
Merge branch 'develop' into feature/improve_local_exceedance
ValentinGebhart Mar 14, 2025
241d2cc
update tutorial with hazard object with correct frequencies
ValentinGebhart Mar 14, 2025
596d3f7
Merge branch 'develop' into feature/improve_local_exceedance
ValentinGebhart Mar 14, 2025
d4c8a16
Update plot.py docstring placement
ValentinGebhart Mar 17, 2025
49fa82a
Update CHANGELOG.md
ValentinGebhart Mar 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ Code freeze date: YYYY-MM-DD
### Added

### Changed

- `Hazard.local_exceedance_intensity`, `Hazard.local_return_period` and `Impact.local_exceedance_impact`, `Impact.local_return_period`, using the `climada.util.interpolation` module: New default (no binning), binning on decimals, and faster implementation [#1012](https://github.com/CLIMADA-project/climada_python/pull/1012)
### Fixed

### Deprecated

### Removed
- `climada.util.interpolation.round_to_sig_digits` [#1012](https://github.com/CLIMADA-project/climada_python/pull/1012)

## 6.0.1

Expand Down
249 changes: 156 additions & 93 deletions climada/engine/impact.py

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions climada/engine/test/test_impact.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ def test_local_exceedance_impact(self):
method="extrapolate",
log_frequency=False,
log_impact=False,
bin_decimals=2,
)
np.testing.assert_allclose(
impact_stats.values[:, 1:].astype(float),
Expand Down Expand Up @@ -594,7 +595,7 @@ def test_local_exceedance_impact_methods(self):

# test log log extrapolation
impact_stats, _, _ = impact.local_exceedance_impact(
return_periods=(1000, 30, 0.1), method="extrapolate"
return_periods=(1000, 30, 0.1), method="extrapolate", bin_decimals=-1
)
np.testing.assert_allclose(
impact_stats.values[:, 1:].astype(float),
Expand Down Expand Up @@ -661,6 +662,7 @@ def test_local_return_period(self):
method="extrapolate",
log_frequency=False,
log_impact=False,
bin_decimals=2,
)

np.testing.assert_allclose(
Expand Down Expand Up @@ -707,7 +709,7 @@ def test_local_return_period_methods(self):

# test log log extrapolation
return_stats, _, _ = impact.local_return_period(
threshold_impact=(0.1, 300, 1e5), method="extrapolate"
threshold_impact=(0.1, 300, 1e5), method="extrapolate", bin_decimals=2
)
np.testing.assert_allclose(
return_stats.values[:, 1:].astype(float),
Expand Down
167 changes: 116 additions & 51 deletions climada/hazard/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@
min_intensity=None,
log_frequency=True,
log_intensity=True,
bin_decimals=None,
):
"""Compute local exceedance intensity for given return periods. The default method
is fitting the ordered intensitites per centroid to the corresponding cummulated
Expand All @@ -506,19 +507,23 @@
periods larger than the Hazard object's observed local return periods will be assigned
the largest local intensity, and return periods smaller than the Hazard object's
observed local return periods will be assigned 0. If set to "extrapolate", local
exceedance intensities will be extrapolated (and interpolated).
Defauls to "interpolate".
exceedance intensities will be extrapolated (and interpolated). The extrapolation to
large return periods uses the two highest intensites of the centroid and their return
periods and extends the interpolation between these points to the given return period
(similar for small return periods). Defauls to "interpolate".
min_intensity : float, optional
Minimum threshold to filter the hazard intensity. If set to None, self.intensity_thres
will be used. Defaults to None.
log_frequency : bool, optional
This parameter is only used if method is set to "interpolate". If set to True,
(cummulative) frequency values are converted to log scale before inter- and
extrapolation. Defaults to True.
If set to True, (cummulative) frequency values are converted to log scale before
inter- and extrapolation. Defaults to True.
log_intensity : bool, optional
This parameter is only used if method is set to "interpolate". If set to True,
intensity values are converted to log scale before inter- and extrapolation.
Defaults to True.
If set to True, intensity values are converted to log scale before
inter- and extrapolation. Defaults to True.
bin_decimals : int, optional
Number of decimals to group and bin intensity values. Binning results in smoother (and
coarser) interpolation and more stable extrapolation. For more details and sensible
values for bin_decimals, see Notes. If None, values are not binned. Defaults to None.

Returns
-------
Expand All @@ -531,6 +536,23 @@
GeoDataFrame label, for reporting and plotting
column_label : function
Column-label-generating function, for reporting and plotting

See Also
--------
util.interpolation.preprocess_and_interpolate_ev :
inter- and extrapolation method

Notes
-------
If an integer bin_decimals is given, the intensity values are binned according to their
bin_decimals decimals, and their corresponding frequencies are summed. This binning leads
to a smoother (and coarser) interpolation, and a more stable extrapolation. For instance,
if bin_decimals=1, the two values 12.01 and 11.97 with corresponding frequencies 0.1 and
0.2 are combined to a value 12.0 with frequency 0.3. The default bin_decimals=None results
in not binning the values.
E.g., if your intensities range from 1 to 100, you could use bin_decimals=1, if your
intensities range from 1e6 to 1e9, you could use bin_decimals=-5, if your intensities
range from 0.0001 to .01, you could use bin_decimals=5.
"""
if not min_intensity and min_intensity != 0:
min_intensity = self.intensity_thres
Expand All @@ -550,23 +572,32 @@

# calculate local exceedance intensity
test_frequency = 1 / np.array(return_periods)
exceedance_intensity = np.array(
[
u_interp.preprocess_and_interpolate_ev(
test_frequency,
None,
self.frequency,
self.intensity.getcol(i_centroid).toarray().flatten(),
log_frequency=log_frequency,
log_values=log_intensity,
value_threshold=min_intensity,
method=method,
y_asymptotic=0.0,
)
for i_centroid in range(self.intensity.shape[1])
]

exceedance_intensity = np.full(
(self.intensity.shape[1], len(test_frequency)),
np.nan if method == "interpolate" else 0.0,
)

nonzero_centroids = np.where(self.intensity.getnnz(axis=0) > 0)[0]
if not len(nonzero_centroids) == 0:
exceedance_intensity[nonzero_centroids, :] = np.array(
[
u_interp.preprocess_and_interpolate_ev(
test_frequency,
None,
self.frequency,
self.intensity.getcol(i_centroid).toarray().flatten(),
log_frequency=log_frequency,
log_values=log_intensity,
value_threshold=min_intensity,
method=method,
y_asymptotic=0.0,
bin_decimals=bin_decimals,
)
for i_centroid in nonzero_centroids
]
)

# create the output GeoDataFrame
gdf = gpd.GeoDataFrame(
geometry=self.centroids.gdf["geometry"], crs=self.centroids.gdf.crs
Expand All @@ -576,9 +607,11 @@

# create label and column_label
label = f"Intensity ({self.units})"
column_label = lambda column_names: [
f"Return Period: {col} {return_period_unit}" for col in column_names
]

def column_label(column_names):
return [

Check warning on line 612 in climada/hazard/base.py

View check run for this annotation

Jenkins - WCR / Code Coverage

Not covered line

Line 612 is not covered by tests
f"Return Period: {col} {return_period_unit}" for col in column_names
]

return gdf, label, column_label

Expand Down Expand Up @@ -609,6 +642,7 @@
min_intensity=None,
log_frequency=True,
log_intensity=True,
bin_decimals=None,
):
"""Compute local return periods for given hazard intensities. The default method
is fitting the ordered intensitites per centroid to the corresponding cummulated
Expand All @@ -628,18 +662,24 @@
intensities will be assigned NaN, and threshold intensities smaller than the Hazard
object's local intensities will be assigned the smallest observed local return period.
If set to "extrapolate", local return periods will be extrapolated (and interpolated).
The extrapolation to large threshold intensities uses the two highest intensites of
the centroid and their return periods and extends the interpolation between these
points to the given threshold intensity (similar for small threshold intensites).
Defaults to "interpolate".
min_intensity : float, optional
Minimum threshold to filter the hazard intensity. If set to None, self.intensity_thres
will be used. Defaults to None.
log_frequency : bool, optional
This parameter is only used if method is set to "interpolate". If set to True,
(cummulative) frequency values are converted to log scale before inter- and
extrapolation. Defaults to True.
If set to True, (cummulative) frequency values are converted to log scale before
inter- and extrapolation. Defaults to True.
log_intensity : bool, optional
This parameter is only used if method is set to "interpolate". If set to True,
intensity values are converted to log scale before inter- and extrapolation.
Defaults to True.
If set to True, intensity values are converted to log scale before
inter- and extrapolation. Defaults to True.
bin_decimals : int, optional
Number of decimals to group and bin intensity values. Binning results in smoother (and
coarser) interpolation and more stable extrapolation. For more details and sensible
values for bin_decimals, see Notes. If None, values are not binned. Defaults to None.


Returns
-------
Expand All @@ -652,6 +692,23 @@
GeoDataFrame label, for reporting and plotting
column_label : function
Column-label-generating function, for reporting and plotting

See Also
--------
util.interpolation.preprocess_and_interpolate_ev :
inter- and extrapolation method

Notes
-------
If an integer bin_decimals is given, the intensity values are binned according to their
bin_decimals decimals, and their corresponding frequencies are summed. This binning leads
to a smoother (and coarser) interpolation, and a more stable extrapolation. For instance,
if bin_decimals=1, the two values 12.01 and 11.97 with corresponding frequencies 0.1 and
0.2 are combined to a value 12.0 with frequency 0.3. The default bin_decimals=None results
in not binning the values.
E.g., if your intensities range from 1 to 100, you could use bin_decimals=1, if your
intensities range from 1e6 to 1e9, you could use bin_decimals=-5, if your intensities
range from 0.0001 to .01, you could use bin_decimals=5.
"""
if not min_intensity and min_intensity != 0:
min_intensity = self.intensity_thres
Expand All @@ -669,23 +726,31 @@
]:
raise ValueError(f"Unknown method: {method}")

# calculate local return periods
return_periods = np.array(
[
u_interp.preprocess_and_interpolate_ev(
None,
np.array(threshold_intensities),
self.frequency,
self.intensity.getcol(i_centroid).toarray().flatten(),
log_frequency=log_frequency,
log_values=log_intensity,
value_threshold=min_intensity,
method=method,
y_asymptotic=np.nan,
)
for i_centroid in range(self.intensity.shape[1])
]
return_periods = np.full(
(self.intensity.shape[1], len(threshold_intensities)), np.nan
)

nonzero_centroids = np.where(self.intensity.getnnz(axis=0) > 0)[0]

if not len(nonzero_centroids) == 0:
return_periods[nonzero_centroids, :] = np.array(
[
u_interp.preprocess_and_interpolate_ev(
None,
np.array(threshold_intensities),
self.frequency,
self.intensity.getcol(i_centroid).toarray().flatten(),
log_frequency=log_frequency,
log_values=log_intensity,
value_threshold=min_intensity,
method=method,
y_asymptotic=np.nan,
bin_decimals=bin_decimals,
)
for i_centroid in nonzero_centroids
]
)

return_periods = safe_divide(1.0, return_periods)

# create the output GeoDataFrame
Expand All @@ -697,9 +762,9 @@

# create label and column_label
label = f"Return Periods ({return_period_unit})"
column_label = lambda column_names: [
f"Threshold Intensity: {col} {self.units}" for col in column_names
]

def column_label(column_names):
return [f"Threshold Intensity: {col} {self.units}" for col in column_names]

Check warning on line 767 in climada/hazard/base.py

View check run for this annotation

Jenkins - WCR / Code Coverage

Not covered line

Line 767 is not covered by tests

return gdf, label, column_label

Expand Down
64 changes: 32 additions & 32 deletions climada/hazard/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import matplotlib.pyplot as plt
import numpy as np
from deprecation import deprecated

Check warning on line 26 in climada/hazard/plot.py

View check run for this annotation

Jenkins - WCR / Pylint

unused-import

NORMAL: Unused deprecated imported from deprecation
Raw output
Used when an imported module or variable is not used.

import climada.util.plot as u_plot

Expand All @@ -37,62 +37,62 @@
Contains all plotting methods of the Hazard class
"""

@deprecated(
details="The use of Hazard.plot_rp_intensity is deprecated."
"Use Hazard.local_exceedance_intensity and util.plot.plot_from_gdf instead."
)
def plot_rp_intensity(
self,
return_periods=(25, 50, 100, 250),
smooth=True,
axis=None,
figsize=(9, 13),
adapt_fontsize=True,
kwargs_local_exceedance_intensity=None,

Check warning on line 44 in climada/hazard/plot.py

View check run for this annotation

Jenkins - WCR / Pylint

invalid-name

LOW: Argument name "kwargs_local_exceedance_intensity" doesn't conform to '(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$' pattern
Raw output
Used when the name doesn't match the regular expression associated to its type(constant, variable, class...).
**kwargs,
):
"""
This function is deprecated,
use Impact.local_exceedance_impact and util.plot.plot_from_gdf instead.

Compute and plot hazard exceedance intensity maps for different
return periods. Calls local_exceedance_inten.
return periods. Calls local_exceedance_intensity. For handling large data sets and for
further options, see Notes.

Parameters
----------
return_periods: tuple(int), optional
return periods to consider
smooth: bool, optional
smooth plot to plot.RESOLUTIONxplot.RESOLUTION
axis: matplotlib.axes._subplots.AxesSubplot, optional
axis to use
figsize: tuple, optional
figure size for plt.subplots
kwargs_local_exceedance_intensity: dict
Dictionary of keyword arguments for the method hazard.local_exceedance_intensity.
kwargs: optional
arguments for pcolormesh matplotlib function used in event plots

Returns
-------
axis, inten_stats: matplotlib.axes._subplots.AxesSubplot, np.ndarray
intenstats is return_periods.size x num_centroids

See Also
---------
hazard.local_exceedance_intensity: method to calculate local exceedance frequencies.

Notes
-----
For handling large data, and for more fleixble options in the exceedance
intensity computation and in the plotting, we recommend to use
gdf, title, labels = hazard.local_exceedance_intensity() and
util.plot.plot_from_gdf(gdf, title, labels) instead.
"""
inten_stats = self.local_exceedance_intensity(return_periods)[0].values[:, 1:].T
inten_stats = inten_stats.astype(float)
colbar_name = "Intensity (" + self.units + ")"
title = list()
for ret in return_periods:
title.append("Return period: " + str(ret) + " years")
axis = u_plot.geo_im_from_array(
inten_stats,
self.centroids.coord,
colbar_name,
title,
smooth=smooth,
axes=axis,
figsize=figsize,
adapt_fontsize=adapt_fontsize,
**kwargs,
LOGGER.info(
"Some errors in the previous calculation of local exceedance intensities have been corrected,"

Check warning on line 80 in climada/hazard/plot.py

View check run for this annotation

Jenkins - WCR / Pylint

line-too-long

LOW: Line too long (106/100)
Raw output
Used when a line is longer than a given number of characters.
" see Hazard.local_exceedance_intensity. To reproduce data with the "
"previous calculation, use CLIMADA v5.0.0 or less."
)

if kwargs_local_exceedance_intensity is None:
kwargs_local_exceedance_intensity = {}

inten_stats, title, column_labels = self.local_exceedance_intensity(
return_periods, **kwargs_local_exceedance_intensity
)

axis = u_plot.plot_from_gdf(
inten_stats, title, column_labels, axis=axis, **kwargs
)
return axis, inten_stats
return axis, inten_stats.values[:, 1:].T.astype(float)

Check warning on line 95 in climada/hazard/plot.py

View check run for this annotation

Jenkins - WCR / Code Coverage

Not covered lines

Lines 79-95 are not covered by tests

def plot_intensity(
self,
Expand Down
Loading