Skip to content

Commit 2e028e3

Browse files
committed
Merge branch 'forecast-class' into implement_mean_min_max
2 parents 64149df + 52edc45 commit 2e028e3

File tree

5 files changed

+349
-101
lines changed

5 files changed

+349
-101
lines changed

climada/engine/impact_forecast.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,55 @@ def mean(self):
302302
haz_type=self.haz_type,
303303
**self._reduce_attrs("mean"),
304304
)
305+
306+
def select(
307+
self,
308+
event_ids=None,
309+
event_names=None,
310+
dates=None,
311+
coord_exp=None,
312+
reset_frequency=False,
313+
member=None,
314+
lead_time=None,
315+
):
316+
"""Select entries based on the parameters and return a new instance.
317+
The selection will contain the intersection of all given parameters.
318+
319+
Parameters
320+
----------
321+
member : Sequence of ints
322+
Ensemble members to select
323+
lead_time : Sequence of numpy.timedelta64
324+
Lead times to select
325+
326+
See Also
327+
--------
328+
:py:meth:`~climada.engine.impact.Impact.select`
329+
"""
330+
if member is not None or lead_time is not None:
331+
mask_member = (
332+
self.idx_member(member)
333+
if member is not None
334+
else np.full_like(self.member, True, dtype=bool)
335+
)
336+
mask_lead_time = (
337+
self.idx_lead_time(lead_time)
338+
if lead_time is not None
339+
else np.full_like(self.lead_time, True, dtype=bool)
340+
)
341+
event_id_from_forecast_mask = np.asarray(self.event_id)[
342+
(mask_member & mask_lead_time)
343+
]
344+
event_ids = (
345+
np.intersect1d(event_ids, event_id_from_forecast_mask)
346+
if event_ids is not None
347+
else event_id_from_forecast_mask
348+
)
349+
350+
return super().select(
351+
event_ids=event_ids,
352+
event_names=event_names,
353+
dates=dates,
354+
coord_exp=coord_exp,
355+
reset_frequency=reset_frequency,
356+
)

climada/engine/test/test_impact_forecast.py

