OpenActuarial

OpenActuarial is a dependency-light Python ecosystem for general actuarial workflows, including experience analysis, projection, rating and pricing models, loss modeling, tail estimation, simulation, and portfolio capital. The packages are modular and can be installed individually, with actuarialpy providing the shared calculation primitives that the workflow packages build on.

The ecosystem

actuarialpy

The primitives. Ratios and per-exposure metrics, chain-ladder development and IBNR, credibility, trend, seasonality, financial mathematics, exposure and lifecycle bases, banding, pooling, margins, and weighted rollups — pure calculation on numpy and pandas, which the experience, projection, and pricing layers build on directly.

actuarialpy
experiencestudies

Experience. Experience reporting and analysis — summaries and views, actual-versus-expected, claimant and concentration analysis, cohort and duration studies, driver and frequency–severity decomposition, rolling monitors, banded summaries, and the two-tier underwriting summary — study functions over the canonical Experience.

experiencestudies
projectionmodels

Projection. Focused claim, premium, and expense projections on supplied exposure — renewal rate actions, a complete → deseasonalize → trend → blend → reseasonalize claim pipeline, scenario adjustments, and exposure-weighted summaries of the results.

projectionmodels
ratingmodels

Pricing. Manual and experience rate build-up, credibility blending, rate indication and decomposition, GLM relativities with diagnostics, frequency–severity models, validation splits and tables, renewal constraints, dislocation reporting, and pricing scenarios with margin targets.

ratingmodels
reservingmodels

Reserving. Claims development and stochastic reserve estimation — chain ladder, Bornhuetter–Ferguson, Benktander, and Cape Cod re-exported from actuarialpy, Mack standard errors, and the over-dispersed-Poisson bootstrap of the full predictive reserve distribution, with residual diagnostics and a .sample seam into risksim.

reservingmodels
lossmodels

Loss modeling. Severity and frequency fitting — including under deductibles and limits — and aggregate loss distributions.

lossmodels
extremeloss

Tails. Extreme-value tail estimation — peaks-over-threshold / GPD and large-claim loading.

extremeloss
risksim

Capital. Portfolio Monte Carlo simulation and risk measures.

risksim

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 pricing–projection pair is deliberately a loop, with one honest caveat about who decides what. Pricing produces the indicated change; the selected action is a business decision — renewal strategy, cohort review, underwriting judgment — that the indication and the monitoring views inform rather than determine. In ecosystem terms: ratingmodels supplies the indication, experiencestudies supplies the cohort, persistency, and actual-versus-expected views the forum argues from, and RenewalRateActions carries whatever was selected, because you project the rates you set. rm.renew’s cap-and-floor is a mechanical stand-in for that selection, not a claim that pricing makes it. The severity and tail work runs as its own forward-looking mode — fit the body, splice the tail, simulate the aggregate, measure the capital — and reaches the deterministic side through pricing: any severity exposing sf and mean_excess prices a pooling charge, which enters the claim projection as a rate load. actuarialpy is not a stage data passes through: it is the primitives layer the boxed packages are built on, and the ecosystem’s only required install edges point into it (the overview draws the dependency graph).

Each seam above is runnable end to end — see the worked examples: Example 1: experience to a renewal rate, Example 2: pricing a book, in columns, Example 3: claims to capital, Example 4: censored payments to coverage terms, Example 5: triangle to indicated change, Example 6: pricing the tail, with error bars, Example 7: the renewal cycle, projected, Example 8: the plan, the actuals, and the miss, Example 9: two lines, one tail, Example 10: the pinned ratio, two ways, Example 11: the reserve, with a distribution, and the ecosystem tour.

From source tables to a whole workflow

The shortest path from raw extracts to an analysis is one construction call: three source tables — membership, a claims listing, billing — become one ExperienceSet, and the same book then routes itself to each package at the grain that package needs.

import actuarialpy as ap
import experiencestudies as es
import ratingmodels as rm

# one construction call: membership defines the grain, each Source names its role
book = ap.ExperienceSet.from_tables(
    membership,                                  # one row per member-month
    grain=["member_id", "month"], exposure="member_months",
    sources=[
        ap.Source(claim_lines, expense="paid_amount",
                  date="incurred_date", name="claims"),
        ap.Source(billing, revenue="billed_premium"),
    ],
    date="month", period="M", dimensions="group_id",
    valuation_date="2026-06-30",
)

book.reconcile()                          # ties every listing back to the tab
es.summary(book, "group_id")              # tab -> grouped experience study
rm.experience_rate(book, by="group_id")   # tab -> credibility-blended indication
# book["claims"] (claim-line grain) -> severity, frequency, and tail fitting
ExperienceSet.from_tables(membership, claims, billing)
├── book.tab       -> experience studies / projection / rating   (member-month grain)
└── book["claims"] -> severity / frequency / tail fitting          (claim-line grain)

One source definition, several workflows — see Start here: one data definition across the ecosystem for the full tour, and Choosing your input for when to reach for the object model versus plain pandas.

A composition on one table

With a single prepared table you wrap it directly — the study layer reads the block, the primitives supply credibility, and the pricing layer blends and indicates. Every number below is real output:

import pandas as pd
import actuarialpy as ap
import experiencestudies as es
import ratingmodels as rm

df = pd.DataFrame({
    "month": pd.date_range("2025-01-01", periods=12, freq="MS"),
    "segment": ["north"] * 12,
    "incurred": [8_000 * v for v in [498, 505, 512, 508, 516, 511,
                                    519, 514, 522, 517, 509, 513.0]],
    "premium": [8_000 * 560.0] * 12,
    "member_months": [8_000] * 12,
})

# study layer: how is the block performing?
exp = ap.Experience(df, expense="incurred", revenue="premium",
                    exposure="member_months", date="month")
seg = es.summary(exp, "segment")                            # loss ratio 0.914
loss_cost = seg["total_expense_per_member_months"].iloc[0]  # 512.00 per member-month

# primitives: credibility from exposure (lives in actuarialpy)
z = ap.limited_fluctuation_z(exposure=96_000, full_credibility_standard=120_000)  # 0.894

# pricing: blend against a manual rate and indicate
manual = rm.ManualRate(base_loss_cost=480, factors={"area": 1.05, "industry": 0.97})
indication = rm.RateIndication(
    experience_loss_cost=loss_cost,
    manual_loss_cost=manual.loss_cost(),
    credibility=z,
    current_rate=560,
    target_loss_ratio=0.85,
)

indication.indicated_rate_change()        # +7.0% — blended, credibility-weighted
indication.rate_change_decomposition()    # attribute the change to each driver

Every numeric argument in the pricing calls also accepts a column — the same call prices a whole book; see Example 2: pricing a book, in columns.

Install

pip install actuarialpy experiencestudies projectionmodels ratingmodels reservingmodels lossmodels extremeloss risksim

Any subset works — pip resolves the declared dependencies: the workflow packages (experiencestudies, projectionmodels, ratingmodels, reservingmodels) declare actuarialpy, and ratingmodels additionally declares statsmodels.