2020"""
2121
2222import logging
23+ from pathlib import Path
24+ from typing import Union
2325
2426import numpy as np
27+ import scipy .sparse as sparse
2528
2629from ..util import log_level
2730from ..util .checker import size
@@ -172,6 +175,62 @@ def calc_freq_curve(self, return_per=None):
172175 LOGGER .error ("calc_freq_curve is not defined for ImpactForecast" )
173176 raise NotImplementedError ("calc_freq_curve is not defined for ImpactForecast" )
174177
178+ @classmethod
179+ def from_hdf5 (cls , file_path : Union [str , Path ]):
180+ """Create an ImpactForecast object from an H5 file.
181+
182+ This assumes a specific layout of the file. If values are not found in the
183+ expected places, they will be set to the default values for an ``Impact`` object.
184+
185+ The following H5 file structure is assumed (H5 groups are terminated with ``/``,
186+ attributes are denoted by ``.attrs/``)::
187+
188+ file.h5
189+ ├─ at_event
190+ ├─ coord_exp
191+ ├─ eai_exp
192+ ├─ event_id
193+ ├─ event_name
194+ ├─ frequency
195+ ├─ imp_mat
196+ ├─ lead_time
197+ ├─ member
198+ ├─ .attrs/
199+ │ ├─ aai_agg
200+ │ ├─ crs
201+ │ ├─ frequency_unit
202+ │ ├─ haz_type
203+ │ ├─ tot_value
204+ │ ├─ unit
205+
206+ As per the :py:func:`climada.engine.impact.Impact.__init__`, any of these entries
207+ is optional. If it is not found, the default value will be used when constructing
208+ the Impact.
209+
210+ The impact matrix ``imp_mat`` can either be an H5 dataset, in which case it is
211+ interpreted as dense representation of the matrix, or an H5 group, in which case
212+ the group is expected to contain the following data for instantiating a
213+ `scipy.sparse.csr_matrix <https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html>`_::
214+
215+ imp_mat/
216+ ├─ data
217+ ├─ indices
218+ ├─ indptr
219+ ├─ .attrs/
220+ │ ├─ shape
221+
222+ Parameters
223+ ----------
224+ file_path : str or Path
225+ The file path of the file to read.
226+
227+ Returns
228+ -------
229+ imp : ImpactForecast
230+ ImpactForecast with data from the given file
231+ """
232+ return super ().from_hdf5 (file_path , add_array_attrs = {"member" , "lead_time" })
233+
175234 def _check_sizes (self ):
176235 """Check sizes of forecast data vs. impact data.
177236
@@ -185,6 +244,123 @@ def _check_sizes(self):
185244 size (exp_len = num_entries , var = self .member , var_name = "Forecast.member" )
186245 size (exp_len = num_entries , var = self .lead_time , var_name = "Forecast.lead_time" )
187246
247+ def _reduce_attrs (self , event_name : str ):
248+ """
249+ Reduce the attributes of an ImpactForecast to a single value.
250+
251+ Attributes are modified as follows:
252+ - lead_time: set to NaT
253+ - member: set to -1
254+ - event_id: set to 0
255+ - event_name: set to the name of the reduction method (default)
256+ - date: set to 0
257+ - frequency: set to 1
258+
259+ Parameters
260+ ----------
261+ event_name : str
262+ The event name given to the reduced data.
263+ """
264+ reduced_attrs = {
265+ "lead_time" : np .array ([np .timedelta64 ("NaT" )]),
266+ "member" : np .array ([- 1 ]),
267+ "event_id" : np .array ([0 ]),
268+ "event_name" : np .array ([event_name ]),
269+ "date" : np .array ([0 ]),
270+ "frequency" : np .array ([1 ]),
271+ }
272+
273+ return reduced_attrs
274+
275+ def min (self ):
276+ """
277+ Reduce the impact matrix and at_event of an ImpactForecast to the minimum
278+ value.
279+
280+ Parameters
281+ ----------
282+ None
283+
284+ Returns
285+ -------
286+ ImpactForecast
287+ An ImpactForecast object with the min impact matrix and at_event.
288+ """
289+ red_imp_mat = self .imp_mat .min (axis = 0 ).tocsr ()
290+ red_at_event = np .array ([red_imp_mat .sum ()])
291+ return ImpactForecast (
292+ frequency_unit = self .frequency_unit ,
293+ coord_exp = self .coord_exp ,
294+ crs = self .crs ,
295+ eai_exp = self .eai_exp ,
296+ at_event = red_at_event ,
297+ tot_value = self .tot_value ,
298+ aai_agg = self .aai_agg ,
299+ unit = self .unit ,
300+ imp_mat = red_imp_mat ,
301+ haz_type = self .haz_type ,
302+ ** self ._reduce_attrs ("min" ),
303+ )
304+
305+ def max (self ):
306+ """
307+ Reduce the impact matrix and at_event of an ImpactForecast to the maximum
308+ value.
309+
310+ Parameters
311+ ----------
312+ None
313+
314+ Returns
315+ -------
316+ ImpactForecast
317+ An ImpactForecast object with the max impact matrix and at_event.
318+ """
319+ red_imp_mat = self .imp_mat .max (axis = 0 ).tocsr ()
320+ red_at_event = np .array ([red_imp_mat .sum ()])
321+ return ImpactForecast (
322+ frequency_unit = self .frequency_unit ,
323+ coord_exp = self .coord_exp ,
324+ crs = self .crs ,
325+ eai_exp = self .eai_exp ,
326+ at_event = red_at_event ,
327+ tot_value = self .tot_value ,
328+ aai_agg = self .aai_agg ,
329+ unit = self .unit ,
330+ imp_mat = red_imp_mat ,
331+ haz_type = self .haz_type ,
332+ ** self ._reduce_attrs ("max" ),
333+ )
334+
335+ def mean (self ):
336+ """
337+ Reduce the impact matrix and at_event of an ImpactForecast to the mean value.
338+
339+ Parameters
340+ ----------
341+ None
342+
343+ Returns
344+ -------
345+ ImpactForecast
346+ An ImpactForecast object with the mean impact matrix and at_event.
347+ """
348+ red_imp_mat = sparse .csr_matrix (self .imp_mat .mean (axis = 0 ))
349+ red_at_event = np .array ([red_imp_mat .sum ()])
350+ return ImpactForecast (
351+ frequency_unit = self .frequency_unit ,
352+ coord_exp = self .coord_exp ,
353+ crs = self .crs ,
354+ eai_exp = self .eai_exp ,
355+ at_event = red_at_event ,
356+ tot_value = self .tot_value ,
357+ aai_agg = self .aai_agg ,
358+ unit = self .unit ,
359+ imp_mat = red_imp_mat ,
360+ haz_type = self .haz_type ,
361+ ** self ._reduce_attrs ("mean" ),
362+ )
363+
188364 def select (
189365 self ,
190366 event_ids = None ,
@@ -205,10 +381,6 @@ def select(
205381 lead_time : Sequence of numpy.timedelta64
206382 Lead times to select
207383
208- Returns
209- -------
210- ImpactForecast
211-
212384 See Also
213385 --------
214386 :py:meth:`~climada.engine.impact.Impact.select`
@@ -240,3 +412,56 @@ def select(
240412 coord_exp = coord_exp ,
241413 reset_frequency = reset_frequency ,
242414 )
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" )
0 commit comments