Skip to content

Commit 84b344b

Browse files
committed
Merge branch 'forecast-class' into forecast-io
2 parents 96ccc58 + e66a558 commit 84b344b

File tree

4 files changed

+214
-4
lines changed

4 files changed

+214
-4
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
@@ -318,3 +318,49 @@ def test_impact_forecast_min_mean_max(impact_forecast_stats, attr):
318318
npt.assert_array_equal(imp_fc_reduced.event_id, [0])
319319
npt.assert_array_equal(imp_fc_reduced.frequency, [1])
320320
npt.assert_array_equal(imp_fc_reduced.date, [0])
321+
322+
323+
@pytest.mark.parametrize("quantile", [0.3, 0.6, 0.8])
324+
def test_impact_forecast_quantile(impact_forecast, quantile):
325+
"""Check quantile method for ImpactForecast"""
326+
imp_fcst_quantile = impact_forecast.quantile(q=quantile)
327+
328+
# assert imp_mat
329+
npt.assert_array_equal(
330+
imp_fcst_quantile.imp_mat.toarray().squeeze(),
331+
np.quantile(impact_forecast.imp_mat.toarray(), quantile, axis=0),
332+
)
333+
# assert at_event
334+
npt.assert_array_equal(
335+
imp_fcst_quantile.at_event,
336+
np.quantile(impact_forecast.at_event, quantile, axis=0).sum(),
337+
)
338+
339+
# check that attributes where reduced correctly
340+
npt.assert_array_equal(imp_fcst_quantile.member, np.array([-1]))
341+
npt.assert_array_equal(
342+
imp_fcst_quantile.lead_time, np.array([np.timedelta64("NaT")])
343+
)
344+
npt.assert_array_equal(imp_fcst_quantile.event_id, np.array([0]))
345+
npt.assert_array_equal(
346+
imp_fcst_quantile.event_name, np.array([f"quantile_{quantile}"])
347+
)
348+
npt.assert_array_equal(imp_fcst_quantile.frequency, np.array([1]))
349+
npt.assert_array_equal(imp_fcst_quantile.date, np.array([0]))
350+
351+
352+
def test_median(impact_forecast):
353+
imp_fcst_median = impact_forecast.median()
354+
imp_fcst_quantile = impact_forecast.quantile(q=0.5)
355+
npt.assert_array_equal(
356+
imp_fcst_median.imp_mat.toarray(), imp_fcst_quantile.imp_mat.toarray()
357+
)
358+
npt.assert_array_equal(imp_fcst_median.imp_mat.toarray(), [[2.5, 2.5]])
359+
360+
# check that attributes where reduced correctly
361+
npt.assert_array_equal(imp_fcst_median.member, np.array([-1]))
362+
npt.assert_array_equal(imp_fcst_median.lead_time, np.array([np.timedelta64("NaT")]))
363+
npt.assert_array_equal(imp_fcst_median.event_id, np.array([0]))
364+
npt.assert_array_equal(imp_fcst_median.event_name, np.array(["median"]))
365+
npt.assert_array_equal(imp_fcst_median.frequency, np.array([1]))
366+
npt.assert_array_equal(imp_fcst_median.date, np.array([0]))

