reserving.Triangle
A loss triangle with four axes: index (segment), column (measure), origin
Usage
reserving.Triangle()and development age, in chainladder-python’s order.
Segments are named by key columns such as "lob" and "state": keys gives their names and index one label per segment. A triangle without keys has one segment, "Total".
Build one from a long table with from_long or from_frame. Ages are whole months from the start of the origin period, so age 12 on a 2021 accident year is valued at December 2021. Cells that were not observed are nan in values; an observed zero stays zero.
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long(
... origin=[2020, 2020, 2021],
... development=[12, 24, 12],
... values={"paid": [100.0, 150.0, 110.0]},
... )
>>> tri.shape(1, 1, 2, 2)
>>> tri.origins, tri.development, tri.valuation([‘2020’, ‘2021’], [12, 24], datetime.date(2021, 12, 31))
>>> tri.values[0][0][[100.0, 150.0], [110.0, nan]]
Attributes
| Name | Description |
|---|---|
| columns | Measure column names. |
| development | Development ages in months, youngest first. |
| development_grain |
Development grain: "Y", "S", "Q" or "M".
|
| index | Segment labels: a str each with one key, a tuple of key values with |
| is_cumulative | Whether the values are cumulative (otherwise incremental). |
| keys | Names of the key columns, in key order; empty without keys. |
| origin_grain |
Origin grain: "Y", "S", "Q" or "M".
|
| origins |
Origin periods, oldest first: "2021", "2021H1",
|
| shape |
Axis lengths: (index, column, origin, development).
|
| valuation | Valuation date of the latest diagonal: the last day of its month, |
| values |
Values as nested lists indexed [index][column][origin][development],
|
columns
Measure column names.
columns: list[str]
development
Development ages in months, youngest first.
development: list[int]
development_grain
Development grain: "Y", "S", "Q" or "M".
development_grain: str
index
Segment labels: a str each with one key, a tuple of key values with
index: list[Any]
several, and ["Total"] without keys.
is_cumulative
Whether the values are cumulative (otherwise incremental).
is_cumulative: bool
keys
Names of the key columns, in key order; empty without keys.
keys: list[str]
origin_grain
Origin grain: "Y", "S", "Q" or "M".
origin_grain: str
origins
Origin periods, oldest first: "2021", "2021H1",
origins: list[str]
"2021Q3" or "2021-07" by grain.
shape
Axis lengths: (index, column, origin, development).
shape: tuple[int, int, int, int]
valuation
Valuation date of the latest diagonal: the last day of its month,
valuation: date
as a datetime.date.
values
Values as nested lists indexed [index][column][origin][development],
values: list[list[list[list[float]]]]
nan where unobserved. numpy.asarray gives the 4-D array.
Methods
| Name | Description |
|---|---|
| from_frame() | Builds a triangle from a data frame in long format. |
| from_long() | Builds a triangle from the columns of a long table, one row per |
| grain() | The triangle at a coarser origin and/or development grain. |
| group_by() | Sums the segments that share the values of keys, dropping the |
| latest_diagonal() | The latest observed value of each origin, as nested lists indexed |
| link_ratios() | Age-to-age link ratios of the cumulative values. Development |
| select() | The segments whose key values match, and the measure columns named. |
| summary() | One row per segment and measure: the key values, column, |
| to_cumulative() | Cumulative values: running sums of the observed increments. |
| to_frame() | The long table of to_long as a pandas DataFrame. Needs pandas. |
| to_incremental() | Incremental values: each observed value minus the previous observed |
| to_long() | The triangle as a long table: a dict of equal-length lists with one |
| to_string() | The printout as text: the origin × development grid for a triangle |
| view() | One segment and measure as an origin × development table. |
from_frame()
Builds a triangle from a data frame in long format.
Usage
from_frame(
data,
origin,
development,
columns,
keys=None,
origin_grain="Y",
development_grain="Y",
cumulative=True,
development_is_valuation=False
)Columns are looked up with data[name], so a pandas or Polars DataFrame works, as does a dict of columns.
Parameters
data: DataFrame or dictorigin: str-
Name of the origin column (dates or integer years).
development: str-
Name of the development column (ages in months, or valuation dates when
development_is_valuationis true). columns: str or list of str-
Names of the measure columns.
keys: str or list of str = None-
Names of the key columns, such as
["lob", "state"]. By default every row is in one segment,"Total". origin_grain: (Y, S, Q, M) = "Y"development_grain: (Y, S, Q, M) = "Y"cumulative: bool = Truedevelopment_is_valuation: bool = False
Returns
Triangle
Raises
ValueError- As for from_long.
Examples
>>> from prospicio.reserving import Triangle
>>> df = {
... "lob": ["Auto", "Auto", "Auto", "Home"],
... "year": [2020, 2020, 2021, 2020],
... "age": [12, 24, 12, 12],
... "paid": [100.0, 150.0, 110.0, 50.0],
... }
>>> tri = Triangle.from_frame(df, "year", "age", "paid", keys="lob")
>>> tri.keys, tri.index, tri.shape([‘lob’], [‘Auto’, ‘Home’], (2, 1, 2, 2))
from_long()
Builds a triangle from the columns of a long table, one row per
Usage
from_long(
origin,
development,
values,
keys=None,
origin_grain="Y",
development_grain="Y",
cumulative=True,
development_is_valuation=False
)(keys, origin, development).
Origins span every period from the earliest to the latest row and ages every development period from the youngest to the oldest. Rows with the same (keys, origin, age) are summed; nan values are missing. Incremental input treats a missing row as a period without movement, as chainladder-python does.
Parameters
origin: array - like-
Any date in each row’s origin period (numpy
datetime64,datetime.date, pandasTimestamp), or integer years. development: array - like-
Development age of each row in months (12, 24, …), or its valuation date when
development_is_valuationis true. values: dict of str to array-like, or array-like-
Measure columns by name. A single array-like is one column named
"values". keys: dict of str to array-like = None-
Key columns by name, such as
{"lob": [...], "state": [...]}, in key order. Values are stored as strings (str()of each);Noneandnanare not allowed. Each distinct combination is a segment. By default every row is in one segment,"Total". origin_grain: (Y, S, Q, M) = "Y"-
Length of an origin period.
development_grain: (Y, S, Q, M) = "Y"-
Spacing of development ages; must divide the origin grain.
cumulative: bool = True-
Whether the values are cumulative (otherwise incremental).
development_is_valuation: bool = False- Whether development holds valuation dates instead of ages.
Returns
Triangle
Raises
ValueError- If columns differ in length, an age is not on the development grid, a value is infinite, the grains are incompatible, a key name is repeated or also a value column, or a key has missing values.
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long(
... origin=[2020, 2020, 2021, 2020],
... development=[12, 24, 12, 12],
... values={"paid": [100.0, 150.0, 110.0, 50.0]},
... keys={"lob": ["Auto", "Auto", "Auto", "Home"], "state": ["CA", "CA", "CA", "NY"]},
... )
>>> tri.keys, tri.index([‘lob’, ‘state’], [(‘Auto’, ‘CA’), (‘Home’, ‘NY’)])
Valuation dates instead of ages:
>>> import datetime
>>> from prospicio.reserving import Triangle
>>> d = datetime.date
>>> tri = Triangle.from_long(
... origin=[d(2021, 2, 1), d(2021, 2, 1), d(2021, 5, 1)],
... development=[d(2021, 3, 31), d(2021, 6, 30), d(2021, 6, 30)],
... values=[10.0, 25.0, 7.0],
... origin_grain="Q",
... development_grain="Q",
... development_is_valuation=True,
... )
>>> tri.origins, tri.development([‘2021Q1’, ‘2021Q2’], [3, 6])
grain()
The triangle at a coarser origin and/or development grain.
Usage
grain(origin_grain, development_grain=None)Parameters
origin_grain: (Y, S, Q, M) = "Y"development_grain: (Y, S, Q, M) = "Y"- By default origin_grain.
Returns
Triangle
Raises
ValueError- If a grain is finer than the current one, or the development grain does not divide the origin grain.
Examples
>>> from prospicio.reserving import Triangle
>>> q = Triangle.from_long(
... [2020, 2020], [3, 6], [1.0, 2.0], origin_grain="Q", development_grain="Q"
... )
>>> y = q.grain("Y")
>>> y.origins, y.development_grain([‘2020’], ‘Y’)
group_by()
Sums the segments that share the values of keys, dropping the
Usage
group_by(keys)other keys.
Cumulative values are summed cell by cell, and a cell is observed if any segment in the group observes it. An incremental triangle is summed as cumulative values and returned incremental.
Parameters
keys: str or list of str-
The keys to keep, in the order the result has them.
[]sums every segment into one, labelled"Total".
Returns
Triangle
Raises
ValueError- If a key is unknown or named twice.
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long(
... [2020, 2020, 2020],
... [12, 12, 12],
... {"paid": [1.0, 2.0, 3.0]},
... keys={"lob": ["Auto", "Auto", "Home"], "state": ["CA", "NY", "NY"]},
... )
>>> by_lob = tri.group_by("lob")
>>> by_lob.index, by_lob.to_long()["paid"]([‘Auto’, ‘Home’], [3.0, 3.0])
>>> tri.group_by([]).to_long()["paid"][6.0]
latest_diagonal()
The latest observed value of each origin, as nested lists indexed
Usage
latest_diagonal()[index][column][origin], nan for an origin with no value.
Returns
list of list of list of float
link_ratios()
Age-to-age link ratios of the cumulative values. Development
Usage
link_ratios()position d holds the ratio from age d to age d + 1, observed where both ages are observed and the earlier value is not zero.
Returns
Triangle
select()
The segments whose key values match, and the measure columns named.
Usage
select(columns=None, **keys)Each keyword names a key and gives one value or a list of values to keep; segments must match every keyword, and keep their order. Values are compared as strings, as keys are stored (str() of a value). A key named columns cannot be selected this way.
Parameters
columns: str or list of str = None-
Measure columns to keep, in this order. By default every column.
**keys: value or list of values-
For example
lob="Auto"orstate=["CA", "NY"]; any iterable that is not a string (a tuple, set, NumPy array or pandas Series) is a list of values.
Returns
Triangle
Raises
ValueError- If a key, value or column is unknown or given twice, a list of values is empty, or no segment matches.
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long(
... [2020, 2020, 2020],
... [12, 12, 12],
... {"paid": [1.0, 2.0, 3.0], "incurred": [2.0, 3.0, 4.0]},
... keys={"lob": ["Auto", "Auto", "Home"], "state": ["CA", "NY", "NY"]},
... )
>>> tri.select(state="NY").index[(‘Auto’, ‘NY’), (‘Home’, ‘NY’)]
>>> tri.select(lob="Auto", state=["CA", "NY"], columns="paid").shape(2, 1, 1, 1)
summary()
One row per segment and measure: the key values, column,
Usage
summary()n_origins (origins with an observed value), first_origin and last_origin of those, valuation (the last day of the latest valuation with an observed value), latest (the sum over origins of the latest cumulative value, so for an incremental triangle the sum of every increment) and cumulative. An origin or valuation is missing (None, which pandas may show as NaN) when the segment has no observed value of the measure.
Returns
pandas.DataFrame or dict- A DataFrame with pandas installed, a dict of lists otherwise.
Raises
ValueError- If a key has the name of one of the summary’s columns.
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long(
... [2020, 2020, 2021, 2020],
... [12, 24, 12, 12],
... {"paid": [100.0, 150.0, 110.0, 50.0]},
... keys={"lob": ["Auto", "Auto", "Auto", "Home"]},
... )
>>> s = tri.summary()
>>> s["lob"].tolist(), s["n_origins"].tolist(), s["latest"].tolist()([‘Auto’, ‘Home’], [2, 1], [260.0, 50.0])
to_cumulative()
Cumulative values: running sums of the observed increments.
Usage
to_cumulative()Returns
Triangle
to_frame()
The long table of to_long as a pandas DataFrame. Needs pandas.
Usage
to_frame()Returns
pandas.DataFrame
to_incremental()
Incremental values: each observed value minus the previous observed
Usage
to_incremental()value in its row.
Returns
Triangle
to_long()
The triangle as a long table: a dict of equal-length lists with one
Usage
to_long()entry per key column (by name), "origin" (start of the origin period, a datetime.date), "development" (age in months) and one per measure column, with a row per (segment, origin, age) that has an observed measure. It feeds back into from_frame (with keys=tri.keys) or pandas.DataFrame.
Returns
dict of str to list
Raises
ValueError-
If a key or measure column is named
originor development.
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long(
... [2020, 2020], [12, 12], {"paid": [1.0, 2.0]}, keys={"lob": ["Auto", "Home"]}
... )
>>> long = tri.to_long()
>>> list(long), long["lob"]([‘lob’, ‘origin’, ‘development’, ‘paid’], [‘Auto’, ‘Home’])
to_string()
The printout as text: the origin × development grid for a triangle
Usage
to_string(max_rows=..., max_cols=...)with one segment and one measure (as view), otherwise the summary table. Numbers are rounded for reading; view and summary give exact values.
Parameters
max_rows: int = 20-
Rows shown before the middle ones are left out; 0 for no limit.
max_cols: int = 12- Development ages shown before the middle ones are left out; 0 for no limit.
Returns
str
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long([2020, 2020, 2021], [12, 24, 12], {"paid": [1000.0, 1500.0, 1100.0]})
>>> print(tri.to_string())Triangle: paid (cumulative, valuation 2021-12) 12 24 2020 1,000 1,500 2021 1,100
view()
One segment and measure as an origin × development table.
Usage
view(column=None, **keys)Parameters
column: str = None-
The measure; may be left out when the triangle has one column.
**keys: value-
One value per key, such as
lob="Auto", compared asstr()of it. Keys not named may take any value, but the choice must leave one segment; a triangle with one segment needs none. A key named column cannot be chosen this way (select it first).
Returns
pandas.DataFrame or dict-
With pandas installed, a DataFrame with the origin labels as its index (named
origin), the ages in months as its columns (named development) andnanwhere a cell is not observed. Without pandas, a dict of lists as the other tables of this module:"origin", then one list per age keyed by the age.
Raises
ValueError- If a key, value or column is unknown, the keys match several segments, or the column is left out and there are several.
Examples
>>> from prospicio.reserving import Triangle
>>> tri = Triangle.from_long(
... [2020, 2020, 2021, 2020],
... [12, 24, 12, 12],
... {"paid": [100.0, 150.0, 110.0, 50.0]},
... keys={"lob": ["Auto", "Auto", "Auto", "Home"]},
... )
>>> v = tri.view(lob="Auto")
>>> list(v.index), list(v.columns), float(v.loc["2021", 12])([‘2020’, ‘2021’], [12, 24], 110.0)