Skip to content

Commit e66a558

Browse files
luseverinluseverinChahan Kropfpeanutfun
authored
Implement quantiles and median for hazard and impact forecasts (#1191)
* Add quantile and median for hazard and impact forecast * Review --------- Co-authored-by: luseverin <[email protected]> Co-authored-by: Chahan Kropf <[email protected]> Co-authored-by: Lukas Riedel <[email protected]>
1 parent 0e7857f commit e66a558

File tree

4 files changed

+214
-0
lines changed

4 files changed

+214
-0
lines changed

climada/engine/impact_forecast.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,56 @@ def select(
412412
coord_exp=coord_exp,
413413
reset_frequency=reset_frequency,
414414
)
415+
416+
def _quantile(self, q: float, event_name: str | None = None):
417+
"""
418+
Reduce the impact matrix and at_event of an ImpactForecast to the quantile value.
419+
"""
420+
red_imp_mat = sparse.csr_matrix(np.quantile(self.imp_mat.toarray(), q, axis=0))
421+
red_at_event = np.array([red_imp_mat.sum()])
422+
if event_name is None:
423+
event_name = f"quantile_{q}"
424+
return ImpactForecast(
425+
frequency_unit=self.frequency_unit,
426+
coord_exp=self.coord_exp,
427+
crs=self.crs,
428+
eai_exp=self.eai_exp,
429+
at_event=red_at_event,
430+
tot_value=self.tot_value,
431+
aai_agg=self.aai_agg,
432+
unit=self.unit,
433+
imp_mat=red_imp_mat,
434+
haz_type=self.haz_type,
435+
**self._reduce_attrs(event_name),
436+
)
437+
438+
def quantile(self, q: float):
439+
"""
440+
Reduce the impact matrix and at_event of an ImpactForecast to the quantile value.
441+
442+
Parameters
443+
----------
444+
q : float
445+
The quantile to compute, which must be between 0 and 1.
446+
447+
Returns
448+
-------
449+
ImpactForecast
450+
An ImpactForecast object with the quantile impact matrix and at_event.
451+
"""
452+
return self._quantile(q=q)
453+
454+
def median(self):
455+
"""
456+
Reduce the impact matrix and at_event of an ImpactForecast to the median value.
457+
458+
Parameters
459+
----------
460+
None
461+
462+
Returns
463+
-------
464+
ImpactForecast
465+
An ImpactForecast object with the median impact matrix and at_event.
466+
"""
467+
return self._quantile(q=0.5, event_name="median")

climada/engine/test/test_impact_forecast.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,49 @@ def test_impact_forecast_min_mean_max(impact_forecast_stats, attr):
298298
npt.assert_array_equal(imp_fc_reduced.event_id, [0])
299299
npt.assert_array_equal(imp_fc_reduced.frequency, [1])
300300
npt.assert_array_equal(imp_fc_reduced.date, [0])
301+
302+
303+
@pytest.mark.parametrize("quantile", [0.3, 0.6, 0.8])
304+
def test_impact_forecast_quantile(impact_forecast, quantile):
305+
"""Check quantile method for ImpactForecast"""
306+
imp_fcst_quantile = impact_forecast.quantile(q=quantile)
307+
308+
# assert imp_mat
309+
npt.assert_array_equal(
310+
imp_fcst_quantile.imp_mat.toarray().squeeze(),
311+
np.quantile(impact_forecast.imp_mat.toarray(), quantile, axis=0),
312+
)
313+
# assert at_event
314+
npt.assert_array_equal(
315+
imp_fcst_quantile.at_event,
316+
np.quantile(impact_forecast.at_event, quantile, axis=0).sum(),
317+
)
318+
319+
# check that attributes where reduced correctly
320+
npt.assert_array_equal(imp_fcst_quantile.member, np.array([-1]))
321+
npt.assert_array_equal(
322+
imp_fcst_quantile.lead_time, np.array([np.timedelta64("NaT")])
323+
)
324+
npt.assert_array_equal(imp_fcst_quantile.event_id, np.array([0]))
325+
npt.assert_array_equal(
326+
imp_fcst_quantile.event_name, np.array([f"quantile_{quantile}"])
327+
)
328+
npt.assert_array_equal(imp_fcst_quantile.frequency, np.array([1]))
329+
npt.assert_array_equal(imp_fcst_quantile.date, np.array([0]))
330+
331+
332+
def test_median(impact_forecast):
333+
imp_fcst_median = impact_forecast.median()
334+
imp_fcst_quantile = impact_forecast.quantile(q=0.5)
335+
npt.assert_array_equal(
336+
imp_fcst_median.imp_mat.toarray(), imp_fcst_quantile.imp_mat.toarray()
337+
)
338+
npt.assert_array_equal(imp_fcst_median.imp_mat.toarray(), [[2.5, 2.5]])
339+
340+
# check that attributes where reduced correctly
341+
npt.assert_array_equal(imp_fcst_median.member, np.array([-1]))
342+
npt.assert_array_equal(imp_fcst_median.lead_time, np.array([np.timedelta64("NaT")]))
343+
npt.assert_array_equal(imp_fcst_median.event_id, np.array([0]))
344+
npt.assert_array_equal(imp_fcst_median.event_name, np.array(["median"]))
345+
npt.assert_array_equal(imp_fcst_median.frequency, np.array([1]))
346+
npt.assert_array_equal(imp_fcst_median.date, np.array([0]))

climada/hazard/forecast.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,60 @@ def select(
281281
extent=extent,
282282
reset_frequency=reset_frequency,
283283
)
284+
285+
def _quantile(self, q: float, event_name: str | None = None):
286+
"""
287+
Reduce the impact matrix and at_event of a HazardForecast to the quantile value.
288+
"""
289+
red_intensity = sparse.csr_matrix(
290+
np.quantile(self.intensity.toarray(), q, axis=0)
291+
)
292+
red_fraction = sparse.csr_matrix(
293+
np.quantile(self.fraction.toarray(), q, axis=0)
294+
)
295+
if event_name is None:
296+
event_name = f"quantile_{q}"
297+
return HazardForecast(
298+
haz_type=self.haz_type,
299+
pool=self.pool,
300+
units=self.units,
301+
centroids=self.centroids,
302+
frequency_unit=self.frequency_unit,
303+
intensity=red_intensity,
304+
fraction=red_fraction,
305+
**self._reduce_attrs(event_name),
306+
)
307+
308+
def quantile(self, q: float):
309+
"""
310+
Reduce the impact matrix and at_event of a HazardForecast to the quantile value.
311+
312+
The quantile value is computed by taking the quantile of the impact matrix
313+
along the event dimension axis (axis=0) and then taking the quantile of the
314+
resulting array.
315+
316+
Parameters
317+
----------
318+
q : float
319+
The quantile to compute, between 0 and 1.
320+
321+
Returns
322+
-------
323+
HazardForecast
324+
A HazardForecast object with the quantile intensity and fraction.
325+
"""
326+
return self._quantile(q=q)
327+
328+
def median(self):
329+
"""
330+
Reduce the impact matrix and at_event of a HazardForecast to the median value.
331+
332+
The median value is computed by taking the median of the impact matrix along the
333+
event dimension axis (axis=0) and then taking the median of the resulting array.
334+
335+
Returns
336+
-------
337+
HazardForecast
338+
A HazardForecast object with the median intensity and fraction.
339+
"""
340+
return self._quantile(q=0.5, event_name="median")

climada/hazard/test/test_forecast.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,61 @@ def test_hazard_forecast_mean_min_max(haz_fc, attr):
258258
npt.assert_array_equal(haz_fcst_reduced.frequency, [1])
259259
npt.assert_array_equal(haz_fcst_reduced.date, [0])
260260
npt.assert_array_equal(haz_fcst_reduced.orig, [True])
261+
262+
263+
@pytest.mark.parametrize("quantile", [0.3, 0.6, 0.8])
264+
def test_hazard_forecast_quantile(haz_fc, quantile):
265+
"""Check quantile method for HazardForecast"""
266+
haz_fcst_quantile = haz_fc.quantile(q=quantile)
267+
268+
# assert intensity
269+
npt.assert_array_equal(
270+
haz_fcst_quantile.intensity.toarray().squeeze(),
271+
np.quantile(haz_fc.intensity.toarray(), quantile, axis=0),
272+
)
273+
# assert fraction
274+
npt.assert_array_equal(
275+
haz_fcst_quantile.fraction.toarray().squeeze(),
276+
np.quantile(haz_fc.fraction.toarray(), quantile, axis=0),
277+
)
278+
279+
# check that attributes where reduced correctly
280+
npt.assert_array_equal(
281+
haz_fcst_quantile.lead_time, np.array([np.timedelta64("NaT")])
282+
)
283+
npt.assert_array_equal(haz_fcst_quantile.member, np.array([-1]))
284+
npt.assert_array_equal(
285+
haz_fcst_quantile.event_name, np.array([f"quantile_{quantile}"])
286+
)
287+
npt.assert_array_equal(haz_fcst_quantile.event_id, np.array([0]))
288+
npt.assert_array_equal(haz_fcst_quantile.frequency, np.array([1]))
289+
npt.assert_array_equal(haz_fcst_quantile.date, np.array([0]))
290+
npt.assert_array_equal(haz_fcst_quantile.orig, np.array([True]))
291+
292+
293+
def test_median(haz_fc):
294+
haz_fcst_median = haz_fc.median()
295+
haz_fcst_quantile = haz_fc.quantile(q=0.5)
296+
npt.assert_array_equal(
297+
haz_fcst_median.intensity.todense(), haz_fcst_quantile.intensity.todense()
298+
)
299+
npt.assert_array_equal(
300+
haz_fcst_median.intensity.todense(),
301+
np.median(haz_fc.intensity.todense(), axis=0),
302+
)
303+
npt.assert_array_equal(
304+
haz_fcst_median.fraction.todense(), haz_fcst_quantile.fraction.todense()
305+
)
306+
npt.assert_array_equal(
307+
haz_fcst_median.fraction.todense(),
308+
np.median(haz_fc.fraction.todense(), axis=0),
309+
)
310+
311+
# check that attributes where reduced correctly
312+
npt.assert_array_equal(haz_fcst_median.member, np.array([-1]))
313+
npt.assert_array_equal(haz_fcst_median.lead_time, np.array([np.timedelta64("NaT")]))
314+
npt.assert_array_equal(haz_fcst_median.event_id, np.array([0]))
315+
npt.assert_array_equal(haz_fcst_median.event_name, np.array(["median"]))
316+
npt.assert_array_equal(haz_fcst_median.frequency, np.array([1]))
317+
npt.assert_array_equal(haz_fcst_median.date, np.array([0]))
318+
npt.assert_array_equal(haz_fcst_median.orig, np.array([True]))

0 commit comments

Comments
 (0)