climada/hazard/forecast.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,6 @@ def from_xarray_raster(
330330
Coordinate reference system identifier. Defaults to "EPSG:4326"
331331
open_dataset_kws : dict, optional
332332
Keyword arguments passed to xarray.open_dataset if data is a file path
333-
334-
Returns
335-
-------
336-
HazardForecast
337333
A forecast hazard object with lead_time and member attributes populated
338334
339335
See Also
@@ -410,3 +406,60 @@ def from_xarray_raster(
410406

411407
# Convert to HazardForecast with forecast attributes
412408
return cls(**Hazard._check_and_cast_attrs(kwargs))
409+
410+
def _quantile(self, q: float, event_name: str | None = None):
411+
"""
412+
Reduce the impact matrix and at_event of a HazardForecast to the quantile value.
413+
"""
414+
red_intensity = sparse.csr_matrix(
415+
np.quantile(self.intensity.toarray(), q, axis=0)
416+
)
417+
red_fraction = sparse.csr_matrix(
418+
np.quantile(self.fraction.toarray(), q, axis=0)
419+
)
420+
if event_name is None:
421+
event_name = f"quantile_{q}"
422+
return HazardForecast(
423+
haz_type=self.haz_type,
424+
pool=self.pool,
425+
units=self.units,
426+
centroids=self.centroids,
427+
frequency_unit=self.frequency_unit,
428+
intensity=red_intensity,
429+
fraction=red_fraction,
430+
**self._reduce_attrs(event_name),
431+
)
432+
433+
def quantile(self, q: float):
434+
"""
435+
Reduce the impact matrix and at_event of a HazardForecast to the quantile value.
436+
437+
The quantile value is computed by taking the quantile of the impact matrix
438+
along the event dimension axis (axis=0) and then taking the quantile of the
439+
resulting array.
440+
441+
Parameters
442+
----------
443+
q : float
444+
The quantile to compute, between 0 and 1.
445+
446+
Returns
447+
-------
448+
HazardForecast
449+
A HazardForecast object with the quantile intensity and fraction.
450+
"""
451+
return self._quantile(q=q)
452+
453+
def median(self):
454+
"""
455+
Reduce the impact matrix and at_event of a HazardForecast to the median value.
456+
457+
The median value is computed by taking the median of the impact matrix along the
458+
event dimension axis (axis=0) and then taking the median of the resulting array.
459+
460+
Returns
461+
-------
462+
HazardForecast
463+
A HazardForecast object with the median intensity and fraction.
464+
"""
465+
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
@@ -410,3 +410,61 @@ def test_hazard_forecast_mean_min_max(haz_fc, attr):
410410
npt.assert_array_equal(haz_fcst_reduced.frequency, [1])
411411
npt.assert_array_equal(haz_fcst_reduced.date, [0])
412412
npt.assert_array_equal(haz_fcst_reduced.orig, [True])
413+
414+
415+
@pytest.mark.parametrize("quantile", [0.3, 0.6, 0.8])
416+
def test_hazard_forecast_quantile(haz_fc, quantile):
417+
"""Check quantile method for HazardForecast"""
418+
haz_fcst_quantile = haz_fc.quantile(q=quantile)
419+
420+
# assert intensity
421+
npt.assert_array_equal(
422+
haz_fcst_quantile.intensity.toarray().squeeze(),
423+
np.quantile(haz_fc.intensity.toarray(), quantile, axis=0),
424+
)
425+
# assert fraction
426+
npt.assert_array_equal(
427+
haz_fcst_quantile.fraction.toarray().squeeze(),
428+
np.quantile(haz_fc.fraction.toarray(), quantile, axis=0),
429+
)
430+
431+
# check that attributes where reduced correctly
432+
npt.assert_array_equal(
433+
haz_fcst_quantile.lead_time, np.array([np.timedelta64("NaT")])
434+
)
435+
npt.assert_array_equal(haz_fcst_quantile.member, np.array([-1]))
436+
npt.assert_array_equal(
437+
haz_fcst_quantile.event_name, np.array([f"quantile_{quantile}"])
438+
)
439+
npt.assert_array_equal(haz_fcst_quantile.event_id, np.array([0]))
440+
npt.assert_array_equal(haz_fcst_quantile.frequency, np.array([1]))
441+
npt.assert_array_equal(haz_fcst_quantile.date, np.array([0]))
442+
npt.assert_array_equal(haz_fcst_quantile.orig, np.array([True]))
443+
444+
445+
def test_median(haz_fc):
446+
haz_fcst_median = haz_fc.median()
447+
haz_fcst_quantile = haz_fc.quantile(q=0.5)
448+
npt.assert_array_equal(
449+
haz_fcst_median.intensity.todense(), haz_fcst_quantile.intensity.todense()
450+
)
451+
npt.assert_array_equal(
452+
haz_fcst_median.intensity.todense(),
453+
np.median(haz_fc.intensity.todense(), axis=0),
454+
)
455+
npt.assert_array_equal(
456+
haz_fcst_median.fraction.todense(), haz_fcst_quantile.fraction.todense()
457+
)
458+
npt.assert_array_equal(
459+
haz_fcst_median.fraction.todense(),
460+
np.median(haz_fc.fraction.todense(), axis=0),
461+
)
462+
463+
# check that attributes where reduced correctly
464+
npt.assert_array_equal(haz_fcst_median.member, np.array([-1]))
465+
npt.assert_array_equal(haz_fcst_median.lead_time, np.array([np.timedelta64("NaT")]))
466+
npt.assert_array_equal(haz_fcst_median.event_id, np.array([0]))
467+
npt.assert_array_equal(haz_fcst_median.event_name, np.array(["median"]))
468+
npt.assert_array_equal(haz_fcst_median.frequency, np.array([1]))
469+
npt.assert_array_equal(haz_fcst_median.date, np.array([0]))
470+
npt.assert_array_equal(haz_fcst_median.orig, np.array([True]))

0 commit comments

Comments
 (0)