## reserving.Triangle


A loss triangle with four axes: index (segment), column (measure), origin


Usage


``` python
reserving.Triangle()
```


and development age, in chainladder-python's order.

Segments are named by key columns such as `"lob"` and `"state"`: [keys](reserving.MackFit.md#prospicio.reserving.MackFit.keys) gives their names and [index](reserving.MackFit.md#prospicio.reserving.MackFit.index) one label per segment. A triangle without keys has one segment, `"Total"`.

Build one from a long table with [from_long](reserving.Triangle.md#prospicio.reserving.Triangle.from_long) or [from_frame](reserving.Triangle.md#prospicio.reserving.Triangle.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](reserving.Triangle.md#prospicio.reserving.Triangle.values); an observed zero stays zero.


## Examples

``` python
>>> 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)

``` python
>>> tri.origins, tri.development, tri.valuation
```

(\['2020', '2021'\], \[12, 24\], datetime.date(2021, 12, 31))

``` python
>>> tri.values[0][0]
```

\[\[100.0, 150.0\], \[110.0, nan\]\]


## Attributes

| Name | Description |
|----|----|
| [columns](#columns) | Measure column names. |
| [development](#development) | Development ages in months, youngest first. |
| [development_grain](#development_grain) | Development grain: `"Y"`, `"S"`, `"Q"` or `"M"`. |
| [index](#index) | Segment labels: a str each with one key, a tuple of key values with |
| [is_cumulative](#is_cumulative) | Whether the values are cumulative (otherwise incremental). |
| [keys](#keys) | Names of the key columns, in key order; empty without keys. |
| [origin_grain](#origin_grain) | Origin grain: `"Y"`, `"S"`, `"Q"` or `"M"`. |
| [origins](#origins) | Origin periods, oldest first: `"2021"`, `"2021H1"`, |
| [shape](#shape) | Axis lengths: `(index, column, origin, development)`. |
| [valuation](#valuation) | Valuation date of the latest diagonal: the last day of its month, |
| [values](#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()](#from_frame) | Builds a triangle from a data frame in long format. |
| [from_long()](#from_long) | Builds a triangle from the columns of a long table, one row per |
| [grain()](#grain) | The triangle at a coarser origin and/or development grain. |
| [group_by()](#group_by) | Sums the segments that share the values of [keys](reserving.MackFit.md#prospicio.reserving.MackFit.keys), dropping the |
| [latest_diagonal()](#latest_diagonal) | The latest observed value of each origin, as nested lists indexed |
| [link_ratios()](#link_ratios) | Age-to-age link ratios of the cumulative values. Development |
| [select()](#select) | The segments whose key values match, and the measure columns named. |
| [summary()](#summary) | One row per segment and measure: the key values, [column](models.Design.md#prospicio.models.Design.column), |
| [to_cumulative()](#to_cumulative) | Cumulative values: running sums of the observed increments. |
| [to_frame()](#to_frame) | The long table of [to_long](reserving.Triangle.md#prospicio.reserving.Triangle.to_long) as a pandas DataFrame. Needs pandas. |
| [to_incremental()](#to_incremental) | Incremental values: each observed value minus the previous observed |
| [to_long()](#to_long) | The triangle as a long table: a dict of equal-length lists with one |
| [to_string()](#to_string) | The printout as text: the origin × development grid for a triangle |
| [view()](#view) | One segment and measure as an origin × development table. |

------------------------------------------------------------------------


#### from_frame()


Builds a triangle from a data frame in long format.


Usage


``` python
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 dict`  

`origin: 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_valuation` is 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 = ``True`  

`development_is_valuation: bool = ``False`  


##### Returns


`Triangle`  


##### Raises


`ValueError`  
As for [from_long](reserving.Triangle.md#prospicio.reserving.Triangle.from_long).


##### Examples

``` python
>>> 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


``` python
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`, pandas `Timestamp`), or integer years.

`development: array - like`  
Development age of each row in months (12, 24, …), or its valuation date when `development_is_valuation` is 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); `None` and `nan` are 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](reserving.MackFit.md#prospicio.reserving.MackFit.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

``` python
>>> 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:

``` python
>>> 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


``` python
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](reserving.Triangle.md#prospicio.reserving.Triangle.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

``` python
>>> 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](reserving.MackFit.md#prospicio.reserving.MackFit.keys), dropping the


Usage


``` python
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

``` python
>>> 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\])

``` python
>>> tri.group_by([]).to_long()["paid"]
```

\[6.0\]

------------------------------------------------------------------------


#### latest_diagonal()


The latest observed value of each origin, as nested lists indexed


Usage


``` python
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


``` python
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


``` python
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](reserving.Triangle.md#prospicio.reserving.Triangle.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"` or `state=["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

``` python
>>> 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')\]

``` python
>>> 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](models.Design.md#prospicio.models.Design.column),


Usage


``` python
summary()
```


`n_origins` (origins with an observed value), `first_origin` and `last_origin` of those, [valuation](reserving.Triangle.md#prospicio.reserving.Triangle.valuation) (the last day of the latest valuation with an observed value), [latest](reserving.MackFit.md#prospicio.reserving.MackFit.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

``` python
>>> 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


``` python
to_cumulative()
```


##### Returns


`Triangle`  


------------------------------------------------------------------------


#### to_frame()


The long table of [to_long](reserving.Triangle.md#prospicio.reserving.Triangle.to_long) as a pandas DataFrame. Needs pandas.


Usage


``` python
to_frame()
```


##### Returns


`pandas.DataFrame`  


------------------------------------------------------------------------


#### to_incremental()


Incremental values: each observed value minus the previous observed


Usage


``` python
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


``` python
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](reserving.Triangle.md#prospicio.reserving.Triangle.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 `origin` or [development](reserving.MackFit.md#prospicio.reserving.MackFit.development).


##### Examples

``` python
>>> 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


``` python
to_string(max_rows=..., max_cols=...)
```


with one segment and one measure (as [view](reserving.Triangle.md#prospicio.reserving.Triangle.view)), otherwise the [summary](models.BayesGlmFit.md#prospicio.models.BayesGlmFit.summary) table. Numbers are rounded for reading; [view](reserving.Triangle.md#prospicio.reserving.Triangle.view) and [summary](models.BayesGlmFit.md#prospicio.models.BayesGlmFit.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

``` python
>>> 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


``` python
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 as `str()` 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](models.Design.md#prospicio.models.Design.column) cannot be chosen this way ([select](models.Design.md#prospicio.models.Design.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](reserving.MackFit.md#prospicio.reserving.MackFit.development)) and `nan` where 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

``` python
>>> 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)
