experiencestudies 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- experiencestudies-0.1.0/.github/workflows/ci.yml +44 -0
- experiencestudies-0.1.0/.github/workflows/release.yml +56 -0
- experiencestudies-0.1.0/.gitignore +66 -0
- experiencestudies-0.1.0/CHANGELOG.md +33 -0
- experiencestudies-0.1.0/LICENSE +21 -0
- experiencestudies-0.1.0/PKG-INFO +193 -0
- experiencestudies-0.1.0/README.md +154 -0
- experiencestudies-0.1.0/examples/README.md +58 -0
- experiencestudies-0.1.0/examples/_sample_data.py +223 -0
- experiencestudies-0.1.0/examples/claimant_concentration.py +74 -0
- experiencestudies-0.1.0/examples/credibility.py +60 -0
- experiencestudies-0.1.0/examples/experience_basics.py +65 -0
- experiencestudies-0.1.0/examples/lifecycle_and_banding.py +60 -0
- experiencestudies-0.1.0/examples/renewal.py +117 -0
- experiencestudies-0.1.0/examples/restatement.py +80 -0
- experiencestudies-0.1.0/examples/rolling_trend_monitor.py +111 -0
- experiencestudies-0.1.0/examples/trend_decomposition.py +92 -0
- experiencestudies-0.1.0/pyproject.toml +49 -0
- experiencestudies-0.1.0/src/experiencestudies/__init__.py +90 -0
- experiencestudies-0.1.0/src/experiencestudies/banding.py +59 -0
- experiencestudies-0.1.0/src/experiencestudies/claimants.py +151 -0
- experiencestudies-0.1.0/src/experiencestudies/cohorts.py +130 -0
- experiencestudies-0.1.0/src/experiencestudies/components.py +166 -0
- experiencestudies-0.1.0/src/experiencestudies/decomposition.py +263 -0
- experiencestudies-0.1.0/src/experiencestudies/expected.py +115 -0
- experiencestudies-0.1.0/src/experiencestudies/experience.py +196 -0
- experiencestudies-0.1.0/src/experiencestudies/forecast.py +113 -0
- experiencestudies-0.1.0/src/experiencestudies/frame.py +825 -0
- experiencestudies-0.1.0/src/experiencestudies/py.typed +0 -0
- experiencestudies-0.1.0/src/experiencestudies/rolling.py +88 -0
- experiencestudies-0.1.0/tests/test_ave_column_order.py +77 -0
- experiencestudies-0.1.0/tests/test_cohort_coverage.py +74 -0
- experiencestudies-0.1.0/tests/test_compare_actual_to_expected.py +118 -0
- experiencestudies-0.1.0/tests/test_decomposition.py +240 -0
- experiencestudies-0.1.0/tests/test_examples_run.py +22 -0
- experiencestudies-0.1.0/tests/test_experience_adjust_facade.py +23 -0
- experiencestudies-0.1.0/tests/test_experience_complete_facade.py +103 -0
- experiencestudies-0.1.0/tests/test_experience_components.py +98 -0
- experiencestudies-0.1.0/tests/test_experience_count_methods.py +129 -0
- experiencestudies-0.1.0/tests/test_experience_deseasonalize_facade.py +128 -0
- experiencestudies-0.1.0/tests/test_experience_facade_extended.py +90 -0
- experiencestudies-0.1.0/tests/test_frame_and_date_ranges.py +79 -0
- experiencestudies-0.1.0/tests/test_restored_integration.py +124 -0
- experiencestudies-0.1.0/tests/test_rolling_cohort_forecast.py +57 -0
- experiencestudies-0.1.0/tests/test_view_column_order.py +110 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# CI for experiencestudies. Depends on actuarialpy, which is pulled from PyPI by
|
|
2
|
+
# the editable install below; publish a compatible actuarialpy (>=0.42) first.
|
|
3
|
+
name: CI
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest]
|
|
16
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
runs-on: ${{ matrix.os }}
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- name: Install
|
|
24
|
+
run: |
|
|
25
|
+
python -m pip install --upgrade pip
|
|
26
|
+
pip install -e ".[dev]"
|
|
27
|
+
- name: Test
|
|
28
|
+
run: pytest -q
|
|
29
|
+
build:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: actions/setup-python@v5
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.12"
|
|
36
|
+
- name: Build sdist and wheel
|
|
37
|
+
run: |
|
|
38
|
+
python -m pip install --upgrade pip build twine
|
|
39
|
+
python -m build
|
|
40
|
+
twine check dist/*
|
|
41
|
+
- uses: actions/upload-artifact@v4
|
|
42
|
+
with:
|
|
43
|
+
name: dist
|
|
44
|
+
path: dist/
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Tag-driven release via PyPI Trusted Publishing (no tokens, no twine
|
|
2
|
+
# passwords). One-time setup per package:
|
|
3
|
+
# 1. On PyPI: project -> Publishing -> add a Trusted Publisher:
|
|
4
|
+
# owner OpenActuarial, repository experiencestudies, workflow release.yml,
|
|
5
|
+
# environment pypi.
|
|
6
|
+
# 2. On GitHub: repo Settings -> Environments -> create "pypi".
|
|
7
|
+
# Then: git tag v{version} && git push --tags
|
|
8
|
+
name: Release
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
tags: ["v*"]
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- run: |
|
|
23
|
+
python -m pip install --upgrade pip build twine
|
|
24
|
+
python -m build
|
|
25
|
+
twine check dist/*
|
|
26
|
+
- uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: dist
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
needs: build
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
environment: pypi
|
|
35
|
+
permissions:
|
|
36
|
+
id-token: write
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/download-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
43
|
+
|
|
44
|
+
github-release:
|
|
45
|
+
needs: publish
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
permissions:
|
|
48
|
+
contents: write
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
- name: Extract latest changelog section
|
|
52
|
+
run: |
|
|
53
|
+
awk '/^## /{n++} n==1' CHANGELOG.md > release_notes.md
|
|
54
|
+
- uses: softprops/action-gh-release@v2
|
|
55
|
+
with:
|
|
56
|
+
body_path: release_notes.md
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Python build artifacts
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
*.egg
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
ENV/
|
|
18
|
+
|
|
19
|
+
# Test and coverage outputs
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.coverage
|
|
22
|
+
coverage.xml
|
|
23
|
+
htmlcov/
|
|
24
|
+
.tox/
|
|
25
|
+
.nox/
|
|
26
|
+
|
|
27
|
+
# Type checker / linter caches
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.pyright/
|
|
30
|
+
.ruff_cache/
|
|
31
|
+
|
|
32
|
+
# Jupyter notebooks
|
|
33
|
+
.ipynb_checkpoints/
|
|
34
|
+
|
|
35
|
+
# Local environment files
|
|
36
|
+
.env
|
|
37
|
+
.env.*
|
|
38
|
+
*.local
|
|
39
|
+
|
|
40
|
+
# IDE / editor files
|
|
41
|
+
.vscode/
|
|
42
|
+
.idea/
|
|
43
|
+
*.swp
|
|
44
|
+
*.swo
|
|
45
|
+
.DS_Store
|
|
46
|
+
|
|
47
|
+
# Logs
|
|
48
|
+
*.log
|
|
49
|
+
|
|
50
|
+
# Temporary files
|
|
51
|
+
tmp/
|
|
52
|
+
temp/
|
|
53
|
+
*.tmp
|
|
54
|
+
|
|
55
|
+
# Data files generated locally
|
|
56
|
+
*.csv
|
|
57
|
+
*.xlsx
|
|
58
|
+
*.xls
|
|
59
|
+
*.parquet
|
|
60
|
+
*.feather
|
|
61
|
+
*.pkl
|
|
62
|
+
*.pickle
|
|
63
|
+
|
|
64
|
+
# Keep example datasets only if intentionally committed
|
|
65
|
+
# !examples/*.csv
|
|
66
|
+
# !examples/*.xlsx
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
Initial release. `experiencestudies` is the experience reporting and analysis
|
|
6
|
+
layer extracted from `actuarialpy` 0.42.0; it builds on the `actuarialpy`
|
|
7
|
+
primitives and depends on `actuarialpy>=0.42`.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- The fluent `Experience` object (construct once with expense/revenue/exposure/
|
|
12
|
+
date roles, then chain `.by()`, `.rolling()`, `.deseasonalize()`, `.complete()`,
|
|
13
|
+
`.adjust()`, ...; every method returns a new `Experience` so restatements
|
|
14
|
+
compose).
|
|
15
|
+
- Experience summaries and views: `summarize_experience`, `summarize_views`,
|
|
16
|
+
`status_summary`.
|
|
17
|
+
- Actual-versus-expected and forecasting: `summarize_actual_vs_expected`,
|
|
18
|
+
`expected_from_rate`, `forecast_from_rate`, `forecast_experience`,
|
|
19
|
+
`compare_actual_to_expected`.
|
|
20
|
+
- Claimant and concentration analysis: `summarize_claimants`, `top_claimants`,
|
|
21
|
+
`large_claimant_flags`, `claim_concentration`.
|
|
22
|
+
- Cohort and duration studies: `cohort_summary`, `cohort_summary_by_period`,
|
|
23
|
+
`duration_summary`.
|
|
24
|
+
- Driver/component and frequency-severity decomposition:
|
|
25
|
+
`component_driver_analysis`, `component_trend`, `summarize_components`,
|
|
26
|
+
`decompose_per_exposure_trend`, `frequency_severity_summary`.
|
|
27
|
+
- Rolling monitors: `rolling_summary`.
|
|
28
|
+
- Banded summaries: `summarize_by_band` (assigns bands via
|
|
29
|
+
`actuarialpy.assign_band`, then summarizes experience per band).
|
|
30
|
+
|
|
31
|
+
All signatures match the pre-split `actuarialpy` versions of these functions, so
|
|
32
|
+
migrating is an import change: import these names from `experiencestudies`
|
|
33
|
+
instead of `actuarialpy`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Bryant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: experiencestudies
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Experience reporting and analysis on tidy tables: experience summaries and views, actual-versus-expected, claimant and cohort studies, driver and frequency-severity decomposition, rolling monitors, banded summaries, and the fluent Experience object. Built on actuarialpy.
|
|
5
|
+
Project-URL: Homepage, https://github.com/OpenActuarial/experiencestudies
|
|
6
|
+
Project-URL: Documentation, https://openactuarial.org/experiencestudies.html
|
|
7
|
+
Project-URL: Repository, https://github.com/OpenActuarial/experiencestudies
|
|
8
|
+
Project-URL: Issues, https://github.com/OpenActuarial/experiencestudies/issues
|
|
9
|
+
Author: Michael Bryant
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: actuarial,analytics,claims,experience analysis,insurance,loss ratio,risk
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: actuarialpy>=0.42
|
|
28
|
+
Requires-Dist: numpy>=1.23
|
|
29
|
+
Requires-Dist: pandas>=1.5
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: build>=1; extra == 'dev'
|
|
32
|
+
Requires-Dist: hypothesis>=6; extra == 'dev'
|
|
33
|
+
Requires-Dist: openpyxl>=3.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
35
|
+
Requires-Dist: twine>=5; extra == 'dev'
|
|
36
|
+
Provides-Extra: excel
|
|
37
|
+
Requires-Dist: openpyxl>=3.1; extra == 'excel'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# experiencestudies
|
|
41
|
+
|
|
42
|
+
[](https://github.com/OpenActuarial/experiencestudies/actions/workflows/ci.yml) [](https://pypi.org/project/experiencestudies/)
|
|
43
|
+
|
|
44
|
+
Experience reporting and analysis on claims, exposure, and premium data: experience
|
|
45
|
+
summaries and views, actual-versus-expected, claimant and concentration analysis, cohort
|
|
46
|
+
and duration studies, driver and frequency-severity decomposition, rolling monitors,
|
|
47
|
+
banded summaries, and simple forecasting — tied together by the fluent `Experience`
|
|
48
|
+
object. Built on [`actuarialpy`](https://github.com/OpenActuarial/actuarialpy), which
|
|
49
|
+
supplies the underlying primitives (ratios, trend, credibility, completion, seasonality,
|
|
50
|
+
financial mathematics). Every result is a DataFrame or Series.
|
|
51
|
+
|
|
52
|
+
## Contents
|
|
53
|
+
|
|
54
|
+
- [Overview](#overview)
|
|
55
|
+
- [Installation](#installation)
|
|
56
|
+
- [Quick start](#quick-start)
|
|
57
|
+
- [The `Experience` object](#the-experience-object)
|
|
58
|
+
- [Experience summaries and views](#experience-summaries-and-views)
|
|
59
|
+
- [Actual versus expected and forecasting](#actual-versus-expected-and-forecasting)
|
|
60
|
+
- [Claimant and concentration analysis](#claimant-and-concentration-analysis)
|
|
61
|
+
- [Cohort and duration studies](#cohort-and-duration-studies)
|
|
62
|
+
- [Driver and frequency-severity decomposition](#driver-and-frequency-severity-decomposition)
|
|
63
|
+
- [Rolling monitors](#rolling-monitors)
|
|
64
|
+
- [Banded summaries](#banded-summaries)
|
|
65
|
+
- [Relationship to actuarialpy](#relationship-to-actuarialpy)
|
|
66
|
+
|
|
67
|
+
## Overview
|
|
68
|
+
|
|
69
|
+
`experiencestudies` is the study layer that sits on top of the `actuarialpy` primitives.
|
|
70
|
+
Where `actuarialpy` answers "what is the loss ratio / development factor / credibility
|
|
71
|
+
weight for this table?", `experiencestudies` answers "how is this block performing, why is
|
|
72
|
+
it moving, and where is the risk concentrated?". It does not perform data preparation or
|
|
73
|
+
encode filed methodology: the caller supplies the tidy table and selects the analysis.
|
|
74
|
+
|
|
75
|
+
There are two interfaces:
|
|
76
|
+
|
|
77
|
+
- **Free functions** — `summarize_experience`, `summarize_actual_vs_expected`,
|
|
78
|
+
`summarize_claimants`, `cohort_summary`, `decompose_per_exposure_trend`,
|
|
79
|
+
`frequency_severity_summary`, `rolling_summary`, `summarize_by_band`, and the
|
|
80
|
+
forecasting helpers. Each takes a DataFrame and returns a DataFrame.
|
|
81
|
+
- **The `Experience` object** — a fluent wrapper that remembers the expense, revenue,
|
|
82
|
+
exposure, and date columns once, then exposes the same analyses as chainable methods
|
|
83
|
+
(`.by()`, `.rolling()`, `.deseasonalize()`, `.complete()`, `.adjust()`, ...), each
|
|
84
|
+
returning a new `Experience` so restatements compose.
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install experiencestudies
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This pulls in `actuarialpy` (>= 0.42) automatically. For the Excel report writer, install
|
|
93
|
+
the `excel` extra:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install "experiencestudies[excel]"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quick start
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import pandas as pd
|
|
103
|
+
from experiencestudies import Experience, summarize_experience
|
|
104
|
+
|
|
105
|
+
df = pd.DataFrame({
|
|
106
|
+
"month": pd.date_range("2024-01-01", periods=12, freq="MS"),
|
|
107
|
+
"lob": ["med"] * 12,
|
|
108
|
+
"claims": [820, 910, 875, 1010, 990, 1105, 1080, 1240, 1180, 1035, 995, 1150.0],
|
|
109
|
+
"premium": [1500] * 12,
|
|
110
|
+
"member_months": [1000] * 12,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
# free-function form
|
|
114
|
+
summary = summarize_experience(
|
|
115
|
+
df, groupby="lob",
|
|
116
|
+
expense_cols="claims", revenue_cols="premium", exposure_cols="member_months",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# fluent form — same result, plus composable views
|
|
120
|
+
exp = Experience(df, expense="claims", revenue="premium",
|
|
121
|
+
exposure="member_months", date="month")
|
|
122
|
+
by_lob = exp.by("lob")
|
|
123
|
+
trailing = exp.rolling(3)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## The `Experience` object
|
|
127
|
+
|
|
128
|
+
Construct once with the column roles, then chain. Each method returns a new `Experience`
|
|
129
|
+
(or a summary DataFrame for the terminal views), so adjustments and restatements compose
|
|
130
|
+
without mutating the source:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
restated = (
|
|
134
|
+
exp.adjust(1.03) # apply a 3% trend/restatement factor
|
|
135
|
+
.deseasonalize(seasonal_factors) # divide out a seasonal shape
|
|
136
|
+
.complete(completion_factors, valuation_date="2024-12-31") # gross up to ultimate
|
|
137
|
+
)
|
|
138
|
+
restated.by("lob") # terminal summary
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The seasonal factors and completion factors come from `actuarialpy`
|
|
142
|
+
(`seasonality_factors`, `completion_factors`); `experiencestudies` applies them through
|
|
143
|
+
the fluent lens.
|
|
144
|
+
|
|
145
|
+
## Experience summaries and views
|
|
146
|
+
|
|
147
|
+
`summarize_experience` and the `Experience.by()` / `.views()` methods produce grouped
|
|
148
|
+
aggregates with loss ratio and per-exposure metrics. `status_summary` and
|
|
149
|
+
`summarize_views` give status- and view-oriented cuts of the same underlying rollup.
|
|
150
|
+
|
|
151
|
+
## Actual versus expected and forecasting
|
|
152
|
+
|
|
153
|
+
`summarize_actual_vs_expected` compares realized experience against an expected column.
|
|
154
|
+
`expected_from_rate` and `forecast_from_rate` build expected/forecast values from a rate
|
|
155
|
+
basis; `forecast_experience` projects an experience frame forward; and
|
|
156
|
+
`compare_actual_to_expected` reports the variance.
|
|
157
|
+
|
|
158
|
+
## Claimant and concentration analysis
|
|
159
|
+
|
|
160
|
+
`summarize_claimants`, `top_claimants`, `large_claimant_flags`, and `claim_concentration`
|
|
161
|
+
identify and rank large claimants and measure how concentrated losses are (e.g. the share
|
|
162
|
+
carried by the top *n*).
|
|
163
|
+
|
|
164
|
+
## Cohort and duration studies
|
|
165
|
+
|
|
166
|
+
`cohort_summary`, `cohort_summary_by_period`, and `duration_summary` track experience by
|
|
167
|
+
entry cohort and by duration since entry.
|
|
168
|
+
|
|
169
|
+
## Driver and frequency-severity decomposition
|
|
170
|
+
|
|
171
|
+
`component_driver_analysis`, `component_trend`, and `summarize_components` attribute
|
|
172
|
+
movement to its components; `decompose_per_exposure_trend` splits a per-exposure trend into
|
|
173
|
+
frequency and severity contributions, and `frequency_severity_summary` reports the two
|
|
174
|
+
sides side by side.
|
|
175
|
+
|
|
176
|
+
## Rolling monitors
|
|
177
|
+
|
|
178
|
+
`rolling_summary` (and `Experience.rolling(window)`) produce trailing-window rollups for
|
|
179
|
+
monitoring emerging experience.
|
|
180
|
+
|
|
181
|
+
## Banded summaries
|
|
182
|
+
|
|
183
|
+
`summarize_by_band` assigns rows to size bands (via `actuarialpy.assign_band`) and
|
|
184
|
+
summarizes experience within each band, preserving band order and surfacing empty bands.
|
|
185
|
+
|
|
186
|
+
## Relationship to actuarialpy
|
|
187
|
+
|
|
188
|
+
`experiencestudies` depends on `actuarialpy` and never the other way around — the
|
|
189
|
+
dependency is strictly one-directional. The size-banding split is the clearest example:
|
|
190
|
+
the `assign_band` primitive lives in `actuarialpy`, while `summarize_by_band` (which needs
|
|
191
|
+
an experience summary) lives here. Because the study layer imports from the primitive
|
|
192
|
+
layer, publish a compatible `actuarialpy` (>= 0.42) before releasing a new
|
|
193
|
+
`experiencestudies`.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# experiencestudies
|
|
2
|
+
|
|
3
|
+
[](https://github.com/OpenActuarial/experiencestudies/actions/workflows/ci.yml) [](https://pypi.org/project/experiencestudies/)
|
|
4
|
+
|
|
5
|
+
Experience reporting and analysis on claims, exposure, and premium data: experience
|
|
6
|
+
summaries and views, actual-versus-expected, claimant and concentration analysis, cohort
|
|
7
|
+
and duration studies, driver and frequency-severity decomposition, rolling monitors,
|
|
8
|
+
banded summaries, and simple forecasting — tied together by the fluent `Experience`
|
|
9
|
+
object. Built on [`actuarialpy`](https://github.com/OpenActuarial/actuarialpy), which
|
|
10
|
+
supplies the underlying primitives (ratios, trend, credibility, completion, seasonality,
|
|
11
|
+
financial mathematics). Every result is a DataFrame or Series.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- [Overview](#overview)
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Quick start](#quick-start)
|
|
18
|
+
- [The `Experience` object](#the-experience-object)
|
|
19
|
+
- [Experience summaries and views](#experience-summaries-and-views)
|
|
20
|
+
- [Actual versus expected and forecasting](#actual-versus-expected-and-forecasting)
|
|
21
|
+
- [Claimant and concentration analysis](#claimant-and-concentration-analysis)
|
|
22
|
+
- [Cohort and duration studies](#cohort-and-duration-studies)
|
|
23
|
+
- [Driver and frequency-severity decomposition](#driver-and-frequency-severity-decomposition)
|
|
24
|
+
- [Rolling monitors](#rolling-monitors)
|
|
25
|
+
- [Banded summaries](#banded-summaries)
|
|
26
|
+
- [Relationship to actuarialpy](#relationship-to-actuarialpy)
|
|
27
|
+
|
|
28
|
+
## Overview
|
|
29
|
+
|
|
30
|
+
`experiencestudies` is the study layer that sits on top of the `actuarialpy` primitives.
|
|
31
|
+
Where `actuarialpy` answers "what is the loss ratio / development factor / credibility
|
|
32
|
+
weight for this table?", `experiencestudies` answers "how is this block performing, why is
|
|
33
|
+
it moving, and where is the risk concentrated?". It does not perform data preparation or
|
|
34
|
+
encode filed methodology: the caller supplies the tidy table and selects the analysis.
|
|
35
|
+
|
|
36
|
+
There are two interfaces:
|
|
37
|
+
|
|
38
|
+
- **Free functions** — `summarize_experience`, `summarize_actual_vs_expected`,
|
|
39
|
+
`summarize_claimants`, `cohort_summary`, `decompose_per_exposure_trend`,
|
|
40
|
+
`frequency_severity_summary`, `rolling_summary`, `summarize_by_band`, and the
|
|
41
|
+
forecasting helpers. Each takes a DataFrame and returns a DataFrame.
|
|
42
|
+
- **The `Experience` object** — a fluent wrapper that remembers the expense, revenue,
|
|
43
|
+
exposure, and date columns once, then exposes the same analyses as chainable methods
|
|
44
|
+
(`.by()`, `.rolling()`, `.deseasonalize()`, `.complete()`, `.adjust()`, ...), each
|
|
45
|
+
returning a new `Experience` so restatements compose.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install experiencestudies
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This pulls in `actuarialpy` (>= 0.42) automatically. For the Excel report writer, install
|
|
54
|
+
the `excel` extra:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install "experiencestudies[excel]"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import pandas as pd
|
|
64
|
+
from experiencestudies import Experience, summarize_experience
|
|
65
|
+
|
|
66
|
+
df = pd.DataFrame({
|
|
67
|
+
"month": pd.date_range("2024-01-01", periods=12, freq="MS"),
|
|
68
|
+
"lob": ["med"] * 12,
|
|
69
|
+
"claims": [820, 910, 875, 1010, 990, 1105, 1080, 1240, 1180, 1035, 995, 1150.0],
|
|
70
|
+
"premium": [1500] * 12,
|
|
71
|
+
"member_months": [1000] * 12,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
# free-function form
|
|
75
|
+
summary = summarize_experience(
|
|
76
|
+
df, groupby="lob",
|
|
77
|
+
expense_cols="claims", revenue_cols="premium", exposure_cols="member_months",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# fluent form — same result, plus composable views
|
|
81
|
+
exp = Experience(df, expense="claims", revenue="premium",
|
|
82
|
+
exposure="member_months", date="month")
|
|
83
|
+
by_lob = exp.by("lob")
|
|
84
|
+
trailing = exp.rolling(3)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## The `Experience` object
|
|
88
|
+
|
|
89
|
+
Construct once with the column roles, then chain. Each method returns a new `Experience`
|
|
90
|
+
(or a summary DataFrame for the terminal views), so adjustments and restatements compose
|
|
91
|
+
without mutating the source:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
restated = (
|
|
95
|
+
exp.adjust(1.03) # apply a 3% trend/restatement factor
|
|
96
|
+
.deseasonalize(seasonal_factors) # divide out a seasonal shape
|
|
97
|
+
.complete(completion_factors, valuation_date="2024-12-31") # gross up to ultimate
|
|
98
|
+
)
|
|
99
|
+
restated.by("lob") # terminal summary
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The seasonal factors and completion factors come from `actuarialpy`
|
|
103
|
+
(`seasonality_factors`, `completion_factors`); `experiencestudies` applies them through
|
|
104
|
+
the fluent lens.
|
|
105
|
+
|
|
106
|
+
## Experience summaries and views
|
|
107
|
+
|
|
108
|
+
`summarize_experience` and the `Experience.by()` / `.views()` methods produce grouped
|
|
109
|
+
aggregates with loss ratio and per-exposure metrics. `status_summary` and
|
|
110
|
+
`summarize_views` give status- and view-oriented cuts of the same underlying rollup.
|
|
111
|
+
|
|
112
|
+
## Actual versus expected and forecasting
|
|
113
|
+
|
|
114
|
+
`summarize_actual_vs_expected` compares realized experience against an expected column.
|
|
115
|
+
`expected_from_rate` and `forecast_from_rate` build expected/forecast values from a rate
|
|
116
|
+
basis; `forecast_experience` projects an experience frame forward; and
|
|
117
|
+
`compare_actual_to_expected` reports the variance.
|
|
118
|
+
|
|
119
|
+
## Claimant and concentration analysis
|
|
120
|
+
|
|
121
|
+
`summarize_claimants`, `top_claimants`, `large_claimant_flags`, and `claim_concentration`
|
|
122
|
+
identify and rank large claimants and measure how concentrated losses are (e.g. the share
|
|
123
|
+
carried by the top *n*).
|
|
124
|
+
|
|
125
|
+
## Cohort and duration studies
|
|
126
|
+
|
|
127
|
+
`cohort_summary`, `cohort_summary_by_period`, and `duration_summary` track experience by
|
|
128
|
+
entry cohort and by duration since entry.
|
|
129
|
+
|
|
130
|
+
## Driver and frequency-severity decomposition
|
|
131
|
+
|
|
132
|
+
`component_driver_analysis`, `component_trend`, and `summarize_components` attribute
|
|
133
|
+
movement to its components; `decompose_per_exposure_trend` splits a per-exposure trend into
|
|
134
|
+
frequency and severity contributions, and `frequency_severity_summary` reports the two
|
|
135
|
+
sides side by side.
|
|
136
|
+
|
|
137
|
+
## Rolling monitors
|
|
138
|
+
|
|
139
|
+
`rolling_summary` (and `Experience.rolling(window)`) produce trailing-window rollups for
|
|
140
|
+
monitoring emerging experience.
|
|
141
|
+
|
|
142
|
+
## Banded summaries
|
|
143
|
+
|
|
144
|
+
`summarize_by_band` assigns rows to size bands (via `actuarialpy.assign_band`) and
|
|
145
|
+
summarizes experience within each band, preserving band order and surfacing empty bands.
|
|
146
|
+
|
|
147
|
+
## Relationship to actuarialpy
|
|
148
|
+
|
|
149
|
+
`experiencestudies` depends on `actuarialpy` and never the other way around — the
|
|
150
|
+
dependency is strictly one-directional. The size-banding split is the clearest example:
|
|
151
|
+
the `assign_band` primitive lives in `actuarialpy`, while `summarize_by_band` (which needs
|
|
152
|
+
an experience summary) lives here. Because the study layer imports from the primitive
|
|
153
|
+
layer, publish a compatible `actuarialpy` (>= 0.42) before releasing a new
|
|
154
|
+
`experiencestudies`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# experiencestudies examples
|
|
2
|
+
|
|
3
|
+
Self-contained, runnable examples for the experience-analysis surfaces of
|
|
4
|
+
`experiencestudies` and its fluent `Experience` object. Each script generates its own small
|
|
5
|
+
synthetic data (via `_sample_data.py`) and prints a short report, so they run with nothing
|
|
6
|
+
but the package installed:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install experiencestudies
|
|
10
|
+
python renewal.py
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Every script is standalone — run any one directly, in any order. They import the underlying
|
|
14
|
+
primitives (`loss_ratio`, `completion_factors`, `fit_trend`, ...) from `actuarialpy` and the
|
|
15
|
+
analysis layer (`Experience`, `decompose_per_exposure_trend`, ...) from `experiencestudies`.
|
|
16
|
+
|
|
17
|
+
The walkthroughs are health-flavored (member-months, PMPM print labels) as a concrete book to
|
|
18
|
+
work through; the core they exercise is domain-agnostic, and the domain vocabulary lives
|
|
19
|
+
entirely in these callers.
|
|
20
|
+
|
|
21
|
+
**Start here:** [`renewal.py`](renewal.py) is the end-to-end study — it threads the individual
|
|
22
|
+
surfaces below into a single group renewal, from experience to indicated rate change. The
|
|
23
|
+
others each focus on one surface.
|
|
24
|
+
|
|
25
|
+
| Script | Surface | What it shows |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `renewal.py` | **end-to-end** | a full group renewal: complete → trend → relativities (`adjust`) → pool large claimants → credibility-blend with the manual → load → `indicated_change` |
|
|
28
|
+
| `experience_basics.py` | `Experience` facade, metrics | Bind roles once; `.by`, `.views`; metric primitives (`loss_ratio`, `per_exposure`, `pure_premium`) |
|
|
29
|
+
| `claimant_concentration.py` | claimants, pooling | `claim_concentration`, `top_claimants`, `large_claimant_flags`, `pool_losses`, `excess_over_threshold` |
|
|
30
|
+
| `restatement.py` | adjustments | `Experience.adjust` chain — scalar trend, per-region and per-line relativities, with a cumulative `audit_col` |
|
|
31
|
+
| `rolling_trend_monitor.py` | rolling, decomposition | a monthly change-in-trend monitor: each evaluation month compares the trailing-12 window to the same window a year earlier via `decompose_per_exposure_trend`, reporting whether the year-over-year move is frequency- or severity-driven |
|
|
32
|
+
| `trend_decomposition.py` | decomposition | `decompose_per_exposure_trend` two-way (frequency × severity), and the three-way `mix_by=` split — adds a mix term via LMDI, reconciling exactly; plus the `Experience.decompose_trend` facade (same split with columns bound once) |
|
|
33
|
+
| `credibility.py` | credibility | `credibility_weighted_estimate`, `Experience.credibility_weighted` |
|
|
34
|
+
| `lifecycle_and_banding.py` | lifecycle, banding | `derive_status`, `Experience.by_status`, `Experience.by_band` (banding via `actuarialpy.assign_band`) |
|
|
35
|
+
|
|
36
|
+
## The sample data
|
|
37
|
+
|
|
38
|
+
`_sample_data.py` (not part of the library) provides deterministic generators shared by these
|
|
39
|
+
scripts:
|
|
40
|
+
|
|
41
|
+
- **`sample_member_months()`** — a member-month experience frame spanning 2024–2025: claim
|
|
42
|
+
components, rebates, non-FFS expense, premium, per-group effective/termination dates, and a
|
|
43
|
+
subscriber count. A few members incur catastrophic claims so the concentration and pooling
|
|
44
|
+
examples have a real tail.
|
|
45
|
+
- **`sample_seasonal_panel()`** — a monthly claims panel by line of business and product over
|
|
46
|
+
four years, with a real month-of-year seasonal pattern, membership growth, and a cost trend.
|
|
47
|
+
- **`sample_trend_cells()`** — a deterministic two-period (2024/2025) claims panel split into
|
|
48
|
+
morbidity-segment × region cells, with uniform within-cell frequency (+3%) and unit-cost
|
|
49
|
+
(+4%) trend and an enrollment shift toward the High segment, so the book-wide two-way
|
|
50
|
+
overstates both drivers and the mix term recovers the difference; carries `claim_count`,
|
|
51
|
+
`allowed`, and `premium` for both the free `decompose_per_exposure_trend` and the
|
|
52
|
+
`Experience.decompose_trend` facade.
|
|
53
|
+
|
|
54
|
+
## Note
|
|
55
|
+
|
|
56
|
+
These build on the `actuarialpy` primitives and mirror the worked examples shipped alongside
|
|
57
|
+
the sibling packages (`lossmodels`, `risksim`, `extremeloss`). For an end-to-end application
|
|
58
|
+
that wires the packages together, see the high-cost-claimant cost-model project.
|