Quick Start ================== Triangles ---------- The core of Bermuda's functionality is housed in the :code:`Triangle` class. :code:`Triangle` objects contain :code:`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: .. code-block:: python 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 :code:`Cell` object has the required fields :code:`period_start`, :code:`period_end`, :code:`evaluation_date`, and :code:`values`. The :code:`Triangle` class also provides an easy to read :code:`__repr__` method when calling/printing the object: .. code:: python >>> 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 :code:`__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 :code:`triangle.slices`, which returns a dictionary of :code:`(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: .. code:: python >>> 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 :code:`0.0` months since period end. We could specify days as the time unit using :code:`triangle.dev_lags(unit="day")`. You can also return individual cell field values via Python's typical :code:`__get_item__` square-bracket syntax: .. code:: python >>> cell["paid_loss"] 1000.0 And you can return individual triangle cells via indexing: .. code:: python >>> triangle[0]["paid_loss"] 1000.0 Triangles can be concatenated. For instance, here's another cell for January evaluated as of February 2025: .. code:: python 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 :code:`+=` `in-place operator `_. A useful property on triangles is :code:`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: .. code:: python >>> 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 :code:`Metadata` -------------------------- The :code:`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 :code:`Metadata` have the following structure: .. code:: python >>> 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 :code:`details` dictionary can take arbitrary key-value pairs (e.g. company name), and the :code:`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 :code:`bermuda.utils.aggregate` and :code:`bermuda.utils.summarize`, to exclude premium fields from grouping function operations. Here's a quick worked example of handling triangles with multiple slices. .. code:: python 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 :code:`trib`. The ``io`` module includes relevant :code:`triangle_from_*` and :code:`*_to_triangle*` formats, e.g. .. code:: pycon >>> 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 :code:`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: .. code-block:: python 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)