Skip to content

Commit fafb76f

Browse files
committed
add exposures plot methods to impact
1 parent c71b529 commit fafb76f

File tree

2 files changed

+150
-58
lines changed

2 files changed

+150
-58
lines changed

climada/engine/impact.py

Lines changed: 142 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
__all__ = ['ImpactFreqCurve', 'Impact']
2323

24+
import ast
2425
import logging
2526
import csv
2627
from itertools import zip_longest
@@ -30,6 +31,7 @@
3031
import xlsxwriter
3132

3233
from climada.entity.tag import Tag
34+
from climada.entity.exposures.base import Exposures, DEF_CRS
3335
from climada.hazard.tag import Tag as TagHaz
3436
from climada.entity.exposures.base import INDICATOR_IF, INDICATOR_CENTR
3537
import climada.util.plot as u_plot
@@ -65,6 +67,7 @@ def __init__(self):
6567
self.event_name = list()
6668
self.date = np.array([], int)
6769
self.coord_exp = np.ndarray([], float)
70+
self.crs = DEF_CRS
6871
self.eai_exp = np.array([])
6972
self.at_event = np.array([])
7073
self.frequency = np.array([])
@@ -157,6 +160,7 @@ def calc(self, exposures, impact_funcs, hazard, save_mat=False):
157160
self.eai_exp = np.zeros(exposures.value.size)
158161
self.tag = {'exp': exposures.tag, 'if_set': impact_funcs.tag,
159162
'haz': hazard.tag}
163+
self.crs = exposures.crs
160164

