projectionmodels¶
Focused actuarial projections of claims, premium, and expenses on supplied exposure. The package is intentionally organized around concrete workflows — most users should not need to construct a calculation graph or define a custom state engine. It depends on actuarialpy for its completion, trend, seasonality, and credibility estimation.
The package root contains the workflow objects most actuaries need:
Object |
Role |
|---|---|
|
Prepare a base claim rate from experience |
|
Project claim rates and claims by claim type |
|
Roll premium forward, including renewal rate actions |
|
Supply effective-dated rate actions |
|
Project per-exposure, fixed, premium-based, and claim-based expenses |
|
Define monthly, quarterly, or annual projection periods |
|
Define entry, exit, renewal, and experience date columns |
|
Split records into existing/new or other date cohorts |
|
Run sensitivities and alternative assumptions |
|
Summarize without averaging ratios or duplicating exposure |
Lower-level modeling objects are available from projectionmodels.advanced,
but they are not part of the primary workflow.
Claims by claim type¶
ClaimExperience prepares a base rate from history, and
ClaimProjection.from_experience carries it through the projection:
experience = pm.ClaimExperience(
hist,
projection_keys=["group_id"],
claim_type_col="claim_type",
date_col="incurred_month",
claims_col="reported_claims",
exposure_col="member_months",
valuation_date="2026-12-31",
)
projection = pm.ClaimProjection.from_experience(
experience,
exposure=exposure,
exposure_col="member_months",
horizon=pm.ProjectionHorizon("2027-01-01", periods=12),
trend=pm.TrendAssumption.from_values("claim_trend", 0.06),
# optionally: completion=, seasonality=, credibility=, complement=
)
results = projection.project()
results.to_frame() # per key × claim type × period detail
Trend, seasonality, completion, and credibility may be supplied directly as
assumption tables (TrendAssumption.from_values and friends) or estimated
from history (below).
Cost levels and pipeline order¶
The claim workflow evaluates, in order: complete → deseasonalize → trend the
experience rate to the blend basis → credibility blend → trend from the basis
to each projection period → reseasonalize → add rate_loads → multiply by
exposure. Exposure is whatever unit the book uses — member-months,
policy-months, car-years — named with exposure_col.
The complement is used as stated. By default the blend basis is the
prospective midpoint of the horizon (complement_basis="prospective"), the
level at which manual and book rates are conventionally quoted — so a
zero-credibility projection reproduces the complement rather than a trended
copy of it. Set complement_basis="experience" if your complement is quoted
at experience-period cost level, or pass an explicit as-of date. Because the
month arithmetic is exactly additive, results at full credibility are
identical under every basis.
rate_loads (for example a pooling charge) are added to the projected rate
as stated: flat across periods, after seasonality, outside the blend.
Estimating assumptions with actuarialpy¶
Estimation is explicit and separate from projection execution. The
projectionmodels.integrations.actuarialpy adapters estimate each assumption
with the corresponding core primitive (completion_factors, fit_trend,
seasonality_factors, limited_fluctuation_z) and return assumption objects
that retain indicated values and diagnostics:
from projectionmodels.integrations.actuarialpy import (
estimate_completion,
estimate_credibility,
estimate_seasonality,
estimate_trend,
)
trend = estimate_trend(
"claim_trend",
deseasonalized_history,
by=["claim_type"],
date_col="incurred_month",
value_col="deseasonalized_claims",
exposure_col="member_months",
)
An actuary can replace an indication while preserving the audit trail — the assumption keeps both the estimate and the selection.
Results¶
ProjectionResults holds the per-period detail and summarizes it correctly:
exposure-weighted rates, summed amounts — never a naive average of ratios,
never exposure counted twice across claim types:
results.to_frame() # full detail
results.summarize(by="calendar_quarter") # weighted rates, summed exposure and amounts
API reference¶
Focused actuarial claim, premium, and expense projection workflows on supplied exposure.
- class Adjustment(target: str, method: str, value: ~typing.Any, name: str | None = None, filters: ~collections.abc.Mapping[str, ~typing.Any] = <factory>, effective_from: ~typing.Any | None = None, effective_to: ~typing.Any | None = None, priority: int = 100)[source]¶
Bases:
objectModify a named input, assumption, or roll-forward under a scenario.
- class Assumption(name: str, values: ~typing.Any, lookup: tuple[str, ...] | list[str] = <factory>, value_col: str | None = None, source: str = 'supplied', indicated_values: ~typing.Any | None = None, diagnostics: ~typing.Any | None = None, metadata: ~collections.abc.Mapping[str, ~typing.Any] = <factory>)[source]¶
Bases:
objectA named assumption resolved by explicit lookup fields.
valuesmay be a scalar, Series, or DataFrame. DataFrame assumptions are joined many-to-one onlookupand must containvalue_col.- resolve(frame: DataFrame, *, strict: bool = True) Series[source]¶
Resolve assumption values onto
framein its original row order.
- select(selection: Any, *, lookup: str | Iterable[str] | None = None, value_col: str | None = None, note: str | None = None) Assumption[source]¶
Replace indicated values with an actuarial selection while retaining audit data.
- class ClaimExperience(data: DataFrame, projection_keys: tuple[str, ...] | list[str], claim_type_col: str, date_col: str, claims_col: str, exposure_col: str, valuation_date: Any | None = None)[source]¶
Bases:
objectHistorical claims used to establish projected base rates.
The input may contain one row per month, claim transaction, or another experience grain.
to_base_ratesdevelops immature claims, removes seasonality, aggregates to projection record + claim type, and calculates a per-exposure experience rate.- prepare(*, completion: CompletionAssumption | None = None, seasonality: SeasonalityAssumption | None = None) DataFrame[source]¶
Return experience with completed and deseasonalized claim columns.
- to_base_rates(*, completion: CompletionAssumption | None = None, seasonality: SeasonalityAssumption | None = None, complement: Assumption | Any | None = None, extra_record_cols: Iterable[str] = ()) DataFrame[source]¶
Aggregate prepared experience to one row per record and claim type.
- class ClaimProjection(base_rates: ~pandas.DataFrame, projection_keys: tuple[str, ...] | list[str], claim_type_col: str, exposure: ~pandas.DataFrame, horizon: ~projectionmodels.horizon.ProjectionHorizon, trend: ~projectionmodels.assumptions.TrendAssumption, seasonality: ~projectionmodels.assumptions.SeasonalityAssumption | None = None, credibility: ~projectionmodels.assumptions.CredibilityAssumption | None = None, complement_basis: str | ~pandas.Timestamp = 'prospective', rate_loads: ~typing.Any = (), exposure_col: str = 'exposure', exposure_period_col: str = 'projection_period', dates: ~projectionmodels.data.ProjectionDates | None = None, additional_assumptions: tuple[~projectionmodels.assumptions.Assumption, ...] | list[~projectionmodels.assumptions.Assumption] = <factory>)[source]¶
Bases:
objectProject credibility-blended claim rates onto supplied exposure.
Exposure is whatever unit the book uses — member-months, policy months, earned car-years — supplied by projection key and period and named with
exposure_col.Pipeline, in order:
experience_claim_rateis trended from each record’sexperience_midpointto the blend basis (trended_experience_rate).The trended experience is credibility blended with
complement_claim_rateas stated (credible_claim_rate).The blended rate is trended from the blend basis to each period’s midpoint (
trended_claim_rate).Seasonality redistributes within the year and
rate_loadsare added, flat and outside the blend (projected_claim_rate).Rates are multiplied by exposure (
projected_claims).
Cost levels —
complement_basisdeclares the level at which the complement is quoted:"prospective"(default): the horizon’s mean period midpoint, the conventional level for manual and book rates. Zero credibility therefore reproduces the complement as stated."experience": the record’s experience midpoint, so the complement is trended alongside experience (the pre-0.5.0 behaviour).an explicit date: any other as-of level.
Because the calendar month arithmetic is exactly additive, projections at full credibility are identical under every basis.
rate_loads(for example a pooling charge) are Assumptions or scalars quoted at prospective level; they are added to the projected rate as stated, per period, after seasonality, and are not credibility weighted.
- class CompletionAssumption(name: str, values: ~typing.Any, lookup: tuple[str, ...] | list[str] = <factory>, value_col: str | None = None, source: str = 'supplied', indicated_values: ~typing.Any | None = None, diagnostics: ~typing.Any | None = None, metadata: ~collections.abc.Mapping[str, ~typing.Any] = <factory>, development_col: str = 'development_month')[source]¶
Bases:
AssumptionClaim completion factors in the divide convention, supplied or estimated.
- class CredibilityAssumption(name: str, values: ~typing.Any, lookup: tuple[str, ...] | list[str] = <factory>, value_col: str | None = None, source: str = 'supplied', indicated_values: ~typing.Any | None = None, diagnostics: ~typing.Any | None = None, metadata: ~collections.abc.Mapping[str, ~typing.Any] = <factory>)[source]¶
Bases:
AssumptionCredibility weights supplied or estimated by actuarialpy.
- class DateCohort(name: str, date_col: str, split_date: Any | None = None, before_label: str = 'before', on_or_after_label: str = 'on_or_after', frequency: str | None = None, breaks: tuple[Any, ...] | None = None, labels: tuple[str, ...] | None = None, include_lowest: bool = True)[source]¶
Bases:
objectCreate a reportable classification from a date column.
- class ExpenseProjection(expenses: DataFrame, projection_keys: tuple[str, ...] | list[str], expense_type_col: str, base_value_col: str, basis_col: str, base_date_col: str, horizon: ProjectionHorizon, trend: TrendAssumption, exposure: DataFrame | None = None, premium: DataFrame | None = None, claims: DataFrame | None = None, exposure_col: str = 'exposure', premium_col: str = 'premium', claims_col: str = 'projected_claims', dates: ProjectionDates | None = None)[source]¶
Bases:
objectProject expenses with per-exposure, fixed, premium, or claims bases.
- class PremiumProjection(premium_data: DataFrame, projection_keys: tuple[str, ...] | list[str], exposure: DataFrame, horizon: ProjectionHorizon, current_rate_col: str = 'current_premium_rate', exposure_col: str = 'exposure', exposure_period_col: str = 'projection_period', renewal_date_col: str = 'renewal_date', recurring_rate_action_col: str | None = None, rate_actions: RenewalRateActions | None = None, dates: ProjectionDates | None = None)[source]¶
Bases:
objectProject premium rates and premium using supplied exposure (member-months, policy months, and so on — see
exposure_col).A recurring action column is applied in every period marked
is_renewal_period. ARenewalRateActionsschedule is applied once in the period containing each supplied effective date. The adjusted rate is carried forward to all subsequent periods.
- class ProjectionDates(entry_date: str | None = None, exit_date: str | None = None, renewal_date: str | None = None, issue_date: str | None = None, experience_start: str | None = None, experience_end: str | None = None, exposure_timing: str = 'whole_period')[source]¶
Bases:
objectColumn roles for lifecycle and actuarial dates.
- class ProjectionHorizon(start: Any, periods: int | None = None, end: Any | None = None, frequency: str = 'monthly')[source]¶
Bases:
objectA deterministic projection timeline.
- Parameters:
start (Any) – First projection date. It is normalized to the beginning of the containing month, quarter, or year.
periods (int | None) – Number of projection periods. Supply either
periodsorend.end (Any | None) – Last date to include. Supply either
periodsorend.frequency (str) –
"monthly","quarterly", or"annual".
- property midpoint: Timestamp¶
Mean period midpoint of the horizon.
This is the prospective rating midpoint used as the default credibility blend basis: manual and book rates are conventionally stated at this level.
- class ProjectionResults(frame: ~pandas.DataFrame, measures: ~collections.abc.Mapping[str, ~projectionmodels.calculations.VariableDefinition], projection_keys: tuple[str, ...], component_keys: tuple[str, ...] = <factory>, assumption_audit_data: ~pandas.DataFrame | None = None, adjustment_audit_data: ~pandas.DataFrame | None = None)[source]¶
Bases:
objectDetailed projection output with mathematically explicit aggregation rules.
- classmethod combine(*results: ProjectionResults) ProjectionResults[source]¶
Combine compatible result sets column-wise on their common identifiers.
- summarize(by: str | Iterable[str], *, measures: str | Iterable[str] | None = None) DataFrame[source]¶
Summarize measures without double-counting coarser-grain values.
A measure’s declared
grainidentifies where it is unique. Requested grouping fields outside that grain are retained, which permits an entity- level exposure to repeat once for each displayed claim type while still counting only once when claim type is omitted from the summary.
- class RenewalRateActions(frame: DataFrame, projection_keys: tuple[str, ...] | list[str], effective_date_col: str = 'effective_date', rate_action_col: str = 'rate_action')[source]¶
Bases:
objectOne-time rate actions keyed by projection record and effective date.
rate_actionvalues are decimal changes:0.10means a 10% increase. Each action is applied once in the projection period containing its effective date. Use a rate-action column onPremiumProjectioninstead when the same action should recur at every renewal anniversary.- to_projection_table(horizon: ProjectionHorizon) DataFrame[source]¶
Return actions keyed to the horizon’s projection periods.
- class Scenario(name: str = 'baseline', adjustments: tuple[~projectionmodels.adjustments.Adjustment, ...] | list[~projectionmodels.adjustments.Adjustment] = <factory>)[source]¶
Bases:
objectA named collection of ordered adjustments.
- class SeasonalityAssumption(name: str, values: ~typing.Any, lookup: tuple[str, ...] | list[str] = <factory>, value_col: str | None = None, source: str = 'supplied', indicated_values: ~typing.Any | None = None, diagnostics: ~typing.Any | None = None, metadata: ~collections.abc.Mapping[str, ~typing.Any] = <factory>, season_col: str = 'season', frequency: str = 'M')[source]¶
Bases:
AssumptionNormalized seasonal multipliers.
- class TrendAssumption(name: str, values: ~typing.Any, lookup: tuple[str, ...] | list[str] = <factory>, value_col: str | None = None, source: str = 'supplied', indicated_values: ~typing.Any | None = None, diagnostics: ~typing.Any | None = None, metadata: ~collections.abc.Mapping[str, ~typing.Any] = <factory>)[source]¶
Bases:
AssumptionAnnual trend rate, supplied or fitted with
actuarialpy.fit_trend().
- exception ValidationError[source]¶
Bases:
ProjectionModelsError,ValueErrorRaised when projection inputs are structurally invalid.