Quick Start¶
Triangles¶
The core of Bermuda’s functionality is housed in the Triangle
class. Triangle
objects contain Cell
objects, ordered
by experience period and evaluation date. Here’s a quick example of a
triangle with one cell for an experience period starting January 1st, 2025
and ending January 31st, 2025, evaluated at the end of January:
import datetime
from bermuda import Cell, Triangle
cell = Cell(
period_start=datetime.date(2025, 1, 1),
period_end=datetime.date(2025, 1, 31),
evaluation_date=datetime.date(2025, 1, 31),
values={"paid_loss": 1e3, "earned_premium": 10e3},
)
triangle = Triangle([cell])
The Cell
object has the required fields
period_start
, period_end
,
evaluation_date
, and values
.
The Triangle
class also
provides an easy to read __repr__
method when calling/printing the object:
>>> triangle
Cumulative Triangle
Number of slices: 1
Number of cells: 1
Triangle category: Regular
Experience range: 2025-01-01/2025-01-31
Experience resolution: 1
Evaluation range: 2025-01-31/2025-01-31
Evaluation resolution: None
Dev Lag range: 0.0 - 0.0 months
Fields:
earned_premium
paid_loss
Common Metadata:
risk_basis Accident
The term ‘slices’ at the top of the __repr__
output is Bermuda’s name for distinct
types of metadata in the triangle. In this case, there is only
one cell and, therefore, only one type of metadata
and one slice. You can see this in more
detail by running triangle.slices
, which returns
a dictionary of (Metadata, Triangle)
key-value
pairs and is useful for iterating over multi-slice
triangles (see below).
Most of the attributes of triangles can be accessed directly through properties and methods, for instance:
>>> triangle.periods
[(datetime.date(2025, 1, 1), datetime.date(2025, 1, 31))]
>>> triangle.evaluation_dates
[datetime.date(2025, 1, 31)]
>>> triangle.dev_lags()
[0.0]
>>> triangle.fields
['earned_premium', 'paid_loss']
Note, Bermuda defines development lags as time since period end,
not time since period start, and by default uses months
as the
time unit. In the above example, there has been
exactly 0.0
months since period end.
We could specify days as the time unit using
triangle.dev_lags(unit="day")
.
You can also return individual cell field values via Python’s typical
__get_item__
square-bracket syntax:
>>> cell["paid_loss"]
1000.0
And you can return individual triangle cells via indexing:
>>> triangle[0]["paid_loss"]
1000.0
Triangles can be concatenated. For instance, here’s another cell for January evaluated as of February 2025:
february_cell = Cell(
period_start=datetime.date(2025, 1, 1),
period_end=datetime.date(2025, 1, 31),
evaluation_date=datetime.date(2025, 2, 28),
values={"paid_loss": 3e3, "earned_premium": 10e3},
)
triangle += Triangle([february_cell])
This example uses Python’s +=
in-place operator.
A useful property on triangles is right_edge
, which returns the cells
for the latest evaluation date for each period in the triangle, which in this example
is the February-evaluated cell:
>>> triangle.right_edge
Cumulative Triangle
Number of slices: 1
Number of cells: 1
Triangle category: Regular
Experience range: 2025-01-01/2025-01-31
Experience resolution: 1
Evaluation range: 2025-02-28/2025-02-28
Evaluation resolution: None
Dev Lag range: 1.0 - 1.0 months
Fields:
earned_premium
paid_loss
Common Metadata:
risk_basis Accident
Triangle Metadata
¶
The Metadata
class identifies distinct
triangle slices. This is useful when working
with multi-program triangles, for example,
or when specific details are useful to house
in the triangle data structure.
By default, empty Metadata
have
the following structure:
>>> from bermuda import Metadata
>>> Metadata()
Metadata(
risk_basis='Accident',
country=None,
currency=None,
reinsurance_basis=None,
loss_definition=None,
per_occurrence_limit=None,
details={},
loss_details={}
)
The details
dictionary can take arbitrary key-value pairs
(e.g. company name), and the loss_details
dictionary
is useful for distinguishing detail fields that should only
be taken into account when operating on losses, not premium.
The latter is used during certain grouping functions, such
as bermuda.utils.aggregate
and bermuda.utils.summarize
, to
exclude premium fields from grouping function operations.
Here’s a quick worked example of handling triangles
with multiple slices.
import datetime
from bermuda import Metadata, Cell, Triangle
metadata_a = Metadata(details=dict(company="A"))
metadata_b = Metadata(details=dict(company="B"))
cell = Cell(
period_start=datetime.date(2025, 1, 1),
period_end=datetime.date(2025, 12, 31),
evaluation_date=datetime.date(2025, 12, 31),
values={"reported_loss": 1e3, "written_premium": 1e4},
metadata=metadata_a,
)
triangle_a = Triangle([cell])
# Use .replace to use the same cell with different metadata
triangle_b = triangle_a.replace(metadata=metadata_b)
combined = triangle_a + triangle_b
assert combined.slices == {metadata_a: triangle_a, metadata_b: triangle_b}
# bermuda.summarize combines cells with the same
# (period_start, period_end, evalution_date) coordinates
summarized = combined.summarize()
assert len(summarized) == 1
assert summarized[0]["reported_loss"] == 2e3
assert summarized[0]["written_premium"] == 2e4
# Only summarize the loss fields
summarized_losses = combined.summarize(summarize_premium=False)
assert summarized_losses[0]["reported_loss"] == 2e3
assert summarized_losses[0]["written_premium"] == 1e4
Above, we introduced a triangle method called summarize
from combining multi-slice triangles. This exists
as its own function in bermuda.utils.summarize
, as well
as a method on the Triangle
class, i.e. triangle.summarize()
.
Loading and saving triangles¶
Bermuda can load and save triangles into a number of formats,
namely long and wide data frames from CSV files, JSON files,
and an internal triangle binary format known as trib
.
The io
module includes relevant triangle_from_*
and
*_to_triangle*
formats, e.g.
>>> from bermuda import meyers_tri, Triangle
>>> meyers_tri.to_json("meyers_triangle.json")
>>> meyers_tri = Triangle.from_json("meyers_triangle.json")
>>> meyers_tri
Cumulative Triangle
Number of slices: 1
Number of cells: 100
Triangle category: Regular
Experience range: 1988-01-01/1997-12-31
Experience resolution: 12
Evaluation range: 1988-12-31/2006-12-31
Evaluation resolution: 12
Dev Lag range: 0.0 - 108.0 months
Fields:
earned_premium
paid_loss
reported_loss
Common Metadata:
currency USD
country US
risk_basis Accident
reinsurance_basis Net
loss_definition Loss+DCC
The .trib
file stands for [tri]angle[b]inary
,
and is often quicker to work with for larger triangles
than other formats.
Bermuda also integrates with triangles from the
Python ChainLadder package
using the bermuda.io.chain_ladder_to_triangle
and
bermuda.io.triangle_to_chain_ladder
functions.
For example, to convert the sample data in the ChainLadder package
to a Bermuda triangle:
import chainladder as cl
from bermuda import (
chain_ladder_to_triangle,
triangle_to_chain_ladder,
)
chain_ladder_tri = cl.load_sample("clrd")
bermuda_tri = chain_ladder_to_triangle(chain_ladder_tri)
chain_ladder_from_bermuda = triangle_to_chain_ladder(bermuda_tri)