Skip to content

Commit 14d1ca8

Browse files
committed
update forecasting docs, fix prediction overwrite, fix get limit
1 parent 9213df3 commit 14d1ca8

File tree

11 files changed

+79
-24
lines changed

11 files changed

+79
-24
lines changed

docs/source/tutorials/forecasting.rst

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,12 @@ Now we can predict future losses using this model. We'll create a triangle that
5050

5151
.. code:: python
5252
53-
import bermuda as tri
53+
from bermuda import Triangle, CumulativeCell
54+
from datetime import date
5455
55-
target_triangle = tri.Triangle(
56+
target_triangle = Triangle(
5657
[
57-
tri.CumulativeCell(
58+
CumulativeCell(
5859
period_start=date(1998, 1, 1),
5960
period_end=date(1998, 12, 31),
6061
evaluation_date=date(2020, 12, 31),
@@ -66,15 +67,32 @@ Now we can predict future losses using this model. We'll create a triangle that
6667
target = client.triangle.create(name="target_triangle", data=target_triangle)
6768
gcc_prediction = gcc_forecast.predict("full_meyers", target_triangle=target)
6869
69-
It can be helpful to convert the prediction to bermuda to inspect the results
70+
It can be helpful to convert the prediction to Bermuda to inspect the results:
7071

7172
.. code:: python
7273
7374
gcc_prediction_tri = gcc_prediction.to_bermuda()
7475
gcc_loss_ratio = gcc_prediction_tri[0]['paid_loss'] / gcc_prediction_tri[0]['earned_premium']
7576
print(f"Ultimate loss ratio: {gcc_loss_ratio}")
77+
78+
And we can also plot the forecasted loss ratio against the training data using
79+
Bermuda's lower-level plotting functionality.
80+
We rename the fields to make the plot easier to read.
7681

77-
We can compare this to a more sophisticated model, like the ``SSM`` model. This model is a bayesian state-space model that incorporates a mean-reverting latent loss ratio.
82+
.. code:: python
83+
84+
(
85+
meyers_tri.select(["paid_loss", "earned_premium"]) +
86+
gcc_prediction_tri.derive_fields(
87+
forecasted_loss = lambda cell: cell["paid_loss"],
88+
).select(
89+
["forecasted_loss", "earned_premium"]
90+
)
91+
).plot_right_edge()
92+
93+
.. image:: gcc-forecasts.png
94+
95+
We can compare this to a more sophisticated model, like the ``SSM`` model. This model is a Bayesian state-space model that incorporates a mean-reverting latent loss ratio.
7896

7997
.. code:: python
8098
@@ -88,8 +106,17 @@ We can compare this to a more sophisticated model, like the ``SSM`` model. This
88106
)
89107
ssm_prediction = ssm_forecast.predict("full_meyers", target_triangle=target)
90108
ssm_prediction_tri = ssm_prediction.to_bermuda()
91-
ssm_loss_ratio = ssm_prediction_tri[0]['paid_loss'] / ssm_prediction_tri[0]['earned_premium']
92109
93-
Note that the ``ssm_loss_ratio`` is a posterior distribution of 10,000 samples of the ultimate loss ratio unlike the GCC point estimate.
110+
ssm_prediction_tri.plot_histogram(
111+
metric_spec="Paid Loss Ratio",
112+
width=500,
113+
height=300,
114+
).properties(
115+
title="SSM paid loss ratio",
116+
).configure_axis(
117+
labelFontSize=14,
118+
)
119+
120+
.. image:: ssm-forecast.png
94121

95-
.. image:: loss_ratio_distribution.png
122+
Note that the loss ratio is now a posterior distribution of 10,000 samples of the ultimate loss ratio unlike the GCC point estimate.
47 KB
Loading
102 KB
Loading
-32.1 KB
Binary file not shown.
46.8 KB
Loading
30.3 KB
Loading

ledger_analytics/cashflow.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,13 @@ def predict(
127127
initial_loss_triangle: Triangle | str | None = None,
128128
prediction_name: str | None = None,
129129
timeout: int = 300,
130+
overwrite: bool = False,
130131
) -> Triangle:
131132
triangle_name = triangle if isinstance(triangle, str) else triangle.name
132133
config = {
133134
"triangle_name": triangle_name,
134135
"predict_config": self.PredictConfig(**(config or {})).__dict__,
136+
"overwrite": overwrite,
135137
}
136138
if prediction_name:
137139
config["prediction_name"] = prediction_name

ledger_analytics/interface.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,19 @@ def delete(self, name: str | None = None, id: str | None = None) -> None:
124124
def _get_details_from_id_name(
125125
self, name: str | None = None, id: str | None = None
126126
) -> str:
127+
n_triangles = self.list(limit=1)["count"]
127128
triangles = [
128129
result
129-
for result in self.list().get("results")
130+
for result in self.list(limit=n_triangles).get("results")
130131
if result.get("name") == name or result.get("id") == id
131132
]
132133
if not len(triangles):
133134
name_or_id = f"name '{name}'" if id is None else f"ID '{id}'"
134135
raise ValueError(f"No triangle found with {name_or_id}.")
135136
return triangles[0]
136137

137-
def list(self) -> list[JSONDict]:
138-
response = self._requester.get(self.endpoint)
138+
def list(self, limit: int = 25) -> list[JSONDict]:
139+
response = self._requester.get(self.endpoint, params={"limit": limit})
139140
if not response.ok:
140141
response.raise_for_status()
141142
return response.json()
@@ -315,6 +316,7 @@ def predict(
315316
timeout: int = 300,
316317
name: str | None = None,
317318
id: str | None = None,
319+
overwrite: bool = False,
318320
):
319321
model = self.get(name, id)
320322
return model.predict(
@@ -323,6 +325,7 @@ def predict(
323325
target_triangle=target_triangle,
324326
prediction_name=prediction_name,
325327
timeout=timeout,
328+
overwrite=overwrite,
326329
)
327330

328331
def terminate(self, name: str | None = None, id: str | None = None):
@@ -333,8 +336,10 @@ def delete(self, name: str | None = None, id: str | None = None) -> None:
333336
model = self.get(name, id)
334337
return model.delete()
335338

336-
def list(self) -> list[JSONDict]:
337-
return self._requester.get(self.endpoint, stream=True).json()
339+
def list(self, limit: int = 25) -> list[JSONDict]:
340+
return self._requester.get(
341+
self.endpoint, stream=True, params={"limit": limit}
342+
).json()
338343

339344
def list_model_types(self) -> list[JSONDict]:
340345
url = self.endpoint + "-type"
@@ -347,9 +352,10 @@ def model_class_slug(self):
347352
def _get_details_from_id_name(
348353
self, model_name: str | None = None, model_id: str | None = None
349354
) -> str:
355+
n_objects = self.list(limit=1)["count"]
350356
models = [
351357
result
352-
for result in self.list().get("results")
358+
for result in self.list(limit=n_objects).get("results")
353359
if result.get("name") == model_name or result.get("id") == model_id
354360
]
355361
if not len(models):
@@ -428,21 +434,25 @@ def predict(
428434
timeout: int = 300,
429435
name: str | None = None,
430436
id: str | None = None,
437+
overwrite: bool = False,
431438
):
432439
model = self.get(name, id)
433440
return model.predict(
434441
triangle,
435442
config=config,
436443
initial_loss_triangle=initial_loss_triangle,
437444
timeout=timeout,
445+
overwrite=overwrite,
438446
)
439447

440448
def delete(self, name: str | None = None, id: str | None = None) -> None:
441449
model = self.get(name, id)
442450
return model.delete()
443451

444-
def list(self) -> list[JSONDict]:
445-
return self._requester.get(self.endpoint, stream=True).json()
452+
def list(self, limit: int = 25) -> list[JSONDict]:
453+
return self._requester.get(
454+
self.endpoint, stream=True, params={"limit": limit}
455+
).json()
446456

447457
def list_model_types(self) -> list[JSONDict]:
448458
url = self.endpoint + "-type"
@@ -455,9 +465,10 @@ def model_class_slug(self):
455465
def _get_details_from_id_name(
456466
self, model_name: str | None = None, model_id: str | None = None
457467
) -> str:
468+
n_objects = self.list(limit=1)["count"]
458469
models = [
459470
result
460-
for result in self.list().get("results")
471+
for result in self.list(limit=n_objects).get("results")
461472
if result.get("name") == model_name or result.get("id") == model_id
462473
]
463474
if not len(models):

ledger_analytics/model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,13 @@ def predict(
149149
target_triangle: Triangle | str | None = None,
150150
prediction_name: str | None = None,
151151
timeout: int = 300,
152+
overwrite: bool = False,
152153
) -> Triangle:
153154
triangle_name = triangle if isinstance(triangle, str) else triangle.name
154155
config = {
155156
"triangle_name": triangle_name,
156157
"predict_config": self.PredictConfig(**(config or {})).__dict__,
158+
"overwrite": overwrite,
157159
}
158160
if prediction_name:
159161
config["prediction_name"] = prediction_name

ledger_analytics/requester.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,25 @@ def __init__(self, api_key: str) -> None:
2626
def post(self, url: str, data: JSONDict):
2727
return self._factory("post", url, data)
2828

29-
def get(self, url: str, data: JSONDict | None = None, stream: bool = False):
30-
return self._factory("get", url, data, stream)
29+
def get(
30+
self,
31+
url: str,
32+
data: JSONDict | None = None,
33+
stream: bool = False,
34+
params: JSONDict | None = None,
35+
):
36+
return self._factory("get", url, data, stream, params=params or {})
3137

3238
def delete(self, url: str, data: JSONDict | None = None):
3339
return self._factory("delete", url, data)
3440

3541
def _factory(
36-
self, method: HTTPMethods, url: str, data: JSONDict, stream: bool = False
42+
self,
43+
method: HTTPMethods,
44+
url: str,
45+
data: JSONDict,
46+
stream: bool = False,
47+
params: JSONDict | None = None,
3748
):
3849
if method.lower() == "post":
3950
request = requests.post
@@ -44,7 +55,9 @@ def _factory(
4455
else:
4556
raise ValueError(f"Unrecognized HTTPMethod {method}.")
4657

47-
response = request(url=url, json=data or {}, headers=self.headers)
58+
response = request(
59+
url=url, json=data or {}, headers=self.headers, params=params
60+
)
4861
self._catch_status(response)
4962
return response
5063

ledger_analytics/tail.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class Config(ValidationConfig):
9797

9898
loss_family: LossFamily = "Gamma"
9999
loss_definition: Literal["paid", "reported", "incurred"] = "paid"
100-
recency_decay: str | float | None = None
100+
recency_decay: str | float = 1.0
101101
line_of_business: str | None = None
102102
min_rel_pred: float = 0.0
103103
dev_lag_intercept: float = 0.0
@@ -183,7 +183,7 @@ class Config(ValidationConfig):
183183

184184
loss_family: LossFamily = "Gamma"
185185
loss_definition: Literal["paid", "reported", "incurred"] = "paid"
186-
recency_decay: str | float | None = None
186+
recency_decay: str | float = 1.0
187187
line_of_business: str | None = None
188188
min_rel_pred: float = 0.0
189189
dev_lag_intercept: float = 0.0
@@ -261,7 +261,7 @@ class Config(ValidationConfig):
261261

262262
loss_definition: Literal["paid", "reported", "incurred"] = "paid"
263263
lambda_: float = 1.0
264-
recency_decay: str | float | None = None
264+
recency_decay: str | float = 1.0
265265
min_rel_pred: float = 0.0
266266
priors: dict[str, list[float] | float] | None = None
267267
prior_only: bool = False

0 commit comments

Comments
 (0)