Skip to content

Commit

Permalink
Feature/deprecation (#535)
Browse files Browse the repository at this point in the history
* calibration_opt: get rid of deprecated methods

* deprecate calc (while 'insured' is not considered)

* deprecation Impact.calc and Entity.read_excel

* cosmetics

* fix circular import

* cosmetics

* sklearn.neighbors.DistanceMetrics has deprecated

* impact.calc deprecation

* impact.calc deprecation

* unsequa tutorial: impact.calc deprecation

* polygons_lines tutorial: impact.calc deprecation

* ImpactFuncSet tutorial: impact.calc deprecation

* MeasureSet tutorial: impact.calc deprecation

* api_client tutorial: impact.calc deprecation

* cosmetics

* increased discrepancy tolerance

* increased discrepancy tolerance

* engine.impact_calc: undo merge reverts

* impact_calc : obsoletion of insured_impact

* engine.impact_calc: fix inadequate truth value

* exposures: consitently use set_gdf

* refactor insured impact calculation

* fix

* cosmetics
  • Loading branch information
emanuel-schmid authored Oct 11, 2022
1 parent 845d5b8 commit 7566391
Show file tree
Hide file tree
Showing 26 changed files with 228 additions and 266 deletions.
8 changes: 4 additions & 4 deletions climada/engine/calibration_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from scipy import interpolate
from scipy.optimize import minimize

from climada.engine import Impact
from climada.engine import ImpactCalc
from climada.entity import ImpactFuncSet, ImpfTropCyclone, impact_funcs
from climada.engine.impact_data import emdat_impact_yearlysum, emdat_impact_event

Expand Down Expand Up @@ -68,10 +68,10 @@ def calib_instance(hazard, exposure, impact_func, df_out=pd.DataFrame(),
"""
ifs = ImpactFuncSet()
ifs.append(impact_func)
impacts = Impact()
impacts.calc(exposure, ifs, hazard, assign_centroids=False)
impacts = ImpactCalc(exposures=exposure, impfset=ifs, hazard=hazard)\
.impact(assign_centroids=False)
if yearly_impact: # impact per year
iys = impacts.calc_impact_year_set(all_years=True)
iys = impacts.impact_per_year(all_years=True)
# Loop over whole year range:
if df_out.empty | df_out.index.shape[0] == 1:
for cnt_, year in enumerate(np.sort(list((iys.keys())))):
Expand Down
21 changes: 10 additions & 11 deletions climada/engine/cost_benefit.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from matplotlib.patches import Rectangle, FancyArrowPatch
from tabulate import tabulate

from climada.engine.impact import Impact
from climada.engine.impact_calc import ImpactCalc

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -539,12 +539,12 @@ def plot_waterfall(hazard, entity, haz_future, ent_future,
present_year = entity.exposures.ref_year
future_year = ent_future.exposures.ref_year

imp = Impact()
imp.calc(entity.exposures, entity.impact_funcs, hazard, assign_centroids=False)
imp = ImpactCalc(entity.exposures, entity.impact_funcs, hazard)\
.impact(assign_centroids=False)
curr_risk = risk_func(imp)

imp = Impact()
imp.calc(ent_future.exposures, ent_future.impact_funcs, haz_future, assign_centroids=False)
imp = ImpactCalc(ent_future.exposures, ent_future.impact_funcs, haz_future)\
.impact(assign_centroids=False)
fut_risk = risk_func(imp)

if not axis:
Expand All @@ -556,8 +556,8 @@ def plot_waterfall(hazard, entity, haz_future, ent_future,

# changing future
# socio-economic dev
imp = Impact()
imp.calc(ent_future.exposures, ent_future.impact_funcs, hazard, assign_centroids=False)
imp = ImpactCalc(ent_future.exposures, ent_future.impact_funcs, hazard)\
.impact(assign_centroids=False)
risk_dev = risk_func(imp)
LOGGER.info('Risk with development at {:d}: {:.3e}'.format(future_year, risk_dev))

Expand Down Expand Up @@ -701,8 +701,8 @@ def plot_waterfall_accumulated(self, hazard, entity, ent_future,
# changing future
time_dep = self._time_dependency_array(imp_time_depen)
# socio-economic dev
imp = Impact()
imp.calc(ent_future.exposures, ent_future.impact_funcs, hazard, assign_centroids=False)
imp = ImpactCalc(ent_future.exposures, ent_future.impact_funcs, hazard)\
.impact(assign_centroids=False)
risk_dev = self._npv_unaverted_impact(risk_func(imp), entity.disc_rates,
time_dep, curr_risk)
LOGGER.info('Total risk with development at {:d}: {:.3e}'.format(
Expand Down Expand Up @@ -774,8 +774,7 @@ def _calc_impact_measures(self, hazard, exposures, meas_set, imp_fun_set,

# compute impact without measures
LOGGER.debug('%s impact with no measure.', when)
imp_tmp = Impact()
imp_tmp.calc(exposures, imp_fun_set, hazard, assign_centroids=False)
imp_tmp = ImpactCalc(exposures, imp_fun_set, hazard).impact(assign_centroids=False)
impact_meas[NO_MEASURE] = dict()
impact_meas[NO_MEASURE]['cost'] = (0, 0)
impact_meas[NO_MEASURE]['risk'] = risk_func(imp_tmp)
Expand Down
10 changes: 4 additions & 6 deletions climada/engine/forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from cartopy.io import shapereader
from mpl_toolkits.axes_grid1 import make_axes_locatable

from climada.engine import Impact
from climada.engine import ImpactCalc
import climada.util.plot as u_plot
from climada.util.config import CONFIG
from climada.util.files_handler import to_list
Expand Down Expand Up @@ -180,7 +180,7 @@ def __init__(
else:
self.exposure_name = exposure_name
self.vulnerability = impact_funcs
self._impact = [Impact() for dt in self.run_datetime]
self._impact = [None for dt in self.run_datetime]

def ei_exp(self, run_datetime=None):
"""
Expand Down Expand Up @@ -295,10 +295,8 @@ def calc(self, force_reassign=False):
if self.hazard:
self.exposure.assign_centroids(self.hazard[0], overwrite=force_reassign)
for ind_i, haz_i in enumerate(self.hazard):
self._impact[ind_i].calc(
self.exposure, self.vulnerability, haz_i,
save_mat=True, assign_centroids=False
)
self._impact[ind_i] = ImpactCalc(self.exposure, self.vulnerability, haz_i)\
.impact(save_mat=True, assign_centroids=False)

def plot_imp_map(
self,
Expand Down
24 changes: 8 additions & 16 deletions climada/engine/impact.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,24 +192,16 @@ def __init__(self,


def calc(self, exposures, impact_funcs, hazard, save_mat=False, assign_centroids=True):
"""This function is deprecated, use ImpactCalc.impact
and ImpactCalc.insured_impact instead.
"""This function is deprecated, use ``ImpactCalc.impact`` instead.
"""
LOGGER.warning("The use of Impact().calc() is deprecated."
" Use ImpactCalc().impact() instead.")
from climada.engine.impact_calc import ImpactCalc
impcalc= ImpactCalc(exposures, impact_funcs, hazard)
if ('deductible' in exposures.gdf) and ('cover' in exposures.gdf) \
and exposures.gdf.cover.max():
LOGGER.warning(
"The use of Impact().calc() is deprecated for exposures with "
"deductible and/or cover. Use ImpactCalc().insured_impact() "
" for insured impacts instead. For non-insured impacts "
"please use ImpactCalc().impact()"
)
self.__dict__ = impcalc.insured_impact(save_mat, assign_centroids).__dict__
else:
LOGGER.warning("The use of Impact().calc() is deprecated. "
"Use ImpactCalc().impact() instead.")
self.__dict__ = impcalc.impact(save_mat, assign_centroids).__dict__
impcalc = ImpactCalc(exposures, impact_funcs, hazard)
self.__dict__ = impcalc.impact(
save_mat=save_mat,
assign_centroids=assign_centroids
).__dict__

#TODO: new name
@classmethod
Expand Down
143 changes: 46 additions & 97 deletions climada/engine/impact_calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,35 +74,8 @@ def n_events(self):
"""Number of hazard events (size of event_id array)"""
return self.hazard.size

@property
def deductible(self):
"""
Deductibles from the exposures. Returns None if no deductibles defined.
Returns
-------
np.array
The deductible per exposure point
"""
if 'deductible' in self.exposures.gdf.columns:
return self.exposures.gdf['deductible'].to_numpy()

@property
def cover(self):
"""
Covers from the exposures. Returns None if no covers defined.
Returns
-------
np.array
The cover per exposure point
"""
if 'cover' in self.exposures.gdf.columns:
return self.exposures.gdf['cover'].to_numpy()

def impact(self, save_mat=True, assign_centroids=True):
def impact(self, save_mat=True, assign_centroids=True,
ignore_cover=False, ignore_deductible=False):
"""Compute the impact of a hazard on exposures.
Parameters
Expand All @@ -116,82 +89,47 @@ def impact(self, save_mat=True, assign_centroids=True):
computation time if the hazards' centroids are already assigned to the exposures
object.
Default: True
ignore_cover : bool, optional
if set to True, the column 'cover' of the exposures GeoDataFrame, if present, is
ignored and the impact it not capped by the values in this column.
Default: False
ignore_deductible : bool, opotional
if set to True, the column 'deductible' of the exposures GeoDataFrame, if present, is
ignored and the impact it not reduced through values in this column.
Default: False
Examples
--------
>>> haz = Hazard.from_mat(HAZ_DEMO_MAT) # Set hazard
>>> impfset = ImpactFuncSet.from_excel(ENT_TEMPLATE_XLS)
>>> exp = Exposures(pd.read_excel(ENT_TEMPLATE_XLS))
>>> impcalc = ImpactCal(exp, impfset, haz)
>>> imp = impcalc.insured_impact()
>>> imp = impcalc.impact(insured=True)
>>> imp.aai_agg
Notes
-----
Deductible and/or cover values in the exposures are ignored.
In case the exposures has no centroids assigned for the given hazard,
the column is added to the exposures geodataframe.
See also
--------
apply_deductible_to_mat : apply deductible to impact matrix
apply_cover_to_mat : apply cover to impact matrix
"""
impf_col = self.exposures.get_impf_column(self.hazard.haz_type)
exp_gdf = self.minimal_exp_gdf(impf_col, assign_centroids)
exp_gdf = self.minimal_exp_gdf(impf_col, assign_centroids, ignore_cover, ignore_deductible)
if exp_gdf.size == 0:
return self._return_empty(save_mat)
LOGGER.info('Calculating impact for %s assets (>0) and %s events.',
self.n_exp_pnt, self.n_events)
exp_gdf.size, self.n_events)
imp_mat_gen = self.imp_mat_gen(exp_gdf, impf_col)
return self._return_impact(imp_mat_gen, save_mat)

insured = ('cover' in exp_gdf and exp_gdf.cover.max() >= 0) \
or ('deductible' in exp_gdf and exp_gdf.deductible.max() > 0)
if insured:
LOGGER.info("cover and/or deductible columns detected,"
" going to calculate insured impact")
#TODO: make a better impact matrix generator for insured impacts when
# the impact matrix is already present
def insured_impact(self, save_mat=False, assign_centroids=True):
"""Compute the impact of a hazard on exposures with a deductible and/or
cover.
For each exposure point, the impact per event is obtained by
substracting the deductible (and is maximally equal to the cover).
imp_mat_gen = self.insured_mat_gen(imp_mat_gen, exp_gdf, impf_col)

Parameters
----------
save_mat : bool
if true, save the total impact matrix (events x exposures)
assign_centroids : bool, optional
indicates whether centroids are assigned to the self.exposures object.
Centroids assignment is an expensive operation; set this to ``False`` to save
computation time if the hazards' centroids are already assigned to the exposures
object.
Default: True
Examples
--------
>>> haz = Hazard.from_mat(HAZ_DEMO_MAT) # Set hazard
>>> impfset = ImpactFuncSet.from_excel(ENT_TEMPLATE_XLS)
>>> exp = Exposures(pd.read_excel(ENT_TEMPLATE_XLS))
>>> impcalc = ImpactCal(exp, impfset, haz)
>>> imp = impcalc.insured_impact()
>>> imp.aai_agg
See also
--------
apply_deductible_to_mat:
apply deductible to impact matrix
apply_cover_to_mat:
apply cover to impact matrix
"""
if self.cover is None and self.deductible is None:
raise AttributeError("Neither cover nor deductible defined."
"Please set exposures.gdf.cover"
"and/or exposures.gdf.deductible")
impf_col = self.exposures.get_impf_column(self.hazard.haz_type)
exp_gdf = self.minimal_exp_gdf(impf_col, assign_centroids)
if exp_gdf.size == 0:
return self._return_empty(save_mat)
LOGGER.info('Calculating impact for %s assets (>0) and %s events.',
exp_gdf.size, self.hazard.size)

imp_mat_gen = self.imp_mat_gen(exp_gdf, impf_col)
ins_mat_gen = self.insured_mat_gen(imp_mat_gen, exp_gdf, impf_col)
return self._return_impact(ins_mat_gen, save_mat)
return self._return_impact(imp_mat_gen, save_mat)

def _return_impact(self, imp_mat_gen, save_mat):
"""Return an impact object from an impact matrix generator
Expand All @@ -212,7 +150,6 @@ def _return_impact(self, imp_mat_gen, save_mat):
--------
imp_mat_gen : impact matrix generator
insured_mat_gen: insured impact matrix generator
"""
if save_mat:
imp_mat = self.stitch_impact_matrix(imp_mat_gen)
Expand Down Expand Up @@ -252,7 +189,7 @@ def _return_empty(self, save_mat):
return Impact.from_eih(self.exposures, self.impfset, self.hazard,
at_event, eai_exp, aai_agg, imp_mat)

def minimal_exp_gdf(self, impf_col, assign_centroids):
def minimal_exp_gdf(self, impf_col, assign_centroids, ignore_cover, ignore_deductible):
"""Get minimal exposures geodataframe for impact computation
Parameters
Expand All @@ -267,6 +204,12 @@ def minimal_exp_gdf(self, impf_col, assign_centroids):
Centroids assignment is an expensive operation; set this to ``False`` to save
computation time if the centroids have not changed since the last impact
calculation.
include_cover : bool
if set to True, the column 'cover' of the exposures GeoDataFrame is excluded from the
returned GeoDataFrame, otherwise it is included if present.
include_deductible : bool
if set to True, the column 'deductible' of the exposures GeoDataFrame is excluded from
the returned GeoDataFrame, otherwise it is included if present.
"""
if assign_centroids:
self.exposures.assign_centroids(self.hazard, overwrite=True)
Expand All @@ -280,9 +223,15 @@ def minimal_exp_gdf(self, impf_col, assign_centroids):
& (self.exposures.gdf.value.values != 0) # value != 0
& (self.exposures.gdf[self.hazard.centr_exp_col].values >= 0) # centroid assigned
)

columns = ['value', impf_col, self.hazard.centr_exp_col]
if not ignore_cover and 'cover' in self.exposures.gdf:
columns.append('cover')
if not ignore_deductible and 'deductible' in self.exposures.gdf:
columns.append('deductible')
exp_gdf = gpd.GeoDataFrame(
{col: self.exposures.gdf[col].values[mask]
for col in ['value', impf_col, self.hazard.centr_exp_col]},
for col in columns},
)
if exp_gdf.size == 0:
LOGGER.warning("No exposures with value >0 in the vicinity of the hazard.")
Expand Down Expand Up @@ -375,16 +324,16 @@ def insured_mat_gen(self, imp_mat_gen, exp_gdf, impf_col):
"""
for mat, exp_idx in imp_mat_gen:
impf_id = exp_gdf[impf_col][exp_idx[0]]
deductible = self.deductible[self._orig_exp_idx[exp_idx]]
cent_idx = exp_gdf[self.hazard.centr_exp_col].values[exp_idx]
impf = self.impfset.get_func(
haz_type=self.hazard.haz_type, fun_id=impf_id
)
mat = self.apply_deductible_to_mat(
mat, deductible, self.hazard, cent_idx, impf
)
cover = self.cover[self._orig_exp_idx[exp_idx]]
mat = self.apply_cover_to_mat(mat, cover)
haz_type=self.hazard.haz_type,
fun_id=impf_id)
if 'deductible' in exp_gdf:
deductible = exp_gdf.deductible.values[exp_idx]
mat = self.apply_deductible_to_mat(mat, deductible, self.hazard, cent_idx, impf)
if 'cover' in exp_gdf:
cover = exp_gdf.cover.values[exp_idx]
mat = self.apply_cover_to_mat(mat, cover)
yield (mat, exp_idx)

def impact_matrix(self, exp_values, cent_idx, impf):
Expand Down
8 changes: 3 additions & 5 deletions climada/engine/test/test_cost_benefit.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from climada.hazard.base import Hazard
from climada.engine.cost_benefit import CostBenefit, risk_aai_agg, \
risk_rp_100, risk_rp_250, _norm_values
from climada.engine import Impact
from climada.engine import ImpactCalc
from climada.util.constants import ENT_DEMO_FUTURE, ENT_DEMO_TODAY
from climada.util.api_client import Client

Expand Down Expand Up @@ -638,7 +638,7 @@ def test_apply_transf_cost_fact_pass(self):
new_cb.imp_meas_future[tr_name]['risk'],
np.sum(new_imp * cost_ben.imp_meas_future['no measure']['impact'].frequency), 5)
self.assertAlmostEqual(new_cb.cost_ben_ratio[tr_name] * new_cb.benefit[tr_name],
risk_transf[2] * 32106013195.316242)
risk_transf[2] * 32106013195.316242, 4)
self.assertTrue(
np.allclose(new_cb.imp_meas_future[tr_name]['efc'].impact,
new_cb.imp_meas_future[tr_name]['impact'].calc_freq_curve().impact))
Expand Down Expand Up @@ -822,9 +822,7 @@ def test_impact(self):
ent = Entity.from_excel(ENT_DEMO_TODAY)
ent.check()
hazard = Hazard.from_mat(HAZ_TEST_MAT)
impact = Impact()
ent.exposures.assign_centroids(hazard)
impact.calc(ent.exposures, ent.impact_funcs, hazard, assign_centroids=False)
impact = ImpactCalc(ent.exposures, ent.impact_funcs, hazard).impact()
return impact

def test_risk_aai_agg_pass(self):
Expand Down
Loading

0 comments on commit 7566391

Please sign in to comment.