Skip to content

Commit d6897ae

Browse files
Tag-Refactoring (#736)
* move tag module to climada.util * dismiss hazard.tag, use util.tag instead plus the new hazard attribute haz_type * entity.tag needed for backwards compatibility * hazard: remove haz_type property (it's an attrubute now) * impact: adapt reading and writing methods to changed attribute/tag haz_type * collateral fix: replace Impact().calc() by ImpactCalc().impact() * hazard.write_hdf5 adapt to moved haz_type attribute * Tag: make description and file lists (always) * exposures: tag.description is a list now * Tag Refactorfing: adapt entity tests, fix some read methods * Tags: fix tests and initialization * test: adapt to changed Tag * impact hdf5: adapt to changed Tag * hazard hdf5: adapt to changed Tag * test_tag: adapt to changed Tag * trop_cyclone: adapt to changed Tag * lagacy tag: remove unnecessary pass * tag: skip unnecessary list comprehension * stop using hazard.tag * impact csv: adapt to changed Tag * entity.tag add module doc string * fix exposures plot title and litpop integ test * climada.test.test_hazard: re-adapt tag changes * cleaning up * add deprecation warnings * changelog: tag refactoring * no message * util.tag: fix direct attribute instantiation
1 parent 0f904d2 commit d6897ae

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+437
-339
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ Code freeze date: YYYY-MM-DD
99
### Dependency Updates
1010

1111
Added:
12+
1213
- `pytest` [#726](https://github.com/CLIMADA-project/climada_python/pull/726)
1314
- `pytest-cov` [#726](https://github.com/CLIMADA-project/climada_python/pull/726)
1415
- `pytest-subtests` [#726](https://github.com/CLIMADA-project/climada_python/pull/726)
1516

1617
Changed:
1718

1819
Removed:
20+
1921
- `nbsphinx` [#712](https://github.com/CLIMADA-project/climada_python/pull/712)
2022
- `pandoc` [#712](https://github.com/CLIMADA-project/climada_python/pull/712)
2123

@@ -48,6 +50,7 @@ Removed:
4850
- Use `pytest` for executing tests [#726](https://github.com/CLIMADA-project/climada_python/pull/726)
4951
- Users can opt-out of the climada specific logging definitions and freely configure logging to their will, by setting the config value `logging.managed` to `false`. [#724](https://github.com/CLIMADA-project/climada_python/pull/724)
5052
- Add option to read additional variables from IBTrACS when using `TCTracks.from_ibtracs_netcdf` [#728](https://github.com/CLIMADA-project/climada_python/pull/728)
53+
- The `haz_type` attribute has been moved from `climada.hazard.tag.Tag` to `climada.hazard.Hazard` itself. [#736](https://github.com/CLIMADA-project/climada_python/pull/736)
5154

5255
### Fixed
5356

@@ -58,6 +61,7 @@ Removed:
5861

5962
- `Centroids.from_geodataframe` and `Centroids.from_pix_bounds` [#721](https://github.com/CLIMADA-project/climada_python/pull/721)
6063
- `Impact.tot_value`: Use `Exposures.affected_total_value` to compute the total value affected by a hazard intensity above a custom threshold [#702](https://github.com/CLIMADA-project/climada_python/pull/702)
64+
- `climada.hazard.tag.Tag` and `climada.entity.tag.Tag`. [#736](https://github.com/CLIMADA-project/climada_python/pull/736). They were unified into `climada.util.tag.Tag`. Note: the latter is to be deprecated and removed in a future version as well.
6165

6266
### Removed
6367

climada/engine/calibration_opt.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def calib_all(hazard, exposure, impf_name_or_instance, param_full_dict,
331331

332332
# prepare hazard and exposure
333333
region_ids = list(np.unique(exposure.region_id))
334-
hazard_type = hazard.tag.haz_type
334+
hazard_type = hazard.haz_type
335335
exposure.assign_centroids(hazard)
336336
# prepare impact data
337337
if isinstance(impact_data_source, pd.DataFrame):
@@ -397,7 +397,7 @@ def calib_optimize(hazard, exposure, impf_name_or_instance, param_dict,
397397

398398
# prepare hazard and exposure
399399
region_ids = list(np.unique(exposure.region_id))
400-
hazard_type = hazard.tag.haz_type
400+
hazard_type = hazard.haz_type
401401
exposure.assign_centroids(hazard)
402402
# prepare impact data
403403
if isinstance(impact_data_source, pd.DataFrame):

climada/engine/cost_benefit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def calc(self, hazard, entity, haz_future=None, ent_future=None, future_year=Non
220220
self.unit = entity.exposures.value_unit
221221

222222
# save measure colors
223-
for meas in entity.measures.get_measure(hazard.tag.haz_type):
223+
for meas in entity.measures.get_measure(hazard.haz_type):
224224
self.color_rgb[meas.name] = meas.color_rgb
225225
self.color_rgb[NO_MEASURE] = colors.to_rgb('deepskyblue')
226226

@@ -803,7 +803,7 @@ def _calc_impact_measures(self, hazard, exposures, meas_set, imp_fun_set,
803803
impact_meas[NO_MEASURE]['impact'] = imp_tmp
804804

805805
# compute impact for each measure
806-
for measure in meas_set.get_measure(hazard.tag.haz_type):
806+
for measure in meas_set.get_measure(hazard.haz_type):
807807
LOGGER.debug('%s impact of measure %s.', when, measure.name)
808808
imp_tmp, risk_transf = measure.calc_impact(exposures, imp_fun_set, hazard,
809809
assign_centroids=False)

climada/engine/forecast.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def haz_summary_str(self, run_datetime=None):
246246
run_datetime = self.run_datetime[0]
247247
haz_ind = np.argwhere(np.isin(self.run_datetime, run_datetime))[0][0]
248248
return (
249-
self.hazard[haz_ind].tag.haz_type
249+
self.hazard[haz_ind].haz_type
250250
+ "_"
251251
+ self.haz_model
252252
+ "_run"

climada/engine/impact.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@
4444
from pyproj import CRS as pyprojCRS
4545
from rasterio.crs import CRS as rasterioCRS # pylint: disable=no-name-in-module
4646

47-
from climada.entity import Exposures, Tag
48-
from climada.hazard import Tag as TagHaz
49-
import climada.util.plot as u_plot
47+
from climada.entity import Exposures
5048
from climada import CONFIG
5149
from climada.util.constants import DEF_CRS, CMAP_IMPACT, DEF_FREQ_UNIT
5250
import climada.util.coordinates as u_coord
5351
import climada.util.dates_times as u_dt
52+
import climada.util.plot as u_plot
5453
from climada.util.select import get_attributes_with_matching_dimension
54+
from climada.util.tag import Tag
5555

5656
LOGGER = logging.getLogger(__name__)
5757

@@ -61,9 +61,6 @@ class Impact():
6161
6262
Attributes
6363
----------
64-
tag : dict
65-
dictionary of tags of exposures, impact functions set and
66-
hazard: {'exp': Tag(), 'impf_set': Tag(), 'haz': TagHaz()}
6764
event_id : np.array
6865
id (>0) of each hazard event
6966
event_name : list
@@ -91,6 +88,11 @@ class Impact():
9188
imp_mat : sparse.csr_matrix
9289
matrix num_events x num_exp with impacts.
9390
only filled if save_mat is True in calc()
91+
tag : dict
92+
dictionary of tags of exposures, impact functions set and
93+
hazard: {'exp': Tag(), 'impf_set': Tag(), 'haz': Tag()}
94+
haz_type : str
95+
the hazard type of the hazard
9496
"""
9597

9698
def __init__(self,
@@ -107,7 +109,8 @@ def __init__(self,
107109
aai_agg=0,
108110
unit='',
109111
imp_mat=None,
110-
tag=None):
112+
tag=None,
113+
haz_type=''):
111114
"""
112115
Init Impact object
113116
@@ -144,9 +147,12 @@ def __init__(self,
144147
matrix num_events x num_exp with impacts.
145148
tag : dict, optional
146149
dictionary of tags of exposures, impact functions set and
147-
hazard: {'exp': Tag(), 'impf_set': Tag(), 'haz': TagHaz()}
150+
hazard: {'exp': Tag(), 'impf_set': Tag(), 'haz': Tag()}
151+
haz_type : str, optional
152+
the hazard type
148153
"""
149154

155+
self.haz_type = haz_type
150156
self.tag = tag or {}
151157
self.event_id = np.array([], int) if event_id is None else event_id
152158
self.event_name = [] if event_name is None else event_name
@@ -201,7 +207,7 @@ def calc(self, exposures, impact_funcs, hazard, save_mat=False, assign_centroids
201207
"""
202208
LOGGER.warning("The use of Impact().calc() is deprecated."
203209
" Use ImpactCalc().impact() instead.")
204-
from climada.engine.impact_calc import ImpactCalc
210+
from climada.engine.impact_calc import ImpactCalc # pylint: disable=import-outside-toplevel
205211
impcalc = ImpactCalc(exposures, impact_funcs, hazard)
206212
self.__dict__ = impcalc.impact(
207213
save_mat=save_mat,
@@ -257,7 +263,8 @@ def from_eih(cls, exposures, impfset, hazard,
257263
tag = {'exp': exposures.tag,
258264
'impf_set': impfset.tag,
259265
'haz': hazard.tag
260-
}
266+
},
267+
haz_type = hazard.haz_type,
261268
)
262269

263270
@property
@@ -882,7 +889,7 @@ def write_csv(self, file_name):
882889
"unit", "tot_value", "aai_agg", "event_id",
883890
"event_name", "event_date", "event_frequency", "frequency_unit",
884891
"at_event", "eai_exp", "exp_lat", "exp_lon", "exp_crs"])
885-
csv_data = [[[self.tag['haz'].haz_type], [self.tag['haz'].file_name],
892+
csv_data = [[[self.haz_type], [self.tag['haz'].file_name],
886893
[self.tag['haz'].description]],
887894
[[self.tag['exp'].file_name], [self.tag['exp'].description]],
888895
[[self.tag['impf_set'].file_name], [self.tag['impf_set'].description]],
@@ -919,7 +926,7 @@ def write_col(i_col, imp_ws, xls_data):
919926
"at_event", "eai_exp", "exp_lat", "exp_lon", "exp_crs"]
920927
for icol, head_dat in enumerate(header):
921928
imp_ws.write(0, icol, head_dat)
922-
data = [self.tag['haz'].haz_type, str(self.tag['haz'].file_name),
929+
data = [str(self.haz_type), str(self.tag['haz'].file_name),
923930
str(self.tag['haz'].description)]
924931
write_col(0, imp_ws, data)
925932
data = [str(self.tag['exp'].file_name), str(self.tag['exp'].description)]
@@ -1018,7 +1025,8 @@ def write_dict(group, name, value):
10181025

10191026
def write_tag(group, name, value):
10201027
"""Write a tag object using the dict writer"""
1021-
write_dict(group, name, value.__dict__)
1028+
group = group.create_group(name) # name is 'exp', 'haz', 'impf_set'
1029+
value.to_hdf5(group) # value is a Tag
10221030

10231031
def _write_csr_dense(group, name, value):
10241032
"""Write a CSR Matrix in dense format"""
@@ -1045,7 +1053,6 @@ def write_csr(group, name, value):
10451053
type_writers = {
10461054
str: write_attribute,
10471055
Tag: write_tag,
1048-
TagHaz: write_tag,
10491056
dict: write_dict,
10501057
sparse.csr_matrix: write_csr,
10511058
Collection: write_dataset,
@@ -1100,7 +1107,7 @@ def from_csv(cls, file_name):
11001107
# pylint: disable=no-member
11011108
LOGGER.info('Reading %s', file_name)
11021109
imp_df = pd.read_csv(file_name)
1103-
imp = cls()
1110+
imp = cls(haz_type=str(imp_df.tag_hazard[0]))
11041111
imp.unit = imp_df.unit[0]
11051112
imp.tot_value = imp_df.tot_value[0]
11061113
imp.aai_agg = imp_df.aai_agg[0]
@@ -1121,13 +1128,12 @@ def from_csv(cls, file_name):
11211128
imp.crs = u_coord.to_crs_user_input(imp_df.exp_crs.values[0])
11221129
except AttributeError:
11231130
imp.crs = DEF_CRS
1124-
imp.tag['haz'] = TagHaz(str(imp_df.tag_hazard[0]),
1125-
str(imp_df.tag_hazard[1]),
1126-
str(imp_df.tag_hazard[2]))
1131+
imp.tag['haz'] = Tag(str(imp_df.tag_hazard[1]),
1132+
str(imp_df.tag_hazard[2]))
11271133
imp.tag['exp'] = Tag(str(imp_df.tag_exposure[0]),
1128-
str(imp_df.tag_exposure[1]))
1134+
str(imp_df.tag_exposure[1]))
11291135
imp.tag['impf_set'] = Tag(str(imp_df.tag_impact_func[0]),
1130-
str(imp_df.tag_impact_func[1]))
1136+
str(imp_df.tag_impact_func[1]))
11311137
return imp
11321138

11331139
def read_csv(self, *args, **kwargs):
@@ -1152,9 +1158,8 @@ def from_excel(cls, file_name):
11521158
"""
11531159
LOGGER.info('Reading %s', file_name)
11541160
dfr = pd.read_excel(file_name)
1155-
imp =cls()
1156-
imp.tag['haz'] = TagHaz(
1157-
haz_type = dfr['tag_hazard'][0],
1161+
imp = cls(haz_type=str(dfr['tag_hazard'][0]))
1162+
imp.tag['haz'] = Tag(
11581163
file_name = dfr['tag_hazard'][1],
11591164
description = dfr['tag_hazard'][2])
11601165
imp.tag['exp'] = Tag()
@@ -1278,7 +1283,7 @@ def from_hdf5(cls, file_path: Union[str, Path]):
12781283

12791284
# Scalar attributes
12801285
scalar_attrs = set(
1281-
("crs", "tot_value", "unit", "aai_agg", "frequency_unit")
1286+
("crs", "tot_value", "unit", "aai_agg", "frequency_unit", "haz_type")
12821287
).intersection(file.attrs.keys())
12831288
kwargs.update({attr: file.attrs[attr] for attr in scalar_attrs})
12841289

@@ -1297,16 +1302,11 @@ def from_hdf5(cls, file_path: Union[str, Path]):
12971302

12981303
# Tags
12991304
if "tag" in file:
1300-
tag_kwargs = dict()
13011305
tag_group = file["tag"]
1302-
subtags = set(("exp", "impf_set")).intersection(tag_group.keys())
1303-
tag_kwargs.update({st: Tag(**tag_group[st].attrs) for st in subtags})
1306+
# the tag group has tags for 'exp', 'haz' and 'impf_set'
1307+
tag_kwargs = {tag: Tag.from_hdf5(tag_group[tag]) for tag in tag_group.keys()}
13041308

1305-
# Special handling for hazard because it has another tag type
1306-
if "haz" in tag_group:
1307-
tag_kwargs["haz"] = TagHaz(**tag_group["haz"].attrs)
13081309
kwargs["tag"] = tag_kwargs
1309-
13101310
# Create the impact object
13111311
return cls(**kwargs)
13121312

@@ -1351,6 +1351,8 @@ def video_direct_impact(exp, impf_set, haz_list, file_name='',
13511351
-------
13521352
list of Impact
13531353
"""
1354+
from climada.engine.impact_calc import ImpactCalc # pylint: disable=import-outside-toplevel
1355+
13541356
if args_exp is None:
13551357
args_exp = dict()
13561358
if args_imp is None:
@@ -1361,8 +1363,7 @@ def video_direct_impact(exp, impf_set, haz_list, file_name='',
13611363
# assign centroids once for all
13621364
exp.assign_centroids(haz_list[0])
13631365
for i_time, _ in enumerate(haz_list):
1364-
imp_tmp = Impact()
1365-
imp_tmp.calc(exp, impf_set, haz_list[i_time], assign_centroids=False)
1366+
imp_tmp = ImpactCalc(exp, impf_set, haz_list[i_time]).impact(assign_centroids=False)
13661367
imp_arr = np.maximum(imp_arr, imp_tmp.eai_exp)
13671368
# remove not impacted exposures
13681369
save_exp = imp_arr > imp_thresh
@@ -1806,6 +1807,7 @@ def stack_attribute(attr_name: str) -> np.ndarray:
18061807
aai_agg=np.nansum([imp.aai_agg for imp in imp_list]),
18071808
imp_mat=imp_mat,
18081809
tag=first_imp.tag,
1810+
haz_type=first_imp.haz_type,
18091811
frequency_unit=first_imp.frequency_unit,
18101812
**kwargs,
18111813
)
@@ -1850,7 +1852,7 @@ class ImpactFreqCurve():
18501852

18511853
tag : dict = field(default_factory=dict)
18521854
"""dictionary of tags of exposures, impact functions set and
1853-
hazard: {'exp': Tag(), 'impf_set': Tag(), 'haz': TagHaz()}"""
1855+
hazard: {'exp': Tag(), 'impf_set': Tag(), 'haz': Tag()}"""
18541856

18551857
return_per : np.array = np.array([])
18561858
"""return period"""

climada/engine/impact_calc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def minimal_exp_gdf(self, impf_col, assign_centroids, ignore_cover, ignore_deduc
231231
self.exposures.assign_centroids(self.hazard, overwrite=True)
232232
elif self.hazard.centr_exp_col not in self.exposures.gdf.columns:
233233
raise ValueError("'assign_centroids' is set to 'False' but no centroids are assigned"
234-
f" for the given hazard type ({self.hazard.tag.haz_type})."
234+
f" for the given hazard type ({self.hazard.haz_type})."
235235
" Run 'exposures.assign_centroids()' beforehand or set"
236236
" 'assign_centroids' to 'True'")
237237
mask = (

climada/engine/impact_data.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
from climada.util.constants import DEF_CRS
3131
import climada.util.coordinates as u_coord
3232
from climada.engine import Impact
33-
from climada.entity.tag import Tag
34-
from climada.hazard.tag import Tag as TagHaz
33+
from climada.util.tag import Tag
3534

3635
LOGGER = logging.getLogger(__name__)
3736

@@ -897,26 +896,26 @@ def emdat_impact_event(emdat_file_csv, countries=None, hazard=None, year_range=N
897896

898897

899898
def emdat_to_impact(emdat_file_csv, hazard_type_climada, year_range=None, countries=None,
900-
hazard_type_emdat=None,
901-
reference_year=None, imp_str="Total Damages"):
899+
hazard_type_emdat=None, reference_year=None, imp_str="Total Damages"):
902900
"""function to load EM-DAT data return impact per event
903901
904902
Parameters
905903
----------
906-
emdat_file_csv : str or pd.DataFrame
904+
emdat_file_csv : str or pd.DataFrame
907905
Either string with full path to CSV-file or
908906
pandas.DataFrame loaded from EM-DAT CSV
909907
countries : list of str
910908
country ISO3-codes or names, e.g. ['JAM', 'CUB'].
911909
default: countries=None for all countries
912-
hazard_type_climada : list or str
910+
hazard_type_climada : str
911+
CLIMADA hazard type abbreviations, e.g. TC, BF, etc.
912+
hazard_type_emdat : list or str
913913
List of Disaster (sub-)type accordung EMDAT terminology, i.e.:
914914
Animal accident, Drought, Earthquake, Epidemic, Extreme temperature,
915915
Flood, Fog, Impact, Insect infestation, Landslide, Mass movement (dry),
916916
Storm, Volcanic activity, Wildfire;
917917
Coastal Flooding, Convective Storm, Riverine Flood, Tropical cyclone,
918-
Tsunami, etc.;
919-
OR CLIMADA hazard type abbreviations, e.g. TC, BF, etc.
918+
Tsunami, etc.
920919
year_range : list or tuple
921920
Year range to be extracted, e.g. (2000, 2015);
922921
(only min and max are considered)
@@ -955,12 +954,11 @@ def emdat_to_impact(emdat_file_csv, hazard_type_climada, year_range=None, countr
955954
if reference_year == 0:
956955
reference_year = None
957956
# Inititate Impact-instance:
958-
impact_instance = Impact()
957+
impact_instance = Impact(haz_type=hazard_type_climada)
959958

960959
impact_instance.tag = dict()
961-
impact_instance.tag['haz'] = TagHaz(haz_type=hazard_type_climada,
962-
file_name=emdat_file_csv,
963-
description='EM-DAT impact, direct import')
960+
impact_instance.tag['haz'] = Tag(file_name=emdat_file_csv,
961+
description='EM-DAT impact, direct import')
964962
impact_instance.tag['exp'] = Tag(file_name=emdat_file_csv,
965963
description='EM-DAT impact, direct import')
966964
impact_instance.tag['impf_set'] = Tag(file_name=None, description=None)

0 commit comments

Comments
 (0)