Skip to content

Commit 2b9b388

Browse files
luseverinChahan Kropfpeanutfun
authored
Implement mean min max for impact forecast and hazard forecast (#1187)
Add min/mean/max methods --------- Co-authored-by: luseverin <[email protected]> Co-authored-by: Chahan Kropf <[email protected]> Co-authored-by: Lukas Riedel <[email protected]>
1 parent 52edc45 commit 2b9b388

File tree

4 files changed

+291
-9
lines changed

4 files changed

+291
-9
lines changed

climada/engine/impact_forecast.py

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import logging
2323

2424
import numpy as np
25+
import scipy.sparse as sparse
2526

2627
from ..util import log_level
2728
from ..util.checker import size
@@ -185,6 +186,123 @@ def _check_sizes(self):
185186
size(exp_len=num_entries, var=self.member, var_name="Forecast.member")
186187
size(exp_len=num_entries, var=self.lead_time, var_name="Forecast.lead_time")
187188

189+
def _reduce_attrs(self, event_name: str):
190+
"""
191+
Reduce the attributes of an ImpactForecast to a single value.
192+
193+
Attributes are modified as follows:
194+
- lead_time: set to NaT
195+
- member: set to -1
196+
- event_id: set to 0
197+
- event_name: set to the name of the reduction method (default)
198+
- date: set to 0
199+
- frequency: set to 1
200+
201+
Parameters
202+
----------
203+
event_name : str
204+
The event name given to the reduced data.
205+
"""
206+
reduced_attrs = {
207+
"lead_time": np.array([np.timedelta64("NaT")]),
208+
"member": np.array([-1]),
209+
"event_id": np.array([0]),
210+
"event_name": np.array([event_name]),
211+
"date": np.array([0]),
212+
"frequency": np.array([1]),
213+
}
214+
215+
return reduced_attrs
216+
217+
def min(self):
218+
"""
219+
Reduce the impact matrix and at_event of an ImpactForecast to the minimum
220+
value.
221+
222+
Parameters
223+
----------
224+
None
225+
226+
Returns
227+
-------
228+
ImpactForecast
229+
An ImpactForecast object with the min impact matrix and at_event.
230+
"""
231+
red_imp_mat = self.imp_mat.min(axis=0).tocsr()
232+
red_at_event = np.array([red_imp_mat.sum()])
233+
return ImpactForecast(
234+
frequency_unit=self.frequency_unit,
235+
coord_exp=self.coord_exp,
236+
crs=self.crs,
237+
eai_exp=self.eai_exp,
238+
at_event=red_at_event,
239+
tot_value=self.tot_value,
240+
aai_agg=self.aai_agg,
241+
unit=self.unit,
242+
imp_mat=red_imp_mat,
243+
haz_type=self.haz_type,
244+
**self._reduce_attrs("min"),
245+
)
246+
247+
def max(self):
248+
"""
249+
Reduce the impact matrix and at_event of an ImpactForecast to the maximum
250+
value.
251+
252+
Parameters
253+
----------
254+
None
255+
256+
Returns
257+
-------
258+
ImpactForecast
259+
An ImpactForecast object with the max impact matrix and at_event.
260+
"""
261+
red_imp_mat = self.imp_mat.max(axis=0).tocsr()
262+
red_at_event = np.array([red_imp_mat.sum()])
263+
return ImpactForecast(
264+
frequency_unit=self.frequency_unit,
265+
coord_exp=self.coord_exp,
266+
crs=self.crs,
267+
eai_exp=self.eai_exp,
268+
at_event=red_at_event,
269+
tot_value=self.tot_value,
270+
aai_agg=self.aai_agg,
271+
unit=self.unit,
272+
imp_mat=red_imp_mat,
273+
haz_type=self.haz_type,
274+
**self._reduce_attrs("max"),
275+
)
276+
277+
def mean(self):
278+
"""
279+
Reduce the impact matrix and at_event of an ImpactForecast to the mean value.
280+
281+
Parameters
282+
----------
283+
None
284+
285+
Returns
286+
-------
287+
ImpactForecast
288+
An ImpactForecast object with the mean impact matrix and at_event.
289+
"""
290+
red_imp_mat = sparse.csr_matrix(self.imp_mat.mean(axis=0))
291+
red_at_event = np.array([red_imp_mat.sum()])
292+
return ImpactForecast(
293+
frequency_unit=self.frequency_unit,
294+
coord_exp=self.coord_exp,
295+
crs=self.crs,
296+
eai_exp=self.eai_exp,
297+
at_event=red_at_event,
298+
tot_value=self.tot_value,
299+
aai_agg=self.aai_agg,
300+
unit=self.unit,
301+
imp_mat=red_imp_mat,
302+
haz_type=self.haz_type,
303+
**self._reduce_attrs("mean"),
304+
)
305+
188306
def select(
189307
self,
190308
event_ids=None,
@@ -205,10 +323,6 @@ def select(
205323
lead_time : Sequence of numpy.timedelta64
206324
Lead times to select
207325
208-
Returns
209-
-------
210-
ImpactForecast
211-
212326
See Also
213327
--------
214328
:py:meth:`~climada.engine.impact.Impact.select`

climada/engine/test/test_impact_forecast.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def test_impact_forecast_concat(impact_forecast, member):
212212

213213

214214
def test_impact_forecast_blocked_methods(impact_forecast):
215-
"""Check if blocked methods raise NotImplementedError"""
215+
"""Check if ImpactForecast.exceedance_freq_curve raises NotImplementedError"""
216216
with pytest.raises(NotImplementedError):
217217
impact_forecast.local_exceedance_impact(np.array([10, 50, 100]))
218218

@@ -221,3 +221,40 @@ def test_impact_forecast_blocked_methods(impact_forecast):
221221

222222
with pytest.raises(NotImplementedError):
223223
impact_forecast.calc_freq_curve(np.array([10, 50, 100]))
224+
225+
226+
@pytest.fixture
227+
def impact_forecast_stats(impact_kwargs, lead_time, member):
228+
max_index = 4
229+
for key, val in impact_kwargs.items():
230+
if isinstance(val, (np.ndarray, list)):
231+
impact_kwargs[key] = val[:max_index]
232+
elif isinstance(val, csr_matrix):
233+
impact_kwargs[key] = val[:max_index, :]
234+
impact_kwargs["imp_mat"] = csr_matrix([[1, 0], [0, 1], [3, 2], [2, 3]])
235+
impact_kwargs["at_event"] = np.array([1, 1, 5, 5])
236+
return ImpactForecast(
237+
lead_time=lead_time[:max_index], member=member[:max_index], **impact_kwargs
238+
)
239+
240+
241+
@pytest.mark.parametrize("attr", ["min", "mean", "max"])
242+
def test_impact_forecast_min_mean_max(impact_forecast_stats, attr):
243+
"""Check mean, min, and max methods for ImpactForecast"""
244+
imp_fc_reduced = getattr(impact_forecast_stats, attr)()
245+
246+
# assert imp_mat
247+
npt.assert_array_equal(
248+
imp_fc_reduced.imp_mat.todense(),
249+
getattr(impact_forecast_stats.imp_mat.todense(), attr)(axis=0),
250+
)
251+
at_event_expected = {"min": [0], "mean": [3], "max": [6]}
252+
npt.assert_array_equal(imp_fc_reduced.at_event, at_event_expected[attr])
253+
254+
# check that attributes where reduced correctly
255+
npt.assert_array_equal(np.isnat(imp_fc_reduced.lead_time), [True])
256+
npt.assert_array_equal(imp_fc_reduced.member, [-1])
257+
npt.assert_array_equal(imp_fc_reduced.event_name, [attr])
258+
npt.assert_array_equal(imp_fc_reduced.event_id, [0])
259+
npt.assert_array_equal(imp_fc_reduced.frequency, [1])
260+
npt.assert_array_equal(imp_fc_reduced.date, [0])

climada/hazard/forecast.py

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import logging
2323

2424
import numpy as np
25+
import scipy.sparse as sparse
2526

2627
from ..util.checker import size
2728
from ..util.forecast import Forecast
@@ -105,6 +106,115 @@ def _check_sizes(self):
105106
size(exp_len=num_entries, var=self.member, var_name="Forecast.member")
106107
size(exp_len=num_entries, var=self.lead_time, var_name="Forecast.lead_time")
107108

109+
def _reduce_attrs(self, event_name: str):
110+
"""
111+
Reduce the attributes of a HazardForecast to a single value.
112+
113+
Attributes are modified as follows:
114+
- lead_time: set to NaT
115+
- member: set to -1
116+
- event_id: set to 0
117+
- event_name: set to the name of the reduction method (default)
118+
- date: set to 0
119+
- frequency: set to 1
120+
121+
Parameters
122+
----------
123+
event_name : str
124+
The event_name given to the reduced data.
125+
"""
126+
reduced_attrs = {
127+
"lead_time": np.array([np.timedelta64("NaT")]),
128+
"member": np.array([-1]),
129+
"event_id": np.array([0]),
130+
"event_name": np.array([event_name]),
131+
"date": np.array([0]),
132+
"frequency": np.array([1]),
133+
"orig": np.array([True]),
134+
}
135+
136+
return reduced_attrs
137+
138+
def min(self):
139+
"""
140+
Reduce the intensity and fraction of a HazardForecast to the minimum
141+
value.
142+
143+
Parameters
144+
----------
145+
None
146+
147+
Returns
148+
-------
149+
HazardForecast
150+
A HazardForecast object with the min intensity and fraction.
151+
"""
152+
red_intensity = self.intensity.min(axis=0).tocsr()
153+
red_fraction = self.fraction.min(axis=0).tocsr()
154+
return HazardForecast(
155+
haz_type=self.haz_type,
156+
pool=self.pool,
157+
units=self.units,
158+
centroids=self.centroids,
159+
frequency_unit=self.frequency_unit,
160+
intensity=red_intensity,
161+
fraction=red_fraction,
162+
**self._reduce_attrs("min"),
163+
)
164+
165+
def max(self):
166+
"""
167+
Reduce the intensity and fraction of a HazardForecast to the maximum
168+
value.
169+
170+
Parameters
171+
----------
172+
None
173+
174+
Returns
175+
-------
176+
HazardForecast
177+
A HazardForecast object with the min intensity and fraction.
178+
"""
179+
red_intensity = self.intensity.max(axis=0).tocsr()
180+
red_fraction = self.fraction.max(axis=0).tocsr()
181+
return HazardForecast(
182+
haz_type=self.haz_type,
183+
pool=self.pool,
184+
units=self.units,
185+
centroids=self.centroids,
186+
frequency_unit=self.frequency_unit,
187+
intensity=red_intensity,
188+
fraction=red_fraction,
189+
**self._reduce_attrs("max"),
190+
)
191+
192+
def mean(self):
193+
"""
194+
Reduce the intensity and fraction of a HazardForecast to the mean value.
195+
196+
Parameters
197+
----------
198+
None
199+
200+
Returns
201+
-------
202+
HazardForecast
203+
A HazardForecast object with the min intensity and fraction.
204+
"""
205+
red_intensity = sparse.csr_matrix(self.intensity.mean(axis=0))
206+
red_fraction = sparse.csr_matrix(self.fraction.mean(axis=0))
207+
return HazardForecast(
208+
haz_type=self.haz_type,
209+
pool=self.pool,
210+
units=self.units,
211+
centroids=self.centroids,
212+
frequency_unit=self.frequency_unit,
213+
intensity=red_intensity,
214+
fraction=red_fraction,
215+
**self._reduce_attrs("mean"),
216+
)
217+
108218
@classmethod
109219
def concat(cls, haz_list: list):
110220
"""Concatenate multiple HazardForecast instances and return a new object"""
@@ -138,10 +248,6 @@ def select(
138248
lead_time : Sequence of numpy.timedelta64
139249
Lead times to select
140250
141-
Returns
142-
-------
143-
HazardForecast
144-
145251
See Also
146252
--------
147253
:py:meth:`~climada.hazard.base.Hazard.select`

climada/hazard/test/test_forecast.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,28 @@ def test_write_read_hazard_forecast(haz_fc, tmp_path):
233233
else:
234234
# npt.assert_array_equal also works for comparing int, float or list
235235
npt.assert_array_equal(haz_fc.__dict__[key], haz_fc_read.__dict__[key])
236+
237+
238+
@pytest.mark.parametrize("attr", ["min", "mean", "max"])
239+
def test_hazard_forecast_mean_min_max(haz_fc, attr):
240+
"""Check mean, min, and max methods for ImpactForecast"""
241+
haz_fcst_reduced = getattr(haz_fc, attr)()
242+
243+
# Assert sparse matrices
244+
npt.assert_array_equal(
245+
haz_fcst_reduced.intensity.todense(),
246+
getattr(haz_fc.intensity.todense(), attr)(axis=0),
247+
)
248+
npt.assert_array_equal(
249+
haz_fcst_reduced.fraction.todense(),
250+
getattr(haz_fc.fraction.todense(), attr)(axis=0),
251+
)
252+
253+
# Check that attributes where reduced correctly
254+
npt.assert_array_equal(np.isnat(haz_fcst_reduced.lead_time), [True])
255+
npt.assert_array_equal(haz_fcst_reduced.member, [-1])
256+
npt.assert_array_equal(haz_fcst_reduced.event_name, [attr])
257+
npt.assert_array_equal(haz_fcst_reduced.event_id, [0])
258+
npt.assert_array_equal(haz_fcst_reduced.frequency, [1])
259+
npt.assert_array_equal(haz_fcst_reduced.date, [0])
260+
npt.assert_array_equal(haz_fcst_reduced.orig, [True])

0 commit comments

Comments
 (0)