catadjust 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
161
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mutahar Chalmers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: catadjust
3
+ Version: 0.1.0
4
+ Summary: Adjustment tools for catastrophe models
5
+ Project-URL: Homepage, https://github.com/MutaharChalmers/catadjust
6
+ Author-email: Mutahar Chalmers <mutahar.chalmers@gmail.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2025 Mutahar Chalmers
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Classifier: Development Status :: 4 - Beta
30
+ Classifier: Intended Audience :: Science/Research
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Topic :: Scientific/Engineering :: GIS
35
+ Requires-Python: >=3.8
36
+ Requires-Dist: numpy>=1.24
37
+ Requires-Dist: pandas>=2.0
38
+ Description-Content-Type: text/markdown
39
+
40
+ # CatAdjust - Tools for adjusting catastrophe models
41
+ Current tools:
42
+ - Hazard ELT Adjustment Tool - Given a location-level ELT with hazard values, and a collection of *target* hazard EEF curves at all locations, this tool adjusts the rates of all events in the ELT, such that the hazard EEF curves from the rate-adjusted ELT match the target hazard EEF curves as closely as possible.
43
+
44
+ Future tools:
45
+ - Loss Adjustment Tool - Given a stochastic ELT or YLT from a catastrophe model, and some historic loss experience, this tool adjusts the losses in the ELT or YLT such that the adjusted OEP curve matches the empirical historic loss OEP curve over some user-defined range.
46
+
47
+ - Industry Loss Conversion Tool - Given two stochastic industry ELTs or YLTs with losses at sub-national (e.g. CRESTA) level, scale the losses from one ELT/YLT such that the EP curves match the EP curves derived from the other ELT/YLT both at aggregate and sub-national level.
48
+
49
+
@@ -0,0 +1,10 @@
1
+ # CatAdjust - Tools for adjusting catastrophe models
2
+ Current tools:
3
+ - Hazard ELT Adjustment Tool - Given a location-level ELT with hazard values, and a collection of *target* hazard EEF curves at all locations, this tool adjusts the rates of all events in the ELT, such that the hazard EEF curves from the rate-adjusted ELT match the target hazard EEF curves as closely as possible.
4
+
5
+ Future tools:
6
+ - Loss Adjustment Tool - Given a stochastic ELT or YLT from a catastrophe model, and some historic loss experience, this tool adjusts the losses in the ELT or YLT such that the adjusted OEP curve matches the empirical historic loss OEP curve over some user-defined range.
7
+
8
+ - Industry Loss Conversion Tool - Given two stochastic industry ELTs or YLTs with losses at sub-national (e.g. CRESTA) level, scale the losses from one ELT/YLT such that the EP curves match the EP curves derived from the other ELT/YLT both at aggregate and sub-national level.
9
+
10
+
@@ -0,0 +1 @@
1
+ from .hazelt_adjust import HazardELTAdjustment
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from tqdm.auto import tqdm
7
+
8
+
9
+ class HazardELTAdjustment:
10
+ """Adjust a catastrophe model location-level ELT with hazard values
11
+ to match arbitrary target location-level hazard EEF curves as closely
12
+ as possible by scaling event rates.
13
+ """
14
+ def __init__(self, elt_raw, loccol, eventcol, ratecol, hazcol):
15
+ """Load raw location-level ELT and pre-process.
16
+
17
+ Parameters
18
+ ----------
19
+ elt_raw : DataFrame
20
+ Raw location-level ELT.
21
+ loccol: str
22
+ Name of column containing locationIDs.
23
+ eventcol: str
24
+ Name of column containing eventIDs.
25
+ ratecol: str
26
+ Name of column containing event rates.
27
+ hazcol: str
28
+ Name of column containing event-location hazard intensity.
29
+ """
30
+
31
+ # Load ELT to be adjusted and pre-process
32
+ elt = elt_raw.astype({loccol: np.int64, eventcol: np.int64,
33
+ ratecol: np.float64, hazcol: np.float64}
34
+ ).drop_duplicates([loccol, eventcol]
35
+ ).sort_values([loccol, hazcol], ascending=[True, False])
36
+ self.loccol = loccol
37
+ self.eventcol = eventcol
38
+ self.ratecol = ratecol
39
+ self.hazcol = hazcol
40
+ self.elt = self.calc_eef(elt)
41
+ m = self.elt.shape[0]
42
+
43
+ # Sorted array of unique eventIDs
44
+ eventIDs_unique = np.sort(self.elt[eventcol].unique())
45
+ self.nevents = eventIDs_unique.size
46
+
47
+ # Convert eventIDs in ELT to indices in event array
48
+ self.loceventixs = np.searchsorted(eventIDs_unique, self.elt[eventcol])
49
+
50
+ # Indices in ELT where location changes
51
+ locbreaks = np.nonzero(np.diff(self.elt[loccol]))[0] + 1
52
+ self.loc_slicers = np.stack([np.r_[0, locbreaks], np.r_[locbreaks, m]]).T
53
+
54
+ def calc_eef(self, elt):
55
+ """Calculate EEFs from a location-level ELT sorted by descending hazard.
56
+
57
+ Parameters
58
+ ----------
59
+ elt : DataFrame
60
+ Processed and sorted (in descending hazard intensity) location-level ELT.
61
+
62
+ Returns
63
+ -------
64
+ elt : DataFrame
65
+ Input ELT with additional EEF column.
66
+ """
67
+
68
+ elt['eef'] = elt.groupby(self.loccol, sort=False)[self.ratecol].transform(np.cumsum)
69
+ return elt
70
+
71
+ def adjust(self, eefs_targ, x0=None, min_rate=1e-12, tol=1e-4, niter=1000, alpha=0.001):
72
+ """Adjust ELT to match location-level hazard curves.
73
+
74
+ Parameters
75
+ ----------
76
+ eefs_targ : Series or ndarray
77
+ Target EEFs in the same order as the processed ELT.
78
+ x0 : Series or ndarray, optional
79
+ Initial guess to use for rate adjustment.
80
+ min_rate : float, optional
81
+ Minimum allowable rate constraint.
82
+ tol : float, optional
83
+ Convergence criterion for cost function. Iteration stops
84
+ once the cost function (mean square error) is less than this value.
85
+ niter : int, optional
86
+ Maximum number of iterations.
87
+ alpha : float, optional
88
+ Learning rate in Adam gradient descent algorithm.
89
+
90
+ Returns
91
+ -------
92
+ elt_adj : DataFrame
93
+ Adjusted ELT.
94
+ res : dict
95
+ Results dict.
96
+ fs : ndarray
97
+ Learning curve.
98
+ """
99
+
100
+ if x0 is None:
101
+ x0 = self.elt.groupby(self.eventcol)[self.ratecol].mean().values
102
+ else:
103
+ x0 = np.array(x0)
104
+
105
+ eefs_targ = np.array(eefs_targ)
106
+ args = (eefs_targ,)
107
+ res, fs = self._adam(self._cost, x0, args, alpha=alpha, niter=niter, tol=tol, amin=min_rate)
108
+ elt_adj = self.elt.copy()
109
+ elt_adj[self.ratecol] = res['x'][self.loceventixs]
110
+ elt_adj = self.calc_eef(elt_adj)
111
+ return elt_adj, res, fs
112
+
113
+ def _cost(self, theta, eefs_targ):
114
+ """Cost function for fitting an ELT to a target EEF by adjusting event rates.
115
+
116
+ Parameters
117
+ ----------
118
+ theta : ndarray
119
+ Rates to calculate cost function for, in unique eventID order.
120
+ eefs_targ : ndarray
121
+ Target EEFs for location-events in the same order as the
122
+ pre-processed ELT.
123
+
124
+ Returns
125
+ -------
126
+ cost : float
127
+ Cost function evaluated at theta.
128
+ cost_grad : ndarray
129
+ Gradient of cost function.
130
+ """
131
+
132
+ # Calculate EEFs for each location by chunked cumulative sums
133
+ eefs_pred = np.empty_like(eefs_targ)
134
+
135
+ # Expand event rates to event-location rates
136
+ rates = theta[self.loceventixs]
137
+ for a, b in self.loc_slicers:
138
+ eefs_pred[a:b] = rates[a:b].cumsum()
139
+
140
+ # Calculate deltas and cost function for current parameters
141
+ deltas = eefs_pred - eefs_targ
142
+ cost = (deltas**2).mean()
143
+
144
+ # Calculate gradient of cost function wrt to event rates
145
+ grad_cost = np.zeros_like(theta)
146
+ for a, b in self.loc_slicers:
147
+ grad_cost[self.loceventixs[a:b]] += deltas[a:b][::-1].cumsum()[::-1]
148
+
149
+ return cost, 2*grad_cost/deltas.size
150
+
151
+ def _adam(self, fun, x0, args=(), alpha=0.001, beta1=0.9, beta2=0.999,
152
+ eps=1e-8, niter=1000, tol=1e-6, amin=-np.inf, amax=np.inf):
153
+ """Adaptive Moment Estimation gradient descent with weight clipping.
154
+
155
+ Parameters
156
+ ----------
157
+ cost : function
158
+ Cost function which returns cost and gradient.
159
+ x0 : ndarray
160
+ Initial values for optimisation.
161
+ args : tuple, optional
162
+ Arguments to be passed to cost function.
163
+ alpha : float, optional
164
+ Learning rate.
165
+ beta1 : float, optional
166
+ Exponential decay rate for gradient momentum.
167
+ beta2 : float, optional
168
+ Exponential decay rate for gradient variance.
169
+ tol : float, optional
170
+ Convergence criterion for cost function. Iteration stops
171
+ once the cost function (mean square error) is less than this value.
172
+ amin : float, optional
173
+ Minimum value allowed for input values.
174
+ amax : float, optional
175
+ Maximum value allowed for input values.
176
+
177
+ Returns
178
+ -------
179
+ cost : float
180
+ Cost function evaluated at theta.
181
+ cost_grad : ndarray
182
+ Gradient of cost function.
183
+ """
184
+
185
+ x, m, v = x0, 0, 0
186
+ fs = np.zeros(niter)
187
+
188
+ pbar = tqdm(range(niter))
189
+ for i in pbar:
190
+ fs[i], grad = fun(x, *args)
191
+
192
+ pbar.set_description(f'f={fs[i]:.2e}{">" if fs[i] > tol else "<="}{tol:.2e}')
193
+ if fs[i] < tol:
194
+ return dict(x=x, fun=fs[i], jac=grad, nit=i), fs
195
+
196
+ # Estimates of first and second moment of gradient
197
+ m = (1 - beta1)*grad + beta1*m
198
+ v = (1 - beta2)*grad**2 + beta2*v
199
+
200
+ # Bias correction
201
+ mhat = m/(1 - beta1**(i+1))
202
+ vhat = v/(1 - beta2**(i+1))
203
+
204
+ # Update step
205
+ x = x - alpha * mhat/(np.sqrt(vhat) + eps)
206
+
207
+ # Weight clipping
208
+ x = np.clip(x, amin, amax)
209
+
210
+ f, grad = fun(x, *args)
211
+ print('Warning: Iteration limit reached before cost function converged within tolerance')
212
+ return dict(x=x, fun=f, jac=grad, nit=i), fs
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "catadjust"
7
+ version = "0.1.0"
8
+ description = "Adjustment tools for catastrophe models"
9
+ authors = [
10
+ { name = "Mutahar Chalmers", email = "mutahar.chalmers@gmail.com" },
11
+ ]
12
+ license = { file = "LICENSE" }
13
+ readme = "README.md"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Science/Research",
20
+ "Topic :: Scientific/Engineering :: GIS",
21
+ ]
22
+ requires-python = ">=3.8"
23
+ dependencies = [
24
+ "numpy>=1.24", "pandas>=2.0"
25
+ ]
26
+
27
+ [project.urls]
28
+ "Homepage" = "https://github.com/MutaharChalmers/catadjust"