3333from typing import Any , Iterable , Union
3434
3535import contextily as ctx
36+ import geopandas as gpd
3637import h5py
3738import matplotlib .animation as animation
3839import matplotlib .pyplot as plt
3940import numpy as np
4041import pandas as pd
4142import xlsxwriter
43+ from deprecation import deprecated
4244from pyproj import CRS as pyprojCRS
4345from rasterio .crs import CRS as rasterioCRS # pylint: disable=no-name-in-module
4446from scipy import sparse
4547from tqdm import tqdm
4648
4749import climada .util .coordinates as u_coord
4850import climada .util .dates_times as u_dt
51+ import climada .util .interpolation as u_interp
4952import climada .util .plot as u_plot
5053from climada import CONFIG
5154from climada .entity import Exposures
@@ -486,20 +489,54 @@ def calc_impact_year_set(self, all_years=True, year_range=None):
486489 )
487490 return self .impact_per_year (all_years = all_years , year_range = year_range )
488491
489- # TODO: rewrite and deprecate method
490- def local_exceedance_imp (self , return_periods = (25 , 50 , 100 , 250 )):
491- """Compute exceedance impact map for given return periods.
492- Requires attribute imp_mat.
492+ def local_exceedance_impact (
493+ self ,
494+ return_periods = (25 , 50 , 100 , 250 ),
495+ method = "interpolate" ,
496+ min_impact = 0 ,
497+ log_frequency = True ,
498+ log_impact = True ,
499+ ):
500+ """Compute local exceedance impact for given return periods. The default method
501+ is fitting the ordered impacts per centroid to the corresponding cummulated
502+ frequency with by linear interpolation on log-log scale.
493503
494504 Parameters
495505 ----------
496- return_periods : Any, optional
497- return periods to consider
498- Dafault is (25, 50, 100, 250)
506+ return_periods : array_like
507+ User-specified return periods for which the exceedance intensity should be calculated
508+ locally (at each centroid). Defaults to (25, 50, 100, 250).
509+ method : str
510+ Method to interpolate to new return periods. Currently available are "interpolate",
511+ "extrapolate", "extrapolate_constant" and "stepfunction". If set to "interpolate",
512+ return periods outside the range of the Impact object's observed local return periods
513+ will be assigned NaN. If set to "extrapolate_constant" or "stepfunction",
514+ return periods larger than the Impact object's observed local return periods will be
515+ assigned the largest local impact, and return periods smaller than the Impact object's
516+ observed local return periods will be assigned 0. If set to "extrapolate", local
517+ exceedance impacts will be extrapolated (and interpolated). Defauls to "interpolate".
518+ min_impact : float, optional
519+ Minimum threshold to filter the impact. Defaults to 0.
520+ log_frequency : bool, optional
521+ This parameter is only used if method is set to "extrapolate" or "interpolate". If set
522+ to True, (cummulative) frequency values are converted to log scale before inter- and
523+ extrapolation. Defaults to True.
524+ log_impact : bool, optional
525+ This parameter is only used if method is set to "extrapolate" or "interpolate". If set
526+ to True, impact values are converted to log scale before inter- and extrapolation.
527+ Defaults to True.
499528
500529 Returns
501530 -------
502- np.array
531+ gdf : gpd.GeoDataFrame
532+ GeoDataFrame containing exeedance impacts for given return periods. Each column
533+ corresponds to a return period, each row corresponds to a centroid. Values
534+ in the gdf correspond to the exceedance impact for the given centroid and
535+ return period
536+ label : str
537+ GeoDataFrame label, for reporting and plotting
538+ column_label : function
539+ Column-label-generating function, for reporting and plotting
503540 """
504541 LOGGER .info (
505542 "Computing exceedance impact map for return periods: %s" , return_periods
@@ -509,29 +546,69 @@ def local_exceedance_imp(self, return_periods=(25, 50, 100, 250)):
509546 "Attribute imp_mat is empty. Recalculate Impact"
510547 "instance with parameter save_mat=True"
511548 )
512- num_cen = self .imp_mat .shape [1 ]
513- imp_stats = np .zeros ((len (return_periods ), num_cen ))
514- cen_step = CONFIG .max_matrix_size .int () // self .imp_mat .shape [0 ]
515- if not cen_step :
516- raise ValueError (
517- "Increase max_matrix_size configuration parameter to > "
518- f"{ self .imp_mat .shape [0 ]} "
519- )
520- # separte in chunks
521- chk = - 1
522- for chk in range (int (num_cen / cen_step )):
523- self ._loc_return_imp (
524- np .array (return_periods ),
525- self .imp_mat [:, chk * cen_step : (chk + 1 ) * cen_step ].toarray (),
526- imp_stats [:, chk * cen_step : (chk + 1 ) * cen_step ],
527- )
528- self ._loc_return_imp (
529- np .array (return_periods ),
530- self .imp_mat [:, (chk + 1 ) * cen_step :].toarray (),
531- imp_stats [:, (chk + 1 ) * cen_step :],
549+
550+ # check frequency unit
551+ return_period_unit = u_dt .convert_frequency_unit_to_time_unit (
552+ self .frequency_unit
532553 )
533554
534- return imp_stats
555+ # check method
556+ if method not in [
557+ "interpolate" ,
558+ "extrapolate" ,
559+ "extrapolate_constant" ,
560+ "stepfunction" ,
561+ ]:
562+ raise ValueError (f"Unknown method: { method } " )
563+
564+ # calculate local exceedance impact
565+ test_frequency = 1 / np .array (return_periods )
566+ exceedance_impact = np .array (
567+ [
568+ u_interp .preprocess_and_interpolate_ev (
569+ test_frequency ,
570+ None ,
571+ self .frequency ,
572+ self .imp_mat .getcol (i_centroid ).toarray ().flatten (),
573+ log_frequency = log_frequency ,
574+ log_values = log_impact ,
575+ value_threshold = min_impact ,
576+ method = method ,
577+ y_asymptotic = 0.0 ,
578+ )
579+ for i_centroid in range (self .imp_mat .shape [1 ])
580+ ]
581+ )
582+
583+ # create the output GeoDataFrame
584+ gdf = gpd .GeoDataFrame (
585+ geometry = gpd .points_from_xy (self .coord_exp [:, 1 ], self .coord_exp [:, 0 ]),
586+ crs = self .crs ,
587+ )
588+ col_names = [f"{ ret_per } " for ret_per in return_periods ]
589+ gdf [col_names ] = exceedance_impact
590+ # create label and column_label
591+ label = f"Impact ({ self .unit } )"
592+ column_label = lambda column_names : [
593+ f"Return Period: { col } { return_period_unit } " for col in column_names
594+ ]
595+
596+ return gdf , label , column_label
597+
598+ @deprecated (
599+ details = "The use of Impact.local_exceedance_imp is deprecated. Use "
600+ "Impact.local_exceedance_impact instead. Some errors in the previous calculation "
601+ "in Impact.local_exceedance_imp have been corrected. To reproduce data with the "
602+ "previous calculation, use CLIMADA v5.0.0 or less."
603+ )
604+ def local_exceedance_imp (self , return_periods = (25 , 50 , 100 , 250 )):
605+ """This function is deprecated, use Impact.local_exceedance_impact instead."""
606+
607+ return (
608+ self .local_exceedance_impact (return_periods )[0 ]
609+ .values [:, 1 :]
610+ .T .astype (float )
611+ )
535612
536613 def calc_freq_curve (self , return_per = None ):
537614 """Compute impact exceedance frequency curve.
@@ -924,6 +1001,10 @@ def plot_basemap_impact_exposure(
9241001
9251002 return axis
9261003
1004+ @deprecated (
1005+ details = "The use of Impact.plot_rp_imp is deprecated."
1006+ "Use Impact.local_exceedance_impact and util.plot.plot_from_gdf instead."
1007+ )
9271008 def plot_rp_imp (
9281009 self ,
9291010 return_periods = (25 , 50 , 100 , 250 ),
@@ -932,7 +1013,11 @@ def plot_rp_imp(
9321013 axis = None ,
9331014 ** kwargs ,
9341015 ):
935- """Compute and plot exceedance impact maps for different return periods.
1016+ """
1017+ This function is deprecated, use Impact.local_exceedance_impact and
1018+ util.plot.plot_from_gdf instead.
1019+
1020+ Compute and plot exceedance impact maps for different return periods.
9361021 Calls local_exceedance_imp.
9371022
9381023 Parameters
@@ -953,7 +1038,10 @@ def plot_rp_imp(
9531038 imp_stats : np.array
9541039 return_periods.size x num_centroids
9551040 """
956- imp_stats = self .local_exceedance_imp (np .array (return_periods ))
1041+ imp_stats = (
1042+ self .local_exceedance_impact (np .array (return_periods ))[0 ].values [:, 1 :].T
1043+ )
1044+ imp_stats = imp_stats .astype (float )
9571045 if imp_stats .size == 0 :
9581046 raise ValueError (
9591047 "Error: Attribute imp_mat is empty. Recalculate Impact"
@@ -1593,36 +1681,6 @@ def run(i_time):
15931681
15941682 return imp_list
15951683
1596- # TODO: rewrite and deprecate method
1597- def _loc_return_imp (self , return_periods , imp , exc_imp ):
1598- """Compute local exceedence impact for given return period.
1599-
1600- Parameters
1601- ----------
1602- return_periods : np.array
1603- return periods to consider
1604- cen_pos :int
1605- centroid position
1606-
1607- Returns
1608- -------
1609- np.array
1610- """
1611- # sorted impacts
1612- sort_pos = np .argsort (imp , axis = 0 )[::- 1 , :]
1613- columns = np .ones (imp .shape , int )
1614- # pylint: disable=unsubscriptable-object # pylint/issues/3139
1615- columns *= np .arange (columns .shape [1 ])
1616- imp_sort = imp [sort_pos , columns ]
1617- # cummulative frequency at sorted intensity
1618- freq_sort = self .frequency [sort_pos ]
1619- np .cumsum (freq_sort , axis = 0 , out = freq_sort )
1620-
1621- for cen_idx in range (imp .shape [1 ]):
1622- exc_imp [:, cen_idx ] = self ._cen_return_imp (
1623- imp_sort [:, cen_idx ], freq_sort [:, cen_idx ], 0 , return_periods
1624- )
1625-
16261684 def _build_exp (self ):
16271685 return Exposures (
16281686 data = {
@@ -1657,43 +1715,6 @@ def _build_exp_event(self, event_id):
16571715 meta = None ,
16581716 )
16591717
1660- @staticmethod
1661- def _cen_return_imp (imp , freq , imp_th , return_periods ):
1662- """From ordered impact and cummulative frequency at centroid, get
1663- exceedance impact at input return periods.
1664-
1665- Parameters
1666- ----------
1667- imp : np.array
1668- sorted impact at centroid
1669- freq : np.array
1670- cummulative frequency at centroid
1671- imp_th : float
1672- impact threshold
1673- return_periods : np.array
1674- return periods
1675-
1676- Returns
1677- -------
1678- np.array
1679- """
1680- imp_th = np .asarray (imp > imp_th ).squeeze ()
1681- imp_cen = imp [imp_th ]
1682- freq_cen = freq [imp_th ]
1683- if not imp_cen .size :
1684- return np .zeros ((return_periods .size ,))
1685- try :
1686- with warnings .catch_warnings ():
1687- warnings .simplefilter ("ignore" )
1688- pol_coef = np .polyfit (np .log (freq_cen ), imp_cen , deg = 1 )
1689- except ValueError :
1690- pol_coef = np .polyfit (np .log (freq_cen ), imp_cen , deg = 0 )
1691- imp_fit = np .polyval (pol_coef , np .log (1 / return_periods ))
1692- wrong_inten = (return_periods > np .max (1 / freq_cen )) & np .isnan (imp_fit )
1693- imp_fit [wrong_inten ] = 0.0
1694-
1695- return imp_fit
1696-
16971718 def select (
16981719 self ,
16991720 event_ids = None ,
0 commit comments