33
33
from typing import Any , Iterable , Union
34
34
35
35
import contextily as ctx
36
+ import geopandas as gpd
36
37
import h5py
37
38
import matplotlib .animation as animation
38
39
import matplotlib .pyplot as plt
39
40
import numpy as np
40
41
import pandas as pd
41
42
import xlsxwriter
43
+ from deprecation import deprecated
42
44
from pyproj import CRS as pyprojCRS
43
45
from rasterio .crs import CRS as rasterioCRS # pylint: disable=no-name-in-module
44
46
from scipy import sparse
45
47
from tqdm import tqdm
46
48
47
49
import climada .util .coordinates as u_coord
48
50
import climada .util .dates_times as u_dt
51
+ import climada .util .interpolation as u_interp
49
52
import climada .util .plot as u_plot
50
53
from climada import CONFIG
51
54
from climada .entity import Exposures
@@ -486,20 +489,54 @@ def calc_impact_year_set(self, all_years=True, year_range=None):
486
489
)
487
490
return self .impact_per_year (all_years = all_years , year_range = year_range )
488
491
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.
493
503
494
504
Parameters
495
505
----------
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.
499
528
500
529
Returns
501
530
-------
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
503
540
"""
504
541
LOGGER .info (
505
542
"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)):
509
546
"Attribute imp_mat is empty. Recalculate Impact"
510
547
"instance with parameter save_mat=True"
511
548
)
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
532
553
)
533
554
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
+ )
535
612
536
613
def calc_freq_curve (self , return_per = None ):
537
614
"""Compute impact exceedance frequency curve.
@@ -924,6 +1001,10 @@ def plot_basemap_impact_exposure(
924
1001
925
1002
return axis
926
1003
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
+ )
927
1008
def plot_rp_imp (
928
1009
self ,
929
1010
return_periods = (25 , 50 , 100 , 250 ),
@@ -932,7 +1013,11 @@ def plot_rp_imp(
932
1013
axis = None ,
933
1014
** kwargs ,
934
1015
):
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.
936
1021
Calls local_exceedance_imp.
937
1022
938
1023
Parameters
@@ -953,7 +1038,10 @@ def plot_rp_imp(
953
1038
imp_stats : np.array
954
1039
return_periods.size x num_centroids
955
1040
"""
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 )
957
1045
if imp_stats .size == 0 :
958
1046
raise ValueError (
959
1047
"Error: Attribute imp_mat is empty. Recalculate Impact"
@@ -1593,36 +1681,6 @@ def run(i_time):
1593
1681
1594
1682
return imp_list
1595
1683
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
-
1626
1684
def _build_exp (self ):
1627
1685
return Exposures (
1628
1686
data = {
@@ -1657,43 +1715,6 @@ def _build_exp_event(self, event_id):
1657
1715
meta = None ,
1658
1716
)
1659
1717
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
-
1697
1718
def select (
1698
1719
self ,
1699
1720
event_ids = None ,
0 commit comments