Skip to content

Commit ff5037f

Browse files
committed
add plot_waterfall_accumulated (which does what previously did plot_waterfall plus arrow)
1 parent adea28a commit ff5037f

File tree

2 files changed

+424
-223
lines changed

2 files changed

+424
-223
lines changed

climada/engine/cost_benefit.py

Lines changed: 107 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import logging
2525
import numpy as np
2626
import matplotlib.pyplot as plt
27-
from matplotlib.patches import Rectangle
27+
from matplotlib.patches import Rectangle, FancyArrowPatch
2828
from tabulate import tabulate
2929

3030
from climada.engine.impact import Impact
@@ -197,7 +197,7 @@ def calc(self, hazard, entity, haz_future=None, ent_future=None, \
197197
self._print_results()
198198

199199
def plot_cost_benefit(self):
200-
""" Plot cost-benefit graph. Call after calc()
200+
""" Plot cost-benefit graph. Call after calc().
201201
202202
Returns:
203203
matplotlib.figure.Figure, matplotlib.axes._subplots.AxesSubplot
@@ -238,13 +238,13 @@ def plot_cost_benefit(self):
238238
return fig, axis
239239

240240
def plot_event_view(self, return_per=(10, 25, 100)):
241-
""" Plot averted damages for return periods. Call after calc()
241+
""" Plot averted damages for return periods. Call after calc().
242242
243243
Returns:
244244
matplotlib.figure.Figure, matplotlib.axes._subplots.AxesSubplot
245245
"""
246246
if not self.imp_meas_future:
247-
LOGGER.error('Compute COstBenefit.calc() first')
247+
LOGGER.error('Compute CostBenefit.calc() first')
248248
raise ValueError
249249
fig, axis = plt.subplots(1, 1)
250250
avert_rp = dict()
@@ -274,8 +274,9 @@ def plot_event_view(self, return_per=(10, 25, 100)):
274274
return fig, axis
275275

276276
def plot_waterfall(self, hazard, entity, haz_future, ent_future,
277-
risk_func=risk_aai_agg, imp_time_depen=1):
278-
""" Plot waterfall graph. Can be called before and after calc()
277+
risk_func=risk_aai_agg):
278+
""" Plot waterfall graph with given risk metric. Can be called before
279+
and after calc().
279280
280281
Parameters:
281282
hazard (Hazard): hazard
@@ -285,8 +286,6 @@ def plot_waterfall(self, hazard, entity, haz_future, ent_future,
285286
ent_future (Entity): entity in the future
286287
risk_func (func, optional): function describing risk measure given
287288
an Impact. Default: average annual impact (aggregated).
288-
imp_time_depen (float, optional): parameter which represent time
289-
evolution of impact. Default: 1 (linear).
290289
291290
Returns:
292291
matplotlib.figure.Figure, matplotlib.axes._subplots.AxesSubplot
@@ -315,28 +314,102 @@ def plot_waterfall(self, hazard, entity, haz_future, ent_future,
315314
norm_fact, norm_name = self._norm_values(curr_risk)
316315

317316
# current situation
318-
risk_future = curr_risk
317+
LOGGER.info('Risk at {:d}: {:.3e}'.format(self.present_year, curr_risk))
318+
319+
# changing future
320+
# socio-economic dev
321+
imp = Impact()
322+
imp.calc(ent_future.exposures, ent_future.impact_funcs, hazard)
323+
risk_dev = risk_func(imp)
324+
# current situation
325+
LOGGER.info('Risk with development at {:d}: {:.3e}'.format(self.future_year,
326+
risk_dev))
327+
328+
# socioecon + cc
329+
LOGGER.info('Risk with development and climate change at {:d}: {:.3e}'.\
330+
format(self.future_year, fut_risk))
331+
332+
axis.bar(1, curr_risk/norm_fact)
333+
axis.text(1, curr_risk/norm_fact, str(int(round(curr_risk/norm_fact))), \
334+
horizontalalignment='center', verticalalignment='bottom', \
335+
fontsize=12, color='k')
336+
axis.bar(2, height=(risk_dev-curr_risk)/norm_fact, bottom=curr_risk/norm_fact)
337+
axis.text(2, curr_risk/norm_fact + (risk_dev-curr_risk)/norm_fact/2, \
338+
str(int(round((risk_dev-curr_risk)/norm_fact))), \
339+
horizontalalignment='center', verticalalignment='center', fontsize=12, color='k')
340+
axis.bar(3, height=(fut_risk-risk_dev)/norm_fact, bottom=risk_dev/norm_fact)
341+
axis.text(3, risk_dev/norm_fact + (fut_risk-risk_dev)/norm_fact/2, \
342+
str(int(round((fut_risk-risk_dev)/norm_fact))), \
343+
horizontalalignment='center', verticalalignment='center', fontsize=12, color='k')
344+
axis.bar(4, height=fut_risk/norm_fact)
345+
axis.text(4, fut_risk/norm_fact, str(int(round(fut_risk/norm_fact))), \
346+
horizontalalignment='center', verticalalignment='bottom', \
347+
fontsize=12, color='k')
348+
plt.xticks(np.arange(4)+1, ['Risk ' + str(self.present_year), \
349+
'Economic \ndevelopment', 'Climate \nchange', 'Risk ' + str(self.future_year)])
350+
axis.set_ylabel('Impact (' + self.unit + ' ' + norm_name + ')')
351+
axis.set_title('Risk at {:d} and {:d}'.format(self.present_year, self.future_year))
352+
return fig, axis
353+
354+
def plot_waterfall_accumulated(self, hazard, entity, haz_future, ent_future,
355+
risk_func=risk_aai_agg, imp_time_depen=1,
356+
plot_arrow=True):
357+
""" Plot waterfall graph with accumulated values from present to future
358+
year. Call after calc(). Provide same risk_func and imp_time_depen as
359+
in calc.
360+
361+
Parameters:
362+
hazard (Hazard): hazard
363+
entity (Entity): entity
364+
haz_future (Hazard): hazard in the future (future year provided at
365+
ent_future)
366+
ent_future (Entity): entity in the future
367+
risk_func (func, optional): function describing risk measure given
368+
an Impact. Default: average annual impact (aggregated).
369+
imp_time_depen (float, optional): parameter which represent time
370+
evolution of impact. Default: 1 (linear).
371+
plot_arrow (bool, optional): plot adaptation arrow
372+
373+
Returns:
374+
matplotlib.figure.Figure, matplotlib.axes._subplots.AxesSubplot
375+
"""
376+
if not self.imp_meas_future or not self.imp_meas_present:
377+
LOGGER.error('Compute CostBenefit.calc() first')
378+
raise ValueError
379+
if ent_future.exposures.ref_year == entity.exposures.ref_year:
380+
LOGGER.error('Same reference years for future and present entities.')
381+
raise ValueError
382+
383+
self.present_year = entity.exposures.ref_year
384+
self.future_year = ent_future.exposures.ref_year
385+
386+
# current situation
387+
curr_risk = self.imp_meas_present['no measure']['risk']
319388
time_dep = self._time_dependency_array()
320-
risk_curr = self._npv_unaverted_impact(risk_future, entity.disc_rates,
389+
risk_curr = self._npv_unaverted_impact(curr_risk, entity.disc_rates,
321390
time_dep)
322-
LOGGER.info('Risk function at {:d}: {:.3e}'.format(self.present_year,
323-
risk_future))
391+
LOGGER.info('Current total risk at {:d}: {:.3e}'.format(self.future_year,
392+
risk_curr))
324393

325394
# changing future
326395
time_dep = self._time_dependency_array(imp_time_depen)
327396
# socio-economic dev
328397
imp = Impact()
329398
imp.calc(ent_future.exposures, ent_future.impact_funcs, hazard)
330-
risk_future = risk_func(imp)
331-
risk_dev = self._npv_unaverted_impact(risk_future, entity.disc_rates,
399+
risk_dev = self._npv_unaverted_impact(risk_func(imp), entity.disc_rates,
332400
time_dep, curr_risk)
401+
LOGGER.info('Total risk with development at {:d}: {:.3e}'.format( \
402+
self.future_year, risk_dev))
403+
333404
# socioecon + cc
334-
risk_future = fut_risk
335-
risk_tot = self._npv_unaverted_impact(risk_future, entity.disc_rates,
336-
time_dep, curr_risk)
337-
LOGGER.info('Risk function at {:d}: {:.3e}'.format(self.future_year,
338-
risk_future))
405+
risk_tot = self._npv_unaverted_impact(self.imp_meas_future['no measure']['risk'], \
406+
entity.disc_rates, time_dep, curr_risk)
407+
LOGGER.info('Total risk with development and climate change at {:d}: {:.3e}'.\
408+
format(self.future_year, risk_tot))
339409

410+
# plot
411+
fig, axis = plt.subplots(1, 1)
412+
norm_fact, norm_name = self._norm_values(curr_risk)
340413
axis.bar(1, risk_curr/norm_fact)
341414
axis.text(1, risk_curr/norm_fact, str(int(round(risk_curr/norm_fact))), \
342415
horizontalalignment='center', verticalalignment='bottom', \
@@ -349,16 +422,27 @@ def plot_waterfall(self, hazard, entity, haz_future, ent_future,
349422
axis.text(3, risk_dev/norm_fact + (risk_tot-risk_dev)/norm_fact/2, \
350423
str(int(round((risk_tot-risk_dev)/norm_fact))), \
351424
horizontalalignment='center', verticalalignment='center', fontsize=12, color='k')
352-
axis.bar(4, height=risk_tot/norm_fact)
425+
bar_4 = axis.bar(4, height=risk_tot/norm_fact)
353426
axis.text(4, risk_tot/norm_fact, str(int(round(risk_tot/norm_fact))), \
354427
horizontalalignment='center', verticalalignment='bottom', \
355428
fontsize=12, color='k')
429+
430+
if plot_arrow:
431+
bar_bottom, bar_top = bar_4[0].get_bbox().get_points()
432+
axis.text(bar_top[0] - (bar_top[0]-bar_bottom[0])/2, bar_top[1],
433+
"Averted", ha="center", va="top", rotation=270, size=15)
434+
arrow_len = min(np.array(list(self.benefit.values())).sum()/norm_fact,
435+
risk_tot/norm_fact)
436+
axis.add_patch(FancyArrowPatch((bar_top[0] - (bar_top[0]-bar_bottom[0])/2, \
437+
bar_top[1]), (bar_top[0]- (bar_top[0]-bar_bottom[0])/2, \
438+
risk_tot/norm_fact-arrow_len), mutation_scale=100, color='k', \
439+
alpha=0.4))
440+
356441
plt.xticks(np.arange(4)+1, ['Risk ' + str(self.present_year), \
357442
'Economic \ndevelopment', 'Climate \nchange', 'Risk ' + str(self.future_year)])
358443
axis.set_ylabel('Impact (' + self.unit + ' ' + norm_name + ')')
359-
axis.set_title('Total accumulated damage from {:d} to {:d}'.format(
360-
self.present_year, self.future_year))
361-
444+
axis.set_title('Total accumulated impact from {:d} to {:d}'.format( \
445+
self.present_year, self.future_year))
362446
return fig, axis
363447

364448
def _calc_impact_measures(self, hazard, exposures, meas_set, imp_fun_set, \

0 commit comments

Comments
 (0)