2121
2222import logging
2323from pathlib import Path
24- from typing import Union
24+ from typing import Literal , Union
2525
2626import numpy as np
27- import scipy . sparse as sparse
27+ from scipy import sparse
2828
2929from ..util import log_level
3030from ..util .checker import size
31- from ..util .forecast import Forecast
31+ from ..util .forecast import Forecast , reduce_unique_selection
3232from .impact import Impact
3333
3434LOGGER = logging .getLogger (__name__ )
@@ -244,48 +244,83 @@ def _check_sizes(self):
244244 size (exp_len = num_entries , var = self .member , var_name = "Forecast.member" )
245245 size (exp_len = num_entries , var = self .lead_time , var_name = "Forecast.lead_time" )
246246
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
247+ def select (
248+ self ,
249+ event_ids = None ,
250+ event_names = None ,
251+ dates = None ,
252+ coord_exp = None ,
253+ reset_frequency = False ,
254+ member = None ,
255+ lead_time = None ,
256+ ):
257+ """Select entries based on the parameters and return a new instance.
258+ The selection will contain the intersection of all given parameters.
258259
259260 Parameters
260261 ----------
261- event_name : str
262- The event name given to the reduced data.
262+ member : Sequence of ints
263+ Ensemble members to select
264+ lead_time : Sequence of numpy.timedelta64
265+ Lead times to select
266+
267+ See Also
268+ --------
269+ :py:meth:`~climada.engine.impact.Impact.select`
263270 """
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 ):
271+ if member is not None or lead_time is not None :
272+ mask_member = (
273+ self .idx_member (member )
274+ if member is not None
275+ else np .full_like (self .member , True , dtype = bool )
276+ )
277+ mask_lead_time = (
278+ self .idx_lead_time (lead_time )
279+ if lead_time is not None
280+ else np .full_like (self .lead_time , True , dtype = bool )
281+ )
282+ event_id_from_forecast_mask = np .asarray (self .event_id )[
283+ (mask_member & mask_lead_time )
284+ ]
285+ event_ids = (
286+ np .intersect1d (event_ids , event_id_from_forecast_mask )
287+ if event_ids is not None
288+ else event_id_from_forecast_mask
289+ )
290+
291+ return super ().select (
292+ event_ids = event_ids ,
293+ event_names = event_names ,
294+ dates = dates ,
295+ coord_exp = coord_exp ,
296+ reset_frequency = reset_frequency ,
297+ )
298+
299+ def min (self , dim : Literal ["member" , "lead_time" ] | None = None ):
276300 """
277301 Reduce the impact matrix and at_event of an ImpactForecast to the minimum
278302 value.
279303
280304 Parameters
281305 ----------
282- None
306+ dim : "member", "lead_time", or None
307+ Dimension to reduce over. If None, reduce over all data.
283308
284309 Returns
285310 -------
286311 ImpactForecast
287312 An ImpactForecast object with the min impact matrix and at_event.
288313 """
314+ if dim is not None :
315+ rdim = self ._reduce_iter_dim (dim )
316+ return reduce_unique_selection (
317+ self ,
318+ values = getattr (self , rdim ),
319+ select = rdim ,
320+ reduce_attr = "min" ,
321+ concat_kws = {"reset_event_ids" : True },
322+ )
323+
289324 red_imp_mat = self .imp_mat .min (axis = 0 ).tocsr ()
290325 red_at_event = np .array ([red_imp_mat .sum ()])
291326 return ImpactForecast (
@@ -302,20 +337,31 @@ def min(self):
302337 ** self ._reduce_attrs ("min" ),
303338 )
304339
305- def max (self ):
340+ def max (self , dim : Literal [ "member" , "lead_time" ] | None = None ):
306341 """
307342 Reduce the impact matrix and at_event of an ImpactForecast to the maximum
308343 value.
309344
310345 Parameters
311346 ----------
312- None
347+ dim : "member", "lead_time", or None
348+ Dimension to reduce over. If None, reduce over all data.
313349
314350 Returns
315351 -------
316352 ImpactForecast
317353 An ImpactForecast object with the max impact matrix and at_event.
318354 """
355+ if dim is not None :
356+ rdim = self ._reduce_iter_dim (dim )
357+ return reduce_unique_selection (
358+ self ,
359+ values = getattr (self , rdim ),
360+ select = rdim ,
361+ reduce_attr = "max" ,
362+ concat_kws = {"reset_event_ids" : True },
363+ )
364+
319365 red_imp_mat = self .imp_mat .max (axis = 0 ).tocsr ()
320366 red_at_event = np .array ([red_imp_mat .sum ()])
321367 return ImpactForecast (
@@ -332,19 +378,30 @@ def max(self):
332378 ** self ._reduce_attrs ("max" ),
333379 )
334380
335- def mean (self ):
381+ def mean (self , dim : Literal [ "member" , "lead_time" ] | None = None ):
336382 """
337383 Reduce the impact matrix and at_event of an ImpactForecast to the mean value.
338384
339385 Parameters
340386 ----------
341- None
387+ dim : "member", "lead_time", or None
388+ Dimension to reduce over. If None, reduce over all data.
342389
343390 Returns
344391 -------
345392 ImpactForecast
346393 An ImpactForecast object with the mean impact matrix and at_event.
347394 """
395+ if dim is not None :
396+ rdim = self ._reduce_iter_dim (dim )
397+ return reduce_unique_selection (
398+ self ,
399+ values = getattr (self , rdim ),
400+ select = rdim ,
401+ reduce_attr = "mean" ,
402+ concat_kws = {"reset_event_ids" : True },
403+ )
404+
348405 red_imp_mat = sparse .csr_matrix (self .imp_mat .mean (axis = 0 ))
349406 red_at_event = np .array ([red_imp_mat .sum ()])
350407 return ImpactForecast (
@@ -361,62 +418,26 @@ def mean(self):
361418 ** self ._reduce_attrs ("mean" ),
362419 )
363420
364- def select (
421+ def _quantile (
365422 self ,
366- event_ids = None ,
367- event_names = None ,
368- dates = None ,
369- coord_exp = None ,
370- reset_frequency = False ,
371- member = None ,
372- lead_time = None ,
423+ q : float ,
424+ dim : Literal ["member" , "lead_time" ] | None = None ,
425+ event_name : str | None = None ,
373426 ):
374- """Select entries based on the parameters and return a new instance.
375- The selection will contain the intersection of all given parameters.
376-
377- Parameters
378- ----------
379- member : Sequence of ints
380- Ensemble members to select
381- lead_time : Sequence of numpy.timedelta64
382- Lead times to select
383-
384- See Also
385- --------
386- :py:meth:`~climada.engine.impact.Impact.select`
387- """
388- if member is not None or lead_time is not None :
389- mask_member = (
390- self .idx_member (member )
391- if member is not None
392- else np .full_like (self .member , True , dtype = bool )
393- )
394- mask_lead_time = (
395- self .idx_lead_time (lead_time )
396- if lead_time is not None
397- else np .full_like (self .lead_time , True , dtype = bool )
398- )
399- event_id_from_forecast_mask = np .asarray (self .event_id )[
400- (mask_member & mask_lead_time )
401- ]
402- event_ids = (
403- np .intersect1d (event_ids , event_id_from_forecast_mask )
404- if event_ids is not None
405- else event_id_from_forecast_mask
406- )
407-
408- return super ().select (
409- event_ids = event_ids ,
410- event_names = event_names ,
411- dates = dates ,
412- coord_exp = coord_exp ,
413- reset_frequency = reset_frequency ,
414- )
415-
416- def _quantile (self , q : float , event_name : str | None = None ):
417427 """
418428 Reduce the impact matrix and at_event of an ImpactForecast to the quantile value.
419429 """
430+ if dim is not None :
431+ rdim = self ._reduce_iter_dim (dim )
432+ return reduce_unique_selection (
433+ self ,
434+ values = getattr (self , rdim ),
435+ select = rdim ,
436+ reduce_attr = "quantile" ,
437+ q = q ,
438+ concat_kws = {"reset_event_ids" : True },
439+ )
440+
420441 red_imp_mat = sparse .csr_matrix (np .quantile (self .imp_mat .toarray (), q , axis = 0 ))
421442 red_at_event = np .array ([red_imp_mat .sum ()])
422443 if event_name is None :
@@ -435,33 +456,38 @@ def _quantile(self, q: float, event_name: str | None = None):
435456 ** self ._reduce_attrs (event_name ),
436457 )
437458
438- def quantile (self , q : float ):
459+ def quantile (self , q : float , dim : Literal [ "member" , "lead_time" ] | None = None ):
439460 """
440461 Reduce the impact matrix and at_event of an ImpactForecast to the quantile value.
441462
442463 Parameters
443464 ----------
444465 q : float
445466 The quantile to compute, which must be between 0 and 1.
467+ dim : "member", "lead_time", or None
468+ The dimension to reduce when computing the quantile. If ``None`` (default),
469+ this computes the centroid-wise quantile over the entire matrix.
446470
447471 Returns
448472 -------
449473 ImpactForecast
450474 An ImpactForecast object with the quantile impact matrix and at_event.
451475 """
452- return self ._quantile (q = q )
476+ return self ._quantile (q = q , dim = dim )
453477
454- def median (self ):
478+ def median (self , dim : Literal [ "member" , "lead_time" ] | None = None ):
455479 """
456480 Reduce the impact matrix and at_event of an ImpactForecast to the median value.
457481
458482 Parameters
459483 ----------
460- None
484+ dim : "member", "lead_time", or None
485+ The dimension to reduce when computing the median. If ``None`` (default),
486+ this computes the centroid-wise median over the entire matrix.
461487
462488 Returns
463489 -------
464490 ImpactForecast
465491 An ImpactForecast object with the median impact matrix and at_event.
466492 """
467- return self ._quantile (q = 0.5 , event_name = "median" )
493+ return self ._quantile (q = 0.5 , dim = dim , event_name = "median" )
0 commit comments