161165
# Select exposures with positive value and assigned centroid
162166
exp_idx = np.where(np.logical_and(exposures.value > 0, \
@@ -215,10 +219,10 @@ def calc(self, exposures, impact_funcs, hazard, save_mat=False):
215219
if save_mat:
216220
self.imp_mat = self.imp_mat.tocsr()
217221

218-
def plot_eai_exposure(self, mask=None, ignore_zero=True,
219-
pop_name=True, buffer=0.0, extend='neither',
220-
var_name=None, **kwargs):
221-
"""Plot expected annual impact of each exposure.
222+
def plot_hexbin_eai_exposure(self, mask=None, ignore_zero=True,
223+
pop_name=True, buffer=0.0, extend='neither',
224+
var_name=None, **kwargs):
225+
"""Plot hexbin expected annual impact of each exposure.
222226
223227
Parameters:
224228
mask (np.array, optional): mask to apply to eai_exp plotted.
@@ -235,61 +239,90 @@ def plot_eai_exposure(self, mask=None, ignore_zero=True,
235239
Returns:
236240
matplotlib.figure.Figure, cartopy.mpl.geoaxes.GeoAxesSubplot
237241
"""
238-
title = 'Expected annual impact'
239-
if var_name is None:
240-
var_name = 'Impact (' + self.unit + ')'
241-
if mask is None:
242-
mask = np.ones((self.eai_exp.size,), dtype=bool)
243-
if ignore_zero:
244-
pos_vals = self.eai_exp[mask] > 0
245-
else:
246-
pos_vals = np.ones((self.eai_exp[mask].size,), dtype=bool)
247-
if 'reduce_C_function' not in kwargs:
248-
kwargs['reduce_C_function'] = np.sum
249-
return u_plot.geo_bin_from_array(self.eai_exp[mask][pos_vals], \
250-
self.coord_exp[mask][pos_vals], var_name, title, pop_name, \
251-
buffer, extend, **kwargs)
242+
eai_exp = self._build_exp()
243+
fig, ax = eai_exp.plot_hexbin(mask, ignore_zero, pop_name, buffer,
244+
extend, **kwargs)
245+
ax[0, 0].set_title('Expected annual impact')
246+
return fig, ax
252247

253-
def _exp_impact(self, exp_iimp, exposures, hazard, imp_fun, insure_flag):
254-
"""Compute impact for inpute exposure indexes and impact function.
248+
def plot_scatter_eai_exposure(self, mask=None, ignore_zero=True,
249+
pop_name=True, buffer=0.0, extend='neither',
250+
var_name=None, **kwargs):
251+
"""Plot scatter expected annual impact of each exposure.
255252
256253
Parameters:
257-
exp_iimp (np.array): exposures indexes
258-
exposures (Exposures): exposures instance
259-
hazard (Hazard): hazard instance
260-
imp_fun (ImpactFunc): impact function instance
261-
insure_flag (bool): consider deductible and cover of exposures
254+
mask (np.array, optional): mask to apply to eai_exp plotted.
255+
ignore_zero (bool, optional): flag to indicate if zero and negative
256+
values are ignored in plot. Default: False
257+
pop_name (bool, optional): add names of the populated places
258+
buffer (float, optional): border to add to coordinates.
259+
Default: 1.0.
260+
extend (str, optional): extend border colorbar with arrows.
261+
[ 'neither' | 'both' | 'min' | 'max' ]
262+
var_name (str, optional): Colorbar label
263+
kwargs (optional): arguments for hexbin matplotlib function
264+
265+
Returns:
266+
matplotlib.figure.Figure, cartopy.mpl.geoaxes.GeoAxesSubplot
262267
"""
263-
if not exp_iimp.size:
264-
return
268+
eai_exp = self._build_exp()
269+
fig, ax = eai_exp.plot_scatter(mask, ignore_zero, pop_name, buffer,
270+
extend, **kwargs)
271+
ax[0, 0].set_title('Expected annual impact')
272+
return fig, ax
265273

266-
# get assigned centroids
267-
icens = exposures[INDICATOR_CENTR + hazard.tag.haz_type].values[exp_iimp]
274+
def plot_raster_eai_exposure(self, res=None, raster_res=None, save_tiff=None,
275+
raster_f=lambda x: np.log10((np.fmax(x+1, 1))),
276+
label='value (log10)', **kwargs):
277+
"""Plot raster expected annual impact of each exposure.
268278
269-
# get affected intensities
270-
inten_val = hazard.intensity[:, icens]
271-
# get affected fractions
272-
fract = hazard.fraction[:, icens]
273-
# impact = fraction * mdr * value
274-
inten_val.data = imp_fun.calc_mdr(inten_val.data)
275-
impact = fract.multiply(inten_val).multiply(exposures.value.values[exp_iimp])
279+
Parameters:
280+
res (float, optional): resolution of current data in units of latitude
281+
and longitude, approximated if not provided.
282+
raster_res (float, optional): desired resolution of the raster
283+
save_tiff (str, optional): file name to save the raster in tiff
284+
format, if provided
285+
raster_f (lambda function): transformation to use to data. Default:
286+
log10 adding 1.
287+
label (str): colorbar label
288+
kwargs (optional): arguments for imshow matplotlib function
276289
277-
if insure_flag and impact.nonzero()[0].size:
278-
inten_val = hazard.intensity[:, icens].todense()
279-
paa = np.interp(inten_val, imp_fun.intensity, imp_fun.paa)
280-
impact = np.minimum(np.maximum(impact - \
281-
exposures.deductible.values[exp_iimp] * paa, 0), \
282-
exposures.cover.values[exp_iimp])
283-
self.eai_exp[exp_iimp] += np.sum(np.asarray(impact) * \
284-
hazard.frequency.reshape(-1, 1), axis=0)
285-
else:
286-
self.eai_exp[exp_iimp] += np.squeeze(np.asarray(np.sum( \
287-
impact.multiply(hazard.frequency.reshape(-1, 1)), axis=0)))
290+
Returns:
291+
matplotlib.figure.Figure, cartopy.mpl.geoaxes.GeoAxesSubplot
292+
"""
293+
eai_exp = self._build_exp()
294+
fig, ax = eai_exp.plot_raster(res, raster_res, save_tiff, raster_f,
295+
label, **kwargs)
296+
ax[0, 0].set_title('Expected annual impact')
297+
return fig, ax
298+
299+
def plot_basemap_eai_exposure(self, mask=None, ignore_zero=False, pop_name=True,
300+
buffer=0.0, extend='neither', zoom=10,
301+
url='http://tile.stamen.com/terrain/tileZ/tileX/tileY.png',
302+
**kwargs):
303+
"""Plot basemap expected annual impact of each exposure.
288304
289-
self.at_event += np.squeeze(np.asarray(np.sum(impact, axis=1)))
290-
self.tot_value += np.sum(exposures.value.values[exp_iimp])
291-
if not isinstance(self.imp_mat, list):
292-
self.imp_mat[:, exp_iimp] = impact
305+
Parameters:
306+
mask (np.array, optional): mask to apply to eai_exp plotted.
307+
ignore_zero (bool, optional): flag to indicate if zero and negative
308+
values are ignored in plot. Default: False
309+
pop_name (bool, optional): add names of the populated places
310+
buffer (float, optional): border to add to coordinates. Default: 0.0.
311+
extend (str, optional): extend border colorbar with arrows.
312+
[ 'neither' | 'both' | 'min' | 'max' ]
313+
zoom (int, optional): zoom coefficient used in the satellite image
314+
url (str, optional): image source, e.g. ctx.sources.OSM_C
315+
kwargs (optional): arguments for scatter matplotlib function, e.g.
316+
cmap='Greys'. Default: 'Wistia'
317+
318+
Returns:
319+
matplotlib.figure.Figure, cartopy.mpl.geoaxes.GeoAxesSubplot
320+
"""
321+
eai_exp = self._build_exp()
322+
fig, ax = eai_exp.plot_basemap(mask, ignore_zero, pop_name, buffer,
323+
extend, zoom, url, **kwargs)
324+
ax[0, 0].set_title('Expected annual impact')
325+
return fig, ax
293326

294327
def write_csv(self, file_name):
295328
""" Write data into csv file. imp_mat is not saved.
@@ -302,15 +335,16 @@ def write_csv(self, file_name):
302335
imp_wr.writerow(["tag_hazard", "tag_exposure", "tag_impact_func",
303336
"unit", "tot_value", "aai_agg", "event_id",
304337
"event_name", "event_date", "event_frequency",
305-
"at_event", "eai_exp", "exp_lat", "exp_lon"])
338+
"at_event", "eai_exp", "exp_lat", "exp_lon", "exp_crs"])
306339
csv_data = [[[self.tag['haz'].haz_type], [self.tag['haz'].file_name],
307340
[self.tag['haz'].description]],
308341
[[self.tag['exp'].file_name], [self.tag['exp'].description]],
309342
[[self.tag['if_set'].file_name], [self.tag['if_set'].description]],
310343
[self.unit], [self.tot_value], [self.aai_agg],
311344
self.event_id, self.event_name, self.date,
312345
self.frequency, self.at_event,
313-
self.eai_exp, self.coord_exp[:, 0], self.coord_exp[:, 1]]
346+
self.eai_exp, self.coord_exp[:, 0], self.coord_exp[:, 1],
347+
[str(self.crs)]]
314348
for values in zip_longest(*csv_data):
315349
imp_wr.writerow(values)
316350

@@ -333,7 +367,7 @@ def write_col(i_col, imp_ws, xls_data):
333367
header = ["tag_hazard", "tag_exposure", "tag_impact_func",
334368
"unit", "tot_value", "aai_agg", "event_id",
335369
"event_name", "event_date", "event_frequency",
336-
"at_event", "eai_exp", "exp_lat", "exp_lon"]
370+
"at_event", "eai_exp", "exp_lat", "exp_lon", "exp_crs"]
337371
for icol, head_dat in enumerate(header):
338372
imp_ws.write(0, icol, head_dat)
339373
data = [self.tag['haz'].haz_type, str(self.tag['haz'].file_name),
@@ -354,6 +388,7 @@ def write_col(i_col, imp_ws, xls_data):
354388
write_col(11, imp_ws, self.eai_exp)
355389
write_col(12, imp_ws, self.coord_exp[:, 0])
356390
write_col(13, imp_ws, self.coord_exp[:, 1])
391+
write_col(14, imp_ws, [str(self.crs)])
357392

358393
imp_wb.close()
359394

@@ -398,6 +433,7 @@ def read_csv(self, file_name):
398433
self.coord_exp = np.zeros((num_exp, 2))
399434
self.coord_exp[:, 0] = imp_df.exp_lat[:num_exp]
400435
self.coord_exp[:, 1] = imp_df.exp_lon[:num_exp]
436+
self.crs = ast.literal_eval(imp_df.exp_crs.values[0])
401437
self.tag['haz'] = TagHaz(str(imp_df.tag_hazard[0]),
402438
str(imp_df.tag_hazard[1]),
403439
str(imp_df.tag_hazard[2]))
@@ -439,6 +475,58 @@ def read_excel(self, file_name):
439475
self.coord_exp = np.zeros((self.eai_exp.size, 2))
440476
self.coord_exp[:, 0] = dfr.exp_lat.values[:self.eai_exp.size]
441477
self.coord_exp[:, 1] = dfr.exp_lon.values[:self.eai_exp.size]
478+
self.crs = ast.literal_eval(dfr.exp_crs.values[0])
479+
480+
def _exp_impact(self, exp_iimp, exposures, hazard, imp_fun, insure_flag):
481+
"""Compute impact for inpute exposure indexes and impact function.
482+
483+
Parameters:
484+
exp_iimp (np.array): exposures indexes
485+
exposures (Exposures): exposures instance
486+
hazard (Hazard): hazard instance
487+
imp_fun (ImpactFunc): impact function instance
488+
insure_flag (bool): consider deductible and cover of exposures
489+
"""
490+
if not exp_iimp.size:
491+
return
492+
493+
# get assigned centroids
494+
icens = exposures[INDICATOR_CENTR + hazard.tag.haz_type].values[exp_iimp]
495+
496+
# get affected intensities
497+
inten_val = hazard.intensity[:, icens]
498+
# get affected fractions
499+
fract = hazard.fraction[:, icens]
500+
# impact = fraction * mdr * value
501+
inten_val.data = imp_fun.calc_mdr(inten_val.data)
502+
impact = fract.multiply(inten_val).multiply(exposures.value.values[exp_iimp])
503+
504+
if insure_flag and impact.nonzero()[0].size:
505+
inten_val = hazard.intensity[:, icens].todense()
506+
paa = np.interp(inten_val, imp_fun.intensity, imp_fun.paa)
507+
impact = np.minimum(np.maximum(impact - \
508+
exposures.deductible.values[exp_iimp] * paa, 0), \
509+
exposures.cover.values[exp_iimp])
510+
self.eai_exp[exp_iimp] += np.sum(np.asarray(impact) * \
511+
hazard.frequency.reshape(-1, 1), axis=0)
512+
else:
513+
self.eai_exp[exp_iimp] += np.squeeze(np.asarray(np.sum( \
514+
impact.multiply(hazard.frequency.reshape(-1, 1)), axis=0)))
515+
516+
self.at_event += np.squeeze(np.asarray(np.sum(impact, axis=1)))
517+
self.tot_value += np.sum(exposures.value.values[exp_iimp])
518+
if not isinstance(self.imp_mat, list):
519+
self.imp_mat[:, exp_iimp] = impact
520+
521+
def _build_exp(self):
522+
eai_exp = Exposures()
523+
eai_exp['value'] = self.eai_exp
524+
eai_exp['latitude'] = self.coord_exp[:, 0]
525+
eai_exp['longitude'] = self.coord_exp[:, 1]
526+
eai_exp.crs = self.crs
527+
eai_exp.value_unit = self.unit
528+
eai_exp.check()
529+
return eai_exp
442530

443531
class ImpactFreqCurve():
444532
""" Impact exceedence frequency curve.

climada/engine/test/test_impact.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
HAZ_DIR = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'hazard/test/data/')
3434
HAZ_TEST_MAT = os.path.join(HAZ_DIR, 'atl_prob_no_name.mat')
3535

36+
DATA_FOLDER = os.path.join(os.path.dirname(__file__) , 'data')
37+
3638
class TestFreqCurve(unittest.TestCase):
3739
'''Test exceedence frequency curve computation'''
3840
def test_ref_value_pass(self):
@@ -306,7 +308,7 @@ def test_write_read_ev_test(self):
306308
imp_write.aai_agg = 1001
307309
imp_write.unit = 'USD'
308310

309-
file_name = os.path.dirname(__file__) + 'test.csv'
311+
file_name = os.path.join(DATA_FOLDER, 'test.csv')
310312
imp_write.write_csv(file_name)
311313

312314
imp_read = Impact()
@@ -345,7 +347,7 @@ def test_write_read_exp_test(self):
345347
imp_write.aai_agg = 1001
346348
imp_write.unit = 'USD'
347349

348-
file_name = os.path.dirname(__file__) + 'test.csv'
350+
file_name = os.path.join(DATA_FOLDER, 'test.csv')
349351
imp_write.write_csv(file_name)
350352

351353
imp_read = Impact()
@@ -361,6 +363,7 @@ def test_write_read_exp_test(self):
361363
self.assertEqual(imp_write.unit, imp_read.unit)
362364
self.assertEqual(0, len([i for i, j in
363365
zip(imp_write.event_name, imp_read.event_name) if i != j]))
366+
self.assertIsInstance(imp_read.crs, dict)
364367

365368
def test_write_read_excel_pass(self):
366369
""" Test write and read in excel """
@@ -373,7 +376,7 @@ def test_write_read_excel_pass(self):
373376
imp_write = Impact()
374377
ent.exposures.assign_centroids(hazard)
375378
imp_write.calc(ent.exposures, ent.impact_funcs, hazard)
376-
file_name = os.path.dirname(__file__) + 'test.xlsx'
379+
file_name = os.path.join(DATA_FOLDER, 'test.xlsx')
377380
imp_write.write_excel(file_name)
378381

379382
imp_read = Impact()
@@ -390,6 +393,7 @@ def test_write_read_excel_pass(self):
390393
self.assertEqual(imp_write.unit, imp_read.unit)
391394
self.assertEqual(0, len([i for i, j in
392395
zip(imp_write.event_name, imp_read.event_name) if i != j]))
396+
self.assertIsInstance(imp_read.crs, dict)
393397

394398
def test_write_imp_mat(self):
395399
""" Test write_excel_imp_mat function """
@@ -402,7 +406,7 @@ def test_write_imp_mat(self):
402406
impact.imp_mat[4, :] = np.arange(4)*5
403407
impact.imp_mat = impact.imp_mat.tocsr()
404408

405-
file_name = os.path.dirname(__file__) + '/test_imp_mat'
409+
file_name = os.path.join(DATA_FOLDER, 'test_imp_mat')
406410
impact.write_sparse_csr(file_name)
407411
read_imp_mat = Impact().read_sparse_csr(file_name+'.npz')
408412
for irow in range(5):

0 commit comments

Comments
 (0)