Skip to content

Commit 6443876

Browse files
committed
updates from feature/risk_trajectory
1 parent b8b4881 commit 6443876

File tree

1 file changed

+364
-1
lines changed

1 file changed

+364
-1
lines changed

climada/test/test_trajectories.py

Lines changed: 364 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
2020
"""
2121

22+
import copy
23+
from itertools import groupby
2224
from unittest import TestCase
2325

26+
import geopandas as gpd
2427
import numpy as np
2528
import pandas as pd
2629

@@ -33,15 +36,26 @@
3336
reusable_minimal_impfset,
3437
reusable_snapshot,
3538
)
36-
from climada.trajectories import StaticRiskTrajectory
39+
from climada.trajectories import InterpolatedRiskTrajectory, StaticRiskTrajectory
3740
from climada.trajectories.constants import (
3841
AAI_METRIC_NAME,
42+
AAI_PER_GROUP_METRIC_NAME,
43+
CONTRIBUTION_BASE_RISK_NAME,
44+
CONTRIBUTION_EXPOSURE_NAME,
45+
CONTRIBUTION_HAZARD_NAME,
46+
CONTRIBUTION_INTERACTION_TERM_NAME,
47+
CONTRIBUTION_VULNERABILITY_NAME,
48+
COORD_ID_COL_NAME,
3949
DATE_COL_NAME,
50+
EAI_METRIC_NAME,
4051
GROUP_COL_NAME,
4152
MEASURE_COL_NAME,
4253
METRIC_COL_NAME,
4354
NO_MEASURE_VALUE,
55+
PERIOD_COL_NAME,
56+
RETURN_PERIOD_METRIC_NAME,
4457
RISK_COL_NAME,
58+
RP_VALUE_PREFIX,
4559
UNIT_COL_NAME,
4660
)
4761
from climada.trajectories.snapshot import Snapshot
@@ -276,3 +290,352 @@ def test_static_trajectory_risk_disc_rate(self):
276290
check_dtype=False,
277291
check_categorical=False,
278292
)
293+
294+
295+
class TestInterpolatedTrajectory(TestCase):
296+
PRESENT_DATE = 2020
297+
HAZ_INCREASE_INTENSITY_FACTOR = 2
298+
EXP_INCREASE_VALUE_FACTOR = 6
299+
FUTURE_DATE = 2022
300+
301+
def setUp(self) -> None:
302+
self.base_snapshot = reusable_snapshot(date=self.PRESENT_DATE)
303+
self.future_snapshot = reusable_snapshot(
304+
hazard_intensity_increase_factor=self.HAZ_INCREASE_INTENSITY_FACTOR,
305+
exposure_value_increase_factor=self.EXP_INCREASE_VALUE_FACTOR,
306+
date=self.FUTURE_DATE,
307+
)
308+
309+
self.expected_base_imp = ImpactCalc(
310+
**self.base_snapshot.impact_calc_data
311+
).impact()
312+
self.expected_future_imp = ImpactCalc(
313+
**self.future_snapshot.impact_calc_data
314+
).impact()
315+
# self.group_vector = self.base_snapshot.exposure.gdf[GROUP_ID_COL_NAME]
316+
self.expected_base_return_period_impacts = {
317+
rp: imp
318+
for rp, imp in zip(
319+
self.expected_base_imp.calc_freq_curve(DEFAULT_RP).return_per,
320+
self.expected_base_imp.calc_freq_curve(DEFAULT_RP).impact,
321+
)
322+
}
323+
self.expected_future_return_period_impacts = {
324+
rp: imp
325+
for rp, imp in zip(
326+
self.expected_future_imp.calc_freq_curve(DEFAULT_RP).return_per,
327+
self.expected_future_imp.calc_freq_curve(DEFAULT_RP).impact,
328+
)
329+
}
330+
331+
# fmt: off
332+
self.expected_interp_metrics = pd.DataFrame.from_dict(
333+
{'index': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
334+
'columns': [DATE_COL_NAME, GROUP_COL_NAME, MEASURE_COL_NAME, METRIC_COL_NAME, UNIT_COL_NAME, RISK_COL_NAME],
335+
'data': [[ pd.Period(2020), 'All',NO_MEASURE_VALUE, 'aai', 'USD', 20.0],
336+
[ pd.Period(2021), 'All',NO_MEASURE_VALUE, 'aai', 'USD', 105.0], # This should indeed not be 240+20 / 2 (because we interpolate each contributor separately)
337+
[ pd.Period(2022), 'All',NO_MEASURE_VALUE, 'aai', 'USD', 240.0],
338+
[ pd.Period(2020), 'All',NO_MEASURE_VALUE, 'rp_20', 'USD', 0.0],
339+
[ pd.Period(2021), 'All',NO_MEASURE_VALUE, 'rp_20', 'USD', 0.0],
340+
[ pd.Period(2022), 'All',NO_MEASURE_VALUE, 'rp_20', 'USD', 0.0],
341+
[ pd.Period(2020), 'All',NO_MEASURE_VALUE, 'rp_50', 'USD', 500.0],
342+
[ pd.Period(2021), 'All',NO_MEASURE_VALUE, 'rp_50', 'USD', 2625.0],
343+
[ pd.Period(2022), 'All',NO_MEASURE_VALUE, 'rp_50', 'USD', 6000.0],
344+
[ pd.Period(2020), 'All',NO_MEASURE_VALUE, 'rp_100', 'USD', 1500.0],
345+
[ pd.Period(2021), 'All',NO_MEASURE_VALUE, 'rp_100', 'USD', 7875.0],
346+
[ pd.Period(2022), 'All',NO_MEASURE_VALUE, 'rp_100', 'USD', 18000.0]],
347+
'index_names': [None],
348+
'column_names': [None]},
349+
orient="tight"
350+
)
351+
352+
self.expected_period_metrics = pd.DataFrame.from_dict(
353+
{'index': [0, 1, 2, 3],
354+
'columns': [PERIOD_COL_NAME, GROUP_COL_NAME, MEASURE_COL_NAME, METRIC_COL_NAME, UNIT_COL_NAME, RISK_COL_NAME],
355+
'data': [[f"{self.PRESENT_DATE} to {self.FUTURE_DATE}", 'All', NO_MEASURE_VALUE, 'aai', 'USD', 365.0/3],
356+
[f"{self.PRESENT_DATE} to {self.FUTURE_DATE}", 'All', NO_MEASURE_VALUE, 'rp_100', 'USD', 27375/3],
357+
[f"{self.PRESENT_DATE} to {self.FUTURE_DATE}", 'All', NO_MEASURE_VALUE, 'rp_20', 'USD', 0.0],
358+
[f"{self.PRESENT_DATE} to {self.FUTURE_DATE}", 'All', NO_MEASURE_VALUE, 'rp_50', 'USD', 9125.0/3]],
359+
'index_names': [None],
360+
'column_names': [None]},
361+
orient="tight"
362+
)
363+
# fmt: on
364+
365+
def test_interp_trajectory(self):
366+
interp_traj = InterpolatedRiskTrajectory(
367+
[self.base_snapshot, self.future_snapshot]
368+
)
369+
pd.testing.assert_frame_equal(
370+
interp_traj.per_date_risk_metrics(),
371+
self.expected_interp_metrics,
372+
check_dtype=False,
373+
check_categorical=False,
374+
)
375+
pd.testing.assert_frame_equal(
376+
interp_traj.per_period_risk_metrics(),
377+
self.expected_period_metrics,
378+
check_dtype=False,
379+
check_categorical=False,
380+
)
381+
382+
def test_interp_trajectory_with_group(self):
383+
exp0 = reusable_minimal_exposures(group_id=CATEGORIES)
384+
exp1 = reusable_minimal_exposures(
385+
group_id=CATEGORIES, increase_value_factor=self.EXP_INCREASE_VALUE_FACTOR
386+
)
387+
snap0 = Snapshot(
388+
exposure=exp0,
389+
hazard=reusable_minimal_hazard(),
390+
impfset=reusable_minimal_impfset(),
391+
date=self.PRESENT_DATE,
392+
)
393+
snap1 = Snapshot(
394+
exposure=exp1,
395+
hazard=reusable_minimal_hazard(
396+
intensity_factor=self.HAZ_INCREASE_INTENSITY_FACTOR
397+
),
398+
impfset=reusable_minimal_impfset(),
399+
date=self.FUTURE_DATE,
400+
)
401+
402+
expected_interp_metrics = pd.concat(
403+
[
404+
self.expected_interp_metrics,
405+
# fmt: off
406+
pd.DataFrame.from_dict(
407+
{
408+
"index": [0, 1, 2, 3, 4, 5],
409+
"columns": [DATE_COL_NAME, GROUP_COL_NAME, MEASURE_COL_NAME, METRIC_COL_NAME, UNIT_COL_NAME, RISK_COL_NAME,],
410+
"data": [
411+
[pd.Period("2020"), 1, NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 15.0,],
412+
[pd.Period("2020"), 2, NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 5.0,],
413+
[pd.Period("2021"), 1, NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 78.75,],
414+
[pd.Period("2021"), 2, NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 26.25,],
415+
[pd.Period("2022"), 1, NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 180.0,],
416+
[pd.Period("2022"), 2, NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 60.0,],
417+
],
418+
"index_names": [None],
419+
"column_names": [None],
420+
},
421+
orient="tight",
422+
),
423+
# fmt: on
424+
],
425+
ignore_index=True,
426+
)
427+
428+
interp_traj = InterpolatedRiskTrajectory([snap0, snap1])
429+
pd.testing.assert_frame_equal(
430+
interp_traj.per_date_risk_metrics(),
431+
expected_interp_metrics,
432+
check_dtype=False,
433+
check_categorical=False,
434+
)
435+
436+
def test_interp_trajectory_change_rp(self):
437+
interp_traj = InterpolatedRiskTrajectory(
438+
[self.base_snapshot, self.future_snapshot], return_periods=[10, 60, 1000]
439+
)
440+
expected = pd.DataFrame.from_dict(
441+
# fmt: off
442+
{
443+
"index": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
444+
"columns": [DATE_COL_NAME, GROUP_COL_NAME, MEASURE_COL_NAME, METRIC_COL_NAME, UNIT_COL_NAME, RISK_COL_NAME,],
445+
"data": [
446+
[pd.Period(2020), "All", NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 20.0,],
447+
[pd.Period(2021), "All", NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 105.0,],
448+
[pd.Period(2022), "All", NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 240.0,],
449+
[pd.Period(2020), "All", NO_MEASURE_VALUE, "rp_10", "USD", 0.0],
450+
[pd.Period(2021), "All", NO_MEASURE_VALUE, "rp_10", "USD", 0.0],
451+
[pd.Period(2022), "All", NO_MEASURE_VALUE, "rp_10", "USD", 0.0],
452+
[pd.Period(2020), "All", NO_MEASURE_VALUE, "rp_60", "USD", 700.0],
453+
[pd.Period(2021), "All", NO_MEASURE_VALUE, "rp_60", "USD", 3675.0],
454+
[pd.Period(2022), "All", NO_MEASURE_VALUE, "rp_60", "USD", 8400.0],
455+
[pd.Period(2020), "All", NO_MEASURE_VALUE, "rp_1000", "USD", 1500.0,],
456+
[pd.Period(2021), "All", NO_MEASURE_VALUE, "rp_1000", "USD", 7875.0,],
457+
[pd.Period(2022), "All", NO_MEASURE_VALUE, "rp_1000", "USD", 18000.0,],
458+
],
459+
"index_names": [None],
460+
"column_names": [None],
461+
},
462+
# fmt: on
463+
orient="tight",
464+
)
465+
pd.testing.assert_frame_equal(
466+
interp_traj.per_date_risk_metrics(),
467+
expected,
468+
check_dtype=False,
469+
check_categorical=False,
470+
)
471+
472+
# Also check change to other return period
473+
interp_traj.return_periods = DEFAULT_RP
474+
pd.testing.assert_frame_equal(
475+
interp_traj.per_date_risk_metrics(),
476+
self.expected_interp_metrics,
477+
check_dtype=False,
478+
check_categorical=False,
479+
)
480+
481+
def test_interp_trajectory_risk_disc_rate(self):
482+
risk_disc_rate = DiscRates(
483+
years=np.array(range(2020, 2023)), rates=np.ones(3) * 0.05
484+
) # Easy check for year 2021 -> 105.0 * 1/(1+0.05) == 100.
485+
interp_traj = InterpolatedRiskTrajectory(
486+
[self.base_snapshot, self.future_snapshot], risk_disc_rates=risk_disc_rate
487+
)
488+
expected = pd.DataFrame.from_dict(
489+
# fmt: off
490+
{
491+
"index": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
492+
"columns": [DATE_COL_NAME, GROUP_COL_NAME, MEASURE_COL_NAME, METRIC_COL_NAME, UNIT_COL_NAME, RISK_COL_NAME,],
493+
"data": [
494+
[pd.Period(2020), "All", NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 20.0,],
495+
[pd.Period(2021), "All", NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 100.0,],
496+
[pd.Period(2022), "All", NO_MEASURE_VALUE, AAI_METRIC_NAME, "USD", 217.68707482993196,],
497+
[pd.Period(2020), "All", NO_MEASURE_VALUE, "rp_20", "USD", 0.0],
498+
[pd.Period(2021), "All", NO_MEASURE_VALUE, "rp_20", "USD", 0.0],
499+
[pd.Period(2022), "All", NO_MEASURE_VALUE, "rp_20", "USD", 0.0],
500+
[pd.Period(2020), "All", NO_MEASURE_VALUE, "rp_50", "USD", 500.0],
501+
[pd.Period(2021), "All", NO_MEASURE_VALUE, "rp_50", "USD", 2500.0],
502+
[pd.Period(2022), "All", NO_MEASURE_VALUE, "rp_50", "USD", 5442.176870748299,],
503+
[pd.Period(2020), "All", NO_MEASURE_VALUE, "rp_100", "USD", 1500.0],
504+
[pd.Period(2021), "All", NO_MEASURE_VALUE, "rp_100", "USD", 7500.0],
505+
[pd.Period(2022), "All", NO_MEASURE_VALUE, "rp_100", "USD", 16326.530612244896,],
506+
],
507+
"index_names": [None],
508+
"column_names": [None],
509+
},
510+
# fmt: on
511+
orient="tight",
512+
)
513+
pd.testing.assert_frame_equal(
514+
interp_traj.per_date_risk_metrics(),
515+
expected,
516+
check_dtype=False,
517+
check_categorical=False,
518+
)
519+
520+
# Also check change to other return period
521+
interp_traj.risk_disc_rates = None
522+
pd.testing.assert_frame_equal(
523+
interp_traj.per_date_risk_metrics(),
524+
self.expected_interp_metrics,
525+
check_dtype=False,
526+
check_categorical=False,
527+
)
528+
529+
def test_interp_trajectory_risk_contributions(self):
530+
interp_traj = InterpolatedRiskTrajectory(
531+
[self.base_snapshot, self.future_snapshot]
532+
)
533+
expected = pd.DataFrame.from_dict(
534+
# fmt: off
535+
{'index': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
536+
'columns': [DATE_COL_NAME, GROUP_COL_NAME, MEASURE_COL_NAME, METRIC_COL_NAME, UNIT_COL_NAME, RISK_COL_NAME,],
537+
'data': [
538+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_BASE_RISK_NAME, 'USD', 20.0],
539+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_BASE_RISK_NAME, 'USD', 20.0],
540+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_BASE_RISK_NAME, 'USD', 20.0],
541+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_EXPOSURE_NAME, 'USD', 0.0],
542+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_EXPOSURE_NAME, 'USD', 50.0],
543+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_EXPOSURE_NAME, 'USD', 100.0],
544+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_HAZARD_NAME, 'USD', 0.0],
545+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_HAZARD_NAME, 'USD', 10.0],
546+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_HAZARD_NAME, 'USD', 20.0],
547+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_VULNERABILITY_NAME, 'USD', 0.0],
548+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_VULNERABILITY_NAME, 'USD', 0.0],
549+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_VULNERABILITY_NAME, 'USD', 0.0],
550+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_INTERACTION_TERM_NAME, 'USD', 0.0],
551+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_INTERACTION_TERM_NAME, 'USD', 25.0],
552+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_INTERACTION_TERM_NAME, 'USD', 100.0]],
553+
'index_names': [None],
554+
'column_names': [None]},
555+
# fmt: on
556+
orient="tight",
557+
)
558+
pd.testing.assert_frame_equal(
559+
interp_traj.risk_contributions_metrics(),
560+
expected,
561+
check_dtype=False,
562+
check_categorical=False,
563+
)
564+
565+
# With changing vulnerability
566+
hazard = reusable_minimal_hazard()
567+
impfset1 = ImpactFuncSet(
568+
[
569+
ImpactFunc(
570+
haz_type=hazard.haz_type,
571+
intensity_unit=hazard.units,
572+
name="linear",
573+
intensity=np.array([0, 100 / 2, 100]),
574+
mdd=np.array([0, 0.5, 1]),
575+
paa=np.array([1, 1, 1]),
576+
id=1,
577+
),
578+
]
579+
)
580+
impfset2 = ImpactFuncSet(
581+
[
582+
ImpactFunc(
583+
haz_type=hazard.haz_type,
584+
intensity_unit=hazard.units,
585+
name="linear-half-paa",
586+
intensity=np.array([0, 100 / 2, 100]),
587+
mdd=np.array([0, 0.5, 1]),
588+
paa=np.array([0.5, 0.5, 0.5]),
589+
id=1,
590+
)
591+
]
592+
)
593+
base_snapshot = Snapshot(
594+
exposure=reusable_minimal_exposures(),
595+
hazard=hazard,
596+
impfset=impfset1,
597+
date=2020,
598+
)
599+
future_snapshot = Snapshot(
600+
exposure=reusable_minimal_exposures(
601+
increase_value_factor=self.EXP_INCREASE_VALUE_FACTOR,
602+
),
603+
hazard=reusable_minimal_hazard(
604+
intensity_factor=self.HAZ_INCREASE_INTENSITY_FACTOR
605+
),
606+
impfset=impfset2,
607+
date=2022,
608+
)
609+
610+
interp_traj = InterpolatedRiskTrajectory([base_snapshot, future_snapshot])
611+
expected = pd.DataFrame.from_dict(
612+
# fmt: off
613+
{'index': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
614+
'columns': [DATE_COL_NAME, GROUP_COL_NAME, MEASURE_COL_NAME, METRIC_COL_NAME, UNIT_COL_NAME, RISK_COL_NAME,],
615+
'data': [
616+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_BASE_RISK_NAME, 'USD', 20.0],
617+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_BASE_RISK_NAME, 'USD', 20.0],
618+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_BASE_RISK_NAME, 'USD', 20.0],
619+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_EXPOSURE_NAME, 'USD', 0.0],
620+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_EXPOSURE_NAME, 'USD', 50.0],
621+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_EXPOSURE_NAME, 'USD', 100.0],
622+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_HAZARD_NAME, 'USD', 0.0],
623+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_HAZARD_NAME, 'USD', 10.0],
624+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_HAZARD_NAME, 'USD', 20.0],
625+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_VULNERABILITY_NAME, 'USD', 0.0],
626+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_VULNERABILITY_NAME, 'USD', -5.0],
627+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_VULNERABILITY_NAME, 'USD', -10.0],
628+
[pd.Period(str(2020)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_INTERACTION_TERM_NAME, 'USD', 0.0],
629+
[pd.Period(str(2021)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_INTERACTION_TERM_NAME, 'USD', 3.75],
630+
[pd.Period(str(2022)), 'All', NO_MEASURE_VALUE, CONTRIBUTION_INTERACTION_TERM_NAME, 'USD', -10.0]],
631+
'index_names': [None],
632+
'column_names': [None]},
633+
# fmt: on
634+
orient="tight",
635+
)
636+
pd.testing.assert_frame_equal(
637+
interp_traj.risk_contributions_metrics(),
638+
expected,
639+
check_dtype=False,
640+
check_categorical=False,
641+
)

0 commit comments

Comments
 (0)