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.
Files changed (45) hide show
  1. experiencestudies-0.1.0/.github/workflows/ci.yml +44 -0
  2. experiencestudies-0.1.0/.github/workflows/release.yml +56 -0
  3. experiencestudies-0.1.0/.gitignore +66 -0
  4. experiencestudies-0.1.0/CHANGELOG.md +33 -0
  5. experiencestudies-0.1.0/LICENSE +21 -0
  6. experiencestudies-0.1.0/PKG-INFO +193 -0
  7. experiencestudies-0.1.0/README.md +154 -0
  8. experiencestudies-0.1.0/examples/README.md +58 -0
  9. experiencestudies-0.1.0/examples/_sample_data.py +223 -0
  10. experiencestudies-0.1.0/examples/claimant_concentration.py +74 -0
  11. experiencestudies-0.1.0/examples/credibility.py +60 -0
  12. experiencestudies-0.1.0/examples/experience_basics.py +65 -0
  13. experiencestudies-0.1.0/examples/lifecycle_and_banding.py +60 -0
  14. experiencestudies-0.1.0/examples/renewal.py +117 -0
  15. experiencestudies-0.1.0/examples/restatement.py +80 -0
  16. experiencestudies-0.1.0/examples/rolling_trend_monitor.py +111 -0
  17. experiencestudies-0.1.0/examples/trend_decomposition.py +92 -0
  18. experiencestudies-0.1.0/pyproject.toml +49 -0
  19. experiencestudies-0.1.0/src/experiencestudies/__init__.py +90 -0
  20. experiencestudies-0.1.0/src/experiencestudies/banding.py +59 -0
  21. experiencestudies-0.1.0/src/experiencestudies/claimants.py +151 -0
  22. experiencestudies-0.1.0/src/experiencestudies/cohorts.py +130 -0
  23. experiencestudies-0.1.0/src/experiencestudies/components.py +166 -0
  24. experiencestudies-0.1.0/src/experiencestudies/decomposition.py +263 -0
  25. experiencestudies-0.1.0/src/experiencestudies/expected.py +115 -0
  26. experiencestudies-0.1.0/src/experiencestudies/experience.py +196 -0
  27. experiencestudies-0.1.0/src/experiencestudies/forecast.py +113 -0
  28. experiencestudies-0.1.0/src/experiencestudies/frame.py +825 -0
  29. experiencestudies-0.1.0/src/experiencestudies/py.typed +0 -0
  30. experiencestudies-0.1.0/src/experiencestudies/rolling.py +88 -0
  31. experiencestudies-0.1.0/tests/test_ave_column_order.py +77 -0
  32. experiencestudies-0.1.0/tests/test_cohort_coverage.py +74 -0
  33. experiencestudies-0.1.0/tests/test_compare_actual_to_expected.py +118 -0
  34. experiencestudies-0.1.0/tests/test_decomposition.py +240 -0
  35. experiencestudies-0.1.0/tests/test_examples_run.py +22 -0
  36. experiencestudies-0.1.0/tests/test_experience_adjust_facade.py +23 -0
  37. experiencestudies-0.1.0/tests/test_experience_complete_facade.py +103 -0
  38. experiencestudies-0.1.0/tests/test_experience_components.py +98 -0
  39. experiencestudies-0.1.0/tests/test_experience_count_methods.py +129 -0
  40. experiencestudies-0.1.0/tests/test_experience_deseasonalize_facade.py +128 -0
  41. experiencestudies-0.1.0/tests/test_experience_facade_extended.py +90 -0
  42. experiencestudies-0.1.0/tests/test_frame_and_date_ranges.py +79 -0
  43. experiencestudies-0.1.0/tests/test_restored_integration.py +124 -0
  44. experiencestudies-0.1.0/tests/test_rolling_cohort_forecast.py +57 -0
  45. 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
+ [![CI](https://github.com/OpenActuarial/experiencestudies/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenActuarial/experiencestudies/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/experiencestudies)](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
+ [![CI](https://github.com/OpenActuarial/experiencestudies/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenActuarial/experiencestudies/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/experiencestudies)](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.