Overview¶
The OpenActuarial packages span a connected analytical workflow—from raw experience to portfolio capital—through eight focused, modular libraries. Each package can be installed individually. actuarialpy provides the shared calculation primitives, and the experience, projection, and pricing layers build on it.
Division of labor¶
actuarialpy — the shared primitives: ratios and per-exposure metrics, chain-ladder development and IBNR, credibility, trend, seasonality, financial mathematics (time value of money), exposure / age and lifecycle bases, size banding, pooling, margins, and weighted rollups. Pure calculation on numpy and pandas — no I/O, no composed reports — which
experiencestudies,projectionmodels, andratingmodelsbuild on directly.experiencestudies — the study layer: experience summaries and views, actual-versus-expected and simple forecasting, claimant and concentration analysis, cohort and duration studies, driver and frequency–severity decomposition, rolling monitors, banded summaries, the two-tier underwriting income statement, and the Excel report writer — study functions over the canonical
actuarialpy.Experience.projectionmodels — the projection layer: claim, premium, and expense projections on supplied exposure, organized as concrete workflows — the
projectentrypoint overClaimProjection,PremiumProjectionwith effective-datedRenewalRateActions,ExpenseProjection, projection horizons and date cohorts, scenario adjustments, and results that summarize without averaging ratios or duplicating exposure.ratingmodels — the pricing layer: manual and experience rate build-up, credibility blending, rate indication and rate-change decomposition, GLM relativities with diagnostics and confidence intervals, frequency–severity models, credibility-smoothed factors, validation splits and tables, renewal constraints, rate-dislocation reporting, and pricing scenarios with closed-form margin solves.
reservingmodels — the reserving layer: claims development and stochastic reserve estimation. The deterministic engine — chain-ladder development, completion factors, Bornhuetter–Ferguson, Benktander, Cape Cod, and Mack standard errors — is re-exported from
actuarialpy, where those triangle primitives already live; on top of it, the over-dispersed-Poisson bootstrap of the full predictive reserve distribution, residual diagnostics, and a.sampleseam so a reserve is arisksimportfolio component.lossmodels — loss-distribution modeling: severity and frequency fitting (complete data or under deductibles and limits), model selection and diagnostics, and aggregate loss.
extremeloss — the tail: peaks-over-threshold / GPD estimation and large-claim loading.
risksim — portfolio Monte Carlo and risk measures.
The workflow¶
In use, the packages compose into a renewal cycle: study the experience, project claims, set rates, and project premium under those rates. Every arrow below corresponds to a real interface.
flowchart LR
subgraph CORE["built on actuarialpy"]
ES["experiencestudies<br/>experience"]
PM["projectionmodels<br/>projection"]
RM["ratingmodels<br/>pricing"]
RES["reservingmodels<br/>reserving"]
end
ES --> PM
ES --> RM
PM -- "projected loss cost" --> RM
RM -- "indicated changes & loads" --> PM
LM["lossmodels<br/>severity & frequency"] -- "pooling charge" --> RM
EL["extremeloss<br/>tail"] -- "pooling charge" --> RM
LM -. "splice" .-> EL
LM --> RS["risksim<br/>capital"]
EL --> RS
RES -- "reserve risk" --> RS
classDef core fill:#eaf2ff,stroke:#3a6ea5,stroke-width:2px,color:#1a1a1a
class CORE core
The loop between pricing and projection is deliberate, with the division of
labor stated honestly: the pricing layer produces the indicated change,
the selected action is a renewal-strategy decision that the indication and
the experiencestudies monitoring views inform rather than determine, and
PremiumProjection takes whatever was selected (as RenewalRateActions),
because you project the rates you set. The
distribution work is its own forward-looking mode — severity and frequency
in lossmodels, the tail in extremeloss, aggregate simulation and risk
measures in risksim — and it reaches the deterministic side through
pricing: any severity object exposing sf and mean_excess prices a
pooling charge in ratingmodels, which enters ClaimProjection as a
rate_load. actuarialpy is not a stage data passes through: it is the
primitives layer the boxed packages are built on. Every package is usable on
its own — enter the graph wherever your problem starts. The install-time
picture is below.
Dependencies¶
The dependency direction is strictly one-way, from the workflow layers down
to the core. experiencestudies, projectionmodels, ratingmodels, and
reservingmodels each depend on actuarialpy and delegate their credibility, trend, completion,
seasonality, and time-value math to it rather than re-implementing;
ratingmodels also depends on statsmodels, to which it delegates GLM
estimation for the same reason. reservingmodels re-exports actuarialpy’s
triangle primitives and Mack standard errors, so it depends on actuarialpy
directly, and it reaches risksim through the same .sample() protocol used
by the distribution packages. extremeloss can optionally pull in
lossmodels through its splice extra for severity splicing. lossmodels
and extremeloss stay array-level at the core but each ships an optional
integrations/actuarialpy module (the actuarialpy extra) that consumes the
canonical claims-listing Experience – an optional edge pointing the same
direction as every other. risksim’s core is numpy-only: it consumes fitted models through a formal
SupportsSample protocol – anything with a .sample() method, which every
lossmodels distribution, extremeloss tail fit, and reservingmodels reserve
model satisfies, along with any distribution object from outside the ecosystem – so its only declared
edge is a dev-extra on lossmodels for its own tests. It never touches
experience, so no edge to actuarialpy exists or should.
flowchart LR
AP["actuarialpy<br/>primitives + Experience contract"]:::core
ES["experiencestudies"]
PM["projectionmodels"]
RM["ratingmodels"]
RES["reservingmodels"]
LM["lossmodels"]
EL["extremeloss"]
RS["risksim"]
ES -->|requires| AP
PM -->|requires| AP
RM -->|requires| AP
RES -->|requires| AP
EL -.->|optional, via splice| LM
LM -.->|optional, via actuarialpy extra| AP
EL -.->|optional, via actuarialpy extra| AP
LM -.->|".sample() protocol, dev extra"| RS
EL -.->|".sample() protocol"| RS
RES -.->|".sample() protocol"| RS
classDef core fill:#eaf2ff,stroke:#3a6ea5,stroke-width:2px,color:#1a1a1a
extremeloss pulls in matplotlib
only through its optional plot extra (pip install "extremeloss[plot]"), for
the diagnostic plots; the base install does not require it. experiencestudies
pulls in openpyxl only through its excel extra, for the workbook writer.
Where packages cooperate without depending, they do it through a
deliberately tiny duck-typed protocol: any severity object exposing
sf(x) and mean_excess(d) — every lossmodels distribution, every
extremeloss GPD tail fit — plugs into
ratingmodels.pooling_charge_from_severity. The seam is two methods, not
an import. risksim runs on the same principle from the other side: its
SupportsSample protocol accepts any object with .sample() – every
lossmodels distribution, every extremeloss tail, every reservingmodels
reserve model, or any distribution you bring from outside the ecosystem – so the workflow diagram’s arrows into
risksim are protocol seams, not imports. The data-object seam works the same way in reverse: the
Experience/ExperienceSet contract flows up through the layers as data,
while every import edge keeps pointing down – a package consuming the
contract is a package depending on actuarialpy, never the other way
around.
Conventions¶
Every package works on plain numpy arrays and pandas Series/DataFrames. There is
no framework to buy into: functions take and return the data structures you
already have, and the classes (Experience, ManualRate, RateIndication, …)
are thin, inspectable wrappers over those functions. Cross-package numerical
conventions — the empirical VaR/TVaR estimators, the rng reproducibility
contract, distribution naming, coverage semantics, and the truncation/censoring
data layout — are collected on the Conventions page.
These conventions are enforced, not aspirational. The distribution
parameterizations are pinned against scipy.stats by conformance tests, the
risk-measure estimators are asserted byte-identical across the three packages
that implement them, the identities quoted on the conventions page are test
assertions, every example script is executed by its package’s test suite, and
the worked examples are themselves regression tests.