Lines changed: 104 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -92,58 +92,114 @@ def test_impact_forecast_from_impact(
9292
self.assert_impact_kwargs(impact_forecast, **impact_kwargs)
9393

9494

95-
@pytest.mark.parametrize(
96-
"var, var_select",
97-
[("event_id", "event_ids"), ("event_name", "event_names"), ("date", "dates")],
98-
)
99-
def test_impact_forecast_select_events(
100-
impact_forecast, lead_time, member, impact_kwargs, var, var_select
101-
):
102-
"""Check if Impact.select works on the derived class"""
103-
select_mask = np.array([2, 1])
104-
ordered_select_mask = np.array([1, 2])
105-
if var == "date":
106-
# Date needs to be a valid delta
107-
select_mask = np.array([1, 2])
108-
ordered_select_mask = np.array([1, 2])
95+
class TestSelect:
10996

110-
var_value = np.array(impact_kwargs[var])[select_mask]
111-
# event_name is a list, convert to numpy array for indexing
112-
impact_fc = impact_forecast.select(**{var_select: var_value})
113-
# NOTE: Events keep their original order
114-
npt.assert_array_equal(
115-
impact_fc.event_id,
116-
impact_forecast.event_id[ordered_select_mask],
117-
)
118-
npt.assert_array_equal(
119-
impact_fc.event_name,
120-
np.array(impact_forecast.event_name)[ordered_select_mask],
121-
)
122-
npt.assert_array_equal(impact_fc.date, impact_forecast.date[ordered_select_mask])
123-
npt.assert_array_equal(
124-
impact_fc.frequency, impact_forecast.frequency[ordered_select_mask]
125-
)
126-
npt.assert_array_equal(impact_fc.member, member[ordered_select_mask])
127-
npt.assert_array_equal(impact_fc.lead_time, lead_time[ordered_select_mask])
128-
npt.assert_array_equal(
129-
impact_fc.imp_mat.todense(),
130-
impact_forecast.imp_mat.todense()[ordered_select_mask],
97+
@pytest.mark.parametrize(
98+
"var, var_select",
99+
[("event_id", "event_ids"), ("event_name", "event_names"), ("date", "dates")],
131100
)
101+
def test_base_class_select(
102+
self, impact_forecast, lead_time, member, impact_kwargs, var, var_select
103+
):
104+
"""Check if Impact.select works on the derived class"""
105+
select_mask = np.array([2, 1])
106+
ordered_select_mask = np.array([1, 2])
107+
if var == "date":
108+
# Date needs to be a valid delta
109+
select_mask = np.array([1, 2])
110+
ordered_select_mask = np.array([1, 2])
111+
112+
var_value = np.array(impact_kwargs[var])[select_mask]
113+
# event_name is a list, convert to numpy array for indexing
114+
impact_fc = impact_forecast.select(**{var_select: var_value})
115+
# NOTE: Events keep their original order
116+
npt.assert_array_equal(
117+
impact_fc.event_id,
118+
impact_forecast.event_id[ordered_select_mask],
119+
)
120+
npt.assert_array_equal(
121+
impact_fc.event_name,
122+
np.array(impact_forecast.event_name)[ordered_select_mask],
123+
)
124+
npt.assert_array_equal(
125+
impact_fc.date, impact_forecast.date[ordered_select_mask]
126+
)
127+
npt.assert_array_equal(
128+
impact_fc.frequency, impact_forecast.frequency[ordered_select_mask]
129+
)
130+
npt.assert_array_equal(impact_fc.member, member[ordered_select_mask])
131+
npt.assert_array_equal(impact_fc.lead_time, lead_time[ordered_select_mask])
132+
npt.assert_array_equal(
133+
impact_fc.imp_mat.todense(),
134+
impact_forecast.imp_mat.todense()[ordered_select_mask],
135+
)
136+
137+
def test_impact_forecast_select_exposure(
138+
self, impact_forecast, lead_time, member, impact_kwargs
139+
):
140+
"""Check if Impact.select works on the derived class"""
141+
exp_col = 0
142+
select_mask = np.array([exp_col])
143+
coord_exp = impact_kwargs["coord_exp"][select_mask]
144+
impact_fc = impact_forecast.select(coord_exp=coord_exp)
145+
npt.assert_array_equal(impact_fc.member, member)
146+
npt.assert_array_equal(impact_fc.lead_time, lead_time)
147+
npt.assert_array_equal(
148+
impact_fc.imp_mat.todense(), impact_forecast.imp_mat.todense()[:, exp_col]
149+
)
132150

151+
def test_derived_select_single(self, impact_forecast, lead_time, member):
152+
imp_fc_select = impact_forecast.select(member=[2, 0])
153+
idx = np.array([0, 2])
154+
npt.assert_array_equal(imp_fc_select.event_id, impact_forecast.event_id[idx])
155+
npt.assert_array_equal(imp_fc_select.member, member[idx])
156+
npt.assert_array_equal(imp_fc_select.lead_time, lead_time[idx])
133157

134-
def test_impact_forecast_select_exposure(
135-
impact_forecast, lead_time, member, impact_kwargs
136-
):
137-
"""Check if Impact.select works on the derived class"""
138-
exp_col = 0
139-
select_mask = np.array([exp_col])
140-
coord_exp = impact_kwargs["coord_exp"][select_mask]
141-
impact_fc = impact_forecast.select(coord_exp=coord_exp)
142-
npt.assert_array_equal(impact_fc.member, member)
143-
npt.assert_array_equal(impact_fc.lead_time, lead_time)
144-
npt.assert_array_equal(
145-
impact_fc.imp_mat.todense(), impact_forecast.imp_mat.todense()[:, exp_col]
146-
)
158+
imp_fc_select = impact_forecast.select(lead_time=lead_time[np.array([2, 0])])
159+
npt.assert_array_equal(imp_fc_select.event_id, impact_forecast.event_id[idx])
160+
npt.assert_array_equal(imp_fc_select.member, member[idx])
161+
npt.assert_array_equal(imp_fc_select.lead_time, lead_time[idx])
162+
163+
def test_derived_select_intersections(
164+
self, impact_forecast, lead_time, member, impact_kwargs
165+
):
166+
imp_fc_select = impact_forecast.select(event_ids=[10, 14], member=[0, 1, 2])
167+
npt.assert_array_equal(
168+
imp_fc_select.event_id, impact_forecast.event_id[np.array([0])]
169+
)
170+
171+
imp_fc_select = impact_forecast.select(
172+
event_ids=[10, 11, 13], member=[0, 1, 2], lead_time=lead_time[1:3]
173+
)
174+
npt.assert_array_equal(
175+
imp_fc_select.event_id, impact_forecast.event_id[np.array([1])]
176+
)
177+
178+
# Test "outer"
179+
impact_forecast2 = ImpactForecast(
180+
lead_time=lead_time,
181+
member=np.zeros_like(member, dtype="int"),
182+
**impact_kwargs,
183+
)
184+
imp_fc_select = impact_forecast2.select(event_ids=[10, 11, 13], member=[0])
185+
npt.assert_array_equal(imp_fc_select.event_id, [10, 11, 13])
186+
npt.assert_array_equal(imp_fc_select.member, [0, 0, 0])
187+
188+
def test_no_select(self, impact_forecast, impact_kwargs):
189+
imp_fc_select = impact_forecast.select()
190+
npt.assert_array_equal(
191+
imp_fc_select.imp_mat.todense(), impact_forecast.imp_mat.todense()
192+
)
193+
194+
num_centroids = len(impact_kwargs["coord_exp"])
195+
imp_fc_select = impact_forecast.select(event_names=["aaaaa", "foo"])
196+
assert imp_fc_select.imp_mat.shape == (0, num_centroids)
197+
imp_fc_select = impact_forecast.select(event_ids=[-1, 1002])
198+
assert imp_fc_select.imp_mat.shape == (0, num_centroids)
199+
imp_fc_select = impact_forecast.select(member=[-1])
200+
assert imp_fc_select.imp_mat.shape == (0, num_centroids)
201+
imp_fc_select = impact_forecast.select(np.timedelta64("3", "Y"))
202+
assert imp_fc_select.imp_mat.shape == (0, num_centroids)
147203

148204

149205
@pytest.mark.skip("Concat from base class does not work")

climada/hazard/forecast.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,70 @@ def mean(self):
214214
fraction=red_fraction,
215215
**self._reduce_attrs("mean"),
216216
)
217+
218+
@classmethod
219+
def concat(cls, haz_list: list):
220+
"""Concatenate multiple HazardForecast instances and return a new object"""
221+
if len(haz_list) == 0:
222+
return cls()
223+
hazard = Hazard.concat(haz_list)
224+
lead_time = np.concatenate(tuple(haz.lead_time for haz in haz_list))
225+
member = np.concatenate(tuple(haz.member for haz in haz_list))
226+
return cls.from_hazard(hazard, lead_time=lead_time, member=member)
227+
228+
def select(
229+
self,
230+
member=None,
231+
lead_time=None,
232+
event_names=None,
233+
event_id=None,
234+
date=None,
235+
orig=None,
236+
reg_id=None,
237+
extent=None,
238+
reset_frequency=False,
239+
):
240+
"""Select entries based on the parameters and return a new instance.
241+
242+
The selection will contain the intersection of all given parameters.
243+
244+
Parameters
245+
----------
246+
member : Sequence of ints
247+
Ensemble members to select
248+
lead_time : Sequence of numpy.timedelta64
249+
Lead times to select
250+
251+
See Also
252+
--------
253+
:py:meth:`~climada.hazard.base.Hazard.select`
254+
"""
255+
if member is not None or lead_time is not None:
256+
mask_member = (
257+
self.idx_member(member)
258+
if member is not None
259+
else np.full_like(self.member, True, dtype=bool)
260+
)
261+
mask_lead_time = (
262+
self.idx_lead_time(lead_time)
263+
if lead_time is not None
264+
else np.full_like(self.lead_time, True, dtype=bool)
265+
)
266+
event_id_from_forecast_mask = np.asarray(self.event_id)[
267+
(mask_member & mask_lead_time)
268+
]
269+
event_id = (
270+
np.intersect1d(event_id, event_id_from_forecast_mask)
271+
if event_id is not None
272+
else event_id_from_forecast_mask
273+
)
274+
275+
return super().select(
276+
event_names=event_names,
277+
event_id=event_id,
278+
date=date,
279+
orig=orig,
280+
reg_id=reg_id,
281+
extent=extent,
282+
reset_frequency=reset_frequency,
283+
)

0 commit comments

Comments
 (0)