Observed since 20 Oct, 18:41 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Modern actuarial pricing library for non-life insurance with GLM-based frequency-severity modeling
Modern Actuarial Pricing Library for Non-Life Insurance
ActuaFlow is a Python library for building GLM-based frequency-severity pricing models with a focus on actuarial best practices.
actuaflow.glm.GAM for smooth non-linear predictor effectsactuaflow.glm.MonotoneGLM applies post-fit monotonicity constraints (increasing/decreasing) to ordered categorical predictorsactuaflow.glm.QuantileGLM conditional quantile regression (statsmodels QuantReg) with severity fan plotsactuaflow.glm.BayesianGLM PyMC-based Bayesian Poisson/Gamma GLM with credible intervals (requires optional pymc dependency)actuaflow.glm.HierarchicalGLM mixed-effects/hierarchical GLM (statsmodels MixedLM, with optional pymer4/rpy2 support for non-Gaussian families)FrequencySeverityCopula for joint frequency/severity simulation, tail dependence, and aggregate loss analysisactuaflow.glm.qpp (FrequencyModel, QPPAdjuster, QPPPricer) prices policies from the policy-specific severity quantile implied by the frequency model's zero-claim probability, rather than the expected lossactuaflow.tariff (TariffPlan, FactorTable, RatingEngine) for base rates, factor relativities, tier adjustments, eligibility rules, JSON/YAML serialization, and version comparison; build_tariff_from_glm() builds a tariff plan directly from a fitted GLM's coefficientsConstrainedRateOptimizer combines credibility-bounded and linear-programming rate optimization with dislocation analysis and dislocation plotsactuaflow.compliance.RateIndicationSummary reports actual loss ratios, indicated rates, credibility weights, and selection flags for rate reviewactuaflow.compliance.RateFilingPackage bundles rate indications and dislocation analyses, and runs anti-discrimination checks on protected-class attributesactuaflow.compliance.SERFFExporter exports rating relativities to SERFF-compliant XML or flat CSV for regulatory submission (e.g. AR, CA, GR filings)DataQualityChecker for missing-value/outlier reports, winsorising, factor consolidation, auto-binning, leakage checks, and calibration curvesAutoImputer for basic missing-value imputationModelFittedEvent, ModelSavedEvent), fitting strategy pattern (FittingStrategy), and ModelStore persistence.SeverityGLM.fit performs zero filtering automatically and lognormal family is internally handled with transformation.FrequencySeverityCopula workflow for joint frequency/severity dependence modeling and aggregate loss simulation.GAM, MonotoneGLM, QuantileGLM, BayesianGLM (optional pymc dependency), HierarchicalGLM (mixed effects).actuaflow.glm.qpp (FrequencyModel, QPPAdjuster, QPPPricer). Note: actuaflow.glm.qpp.FrequencyModel is a lightweight QPP-specific wrapper and is distinct from the top-level actuaflow.FrequencyModel frequency-severity workflow class.actuaflow.tariff: TariffPlan, FactorTable, RatingEngine) and constrained rate optimizer (ConstrainedRateOptimizer) with dislocation analysis.actuaflow.compliance: RateIndicationSummary, RateFilingPackage, SERFFExporter) for rate filing and SERFF export.DataQualityChecker, AutoImputer) in actuaflow.data.pip install actuaflow
Or install from source:
git clone https://github.com/WattyAI/actuaflow.git
cd actuaflow
pip install -e .
import actuaflow as af
import pandas as pd
# Load data
policies = af.load_data('policies.csv')
claims = af.load_data('claims.csv')
# 1. Frequency Model
freq_model = af.FrequencyModel(family='poisson', link='log')
freq_model.prepare_data(policies, claims, policy_id='policy_id')
freq_model.fit(
formula='claim_count ~ age_group + vehicle_type + region',
offset='exposure'
)
print(freq_model.summary())
print(f"Dispersion: {freq_model.diagnostics_['dispersion']:.3f}")
# 2. Severity Model
sev_model = af.SeverityModel(family='gamma', link='log')
sev_model.prepare_data(claims, policies, policy_id='policy_id')
sev_model.fit(formula='amount ~ age_group + injury_type')
print(sev_model.summary())
# 3. Combined Model
agg_model = af.combine_models(freq_model, sev_model)
print(f"Base Pure Premium: ${agg_model.base_pure_premium_:.2f}")
# Get factor table
factor_table = agg_model.create_factor_table()
print(factor_table)
# 4. Premium Calculation
pure_premium = agg_model.predict_pure_premium(policies, exposure='exposure')
loadings = {
'inflation': 0.03,
'expense_ratio': 0.15,
'commission': 0.10,
'profit_margin': 0.05,
'tax_rate': 0.02
}
premium_df = af.calculate_premium(pure_premium, loadings, exposure=policies['exposure'])
print(f"Total Loaded Premium: ${premium_df['loaded_premium'].sum():,.0f}")
ActuaFlow functions that prepare frequency data accept flexible column names for policy and claims identifiers. Example:
from actuaflow import FrequencyModel
# Policy table uses 'policy_key', claims table uses 'pol_id'
freq_model = FrequencyModel()
freq_model.prepare_data(
policy_data=policies,
claims_data=claims,
policy_id_policy='policy_key',
policy_id_claims='pol_id'
)
This makes it easy to work with data exports that use different naming conventions.
Use the calculate_exposure helper to compute exposure between a start and end date. It supports years, days, and months methods and accepts custom column names for the start/end dates as well as a custom output column name.
from actuaflow import calculate_exposure
policies_with_exposure = calculate_exposure(
policy_data=policies,
start_date_col='policy_start_date',
end_date_col='policy_end_date',
method='years',
exposure_col='exposure'
)
print(policies_with_exposure[['policy_id', 'exposure']].head())
The function validates date columns and returns a copy of the dataframe with the exposure column added.
# Comprehensive diagnostics
diag = af.compute_diagnostics(freq_model.model_, policies)
print(f"AIC: {diag['aic']:.2f}")
print(f"Dispersion: {diag['dispersion']:.3f}")
# VIF for multicollinearity
if diag['vif']:
for var, vif in diag['vif'].items():
print(f"{var}: VIF = {vif:.2f}")
# Lift curve
y_true = policies['claim_count']
y_pred = freq_model.predict()
bins, actual, predicted = af.compute_lift_curve(y_true, y_pred, n_bins=10)
# Gini index
has_claim = (y_true > 0).astype(int)
gini = af.compute_gini_index(has_claim, y_pred)
print(f"Gini coefficient: {gini:.3f}")
# Compute rate per exposure
rate = af.compute_rate_per_exposure(
pure_premium=policies['pure_premium'],
exposure=policies['exposure'],
loadings={'profit': 0.05, 'expenses': 0.15}
)
# Create class plan
relativities = {
'age_group': {'18-25': 1.5, '26-35': 1.0, '36-45': 0.8, '46+': 0.7},
'vehicle_type': {'sedan': 1.0, 'suv': 1.2, 'sports': 1.8}
}
rated_policies = af.create_class_plan(
data=policies,
rating_factors=['age_group', 'vehicle_type'],
base_rate=100.0,
relativities=relativities,
exposure_col='exposure'
)
# Apply trend factor
trended_losses = af.apply_trend_factor(
historical_value=100000,
trend_rate=0.03,
years=4
)
print(f"Trended to current: ${trended_losses:,.0f}")
# Premium impact from factor changes
factor_changes = {
'age_group': {'18-25': 1.10, '26-35': 1.00, '36+': 0.95}
}
impact = af.compute_premium_impact(
data=policies,
base_premium_col='premium',
factor_changes=factor_changes
)
print(f"Total impact: ${impact['premium_change'].sum():,.0f}")
print(f"Average change: {impact['premium_change_pct'].mean():.1f}%")
# Segment analysis
segment_impact = af.segment_impact_analysis(
data=impact,
premium_col='premium_current',
segment_cols=['age_group', 'territory'],
proposed_premium_col='premium_proposed'
)
# Price elasticity
elasticity = af.estimate_demand_elasticity(
price_history=historical_data,
price_col='average_premium',
volume_col='policy_count'
)
print(f"Elasticity: {elasticity['elasticity']:.3f}")
# Elasticity curve
curve = af.compute_elasticity_curve(
current_price=100,
current_volume=10000,
elasticity=-1.5,
price_range=(80, 120)
)
Full documentation available at: https://actuaflow.readthedocs.io
Jupyter notebooks demonstrating common workflows:
examples/motor_insurance.ipynb - Complete motor insurance pricingexamples/property_pricing.ipynb - Property insurance GLMsexamples/glm_basics.ipynb - GLM fundamentalsexamples/exposure_rating.ipynb - Exposure-based ratingexamples/portfolio_analysis.ipynb - Premium impact and elasticityactuaflow/
├── glm/ # GLM models and variants
│ ├── models.py # BaseGLM, FrequencyGLM, SeverityGLM, TweedieGLM
│ ├── diagnostics.py # VIF, lift curves, Gini, etc.
│ ├── gam.py # GAM (smooth non-linear effects)
│ ├── monotone_glm.py # MonotoneGLM (isotonic monotonicity constraints)
│ ├── quantile_regression.py # QuantileGLM (conditional quantile regression)
│ ├── qpp.py # QPP pricing (FrequencyModel, QPPAdjuster, QPPPricer)
│ ├── bayesian_glm.py # BayesianGLM (PyMC-based, optional `pymc` dependency)
│ ├── mixed_models.py # HierarchicalGLM (mixed effects)
│ ├── zero_inflated.py # ZeroInflatedPoisson, ZeroInflatedNegativeBinomial
│ └── hurdle.py # HurdleGamma, HurdleLognormal
├── freqsev/ # Frequency-severity workflow
│ ├── frequency.py # FrequencyModel
│ ├── severity.py # SeverityModel
│ ├── aggregate.py # AggregateModel, premium calculation
│ ├── copula.py # FrequencySeverityCopula (dependence modeling)
│ └── large_loss.py # LargeLossHandler
├── exposure/ # Exposure rating tools
│ ├── rating.py # Class plans, rate per exposure
│ ├── trending.py # Trend factors, on-leveling
│ └── exposure_curves.py # ExposureCurve
├── portfolio/ # Portfolio analysis
│ ├── impact.py # Premium impact, mix-shift
│ ├── elasticity.py # Price elasticity, optimization
│ └── lifetime_value.py # CustomerLifetimeValue
├── tariff/ # Tariff plans and rating engine
│ ├── factor_table.py # FactorTable
│ ├── tariff_plan.py # TariffPlan (base rate, tiers, eligibility, serialization)
│ └── rating_engine.py # RatingEngine, build_tariff_from_glm
├── optimization/ # Rate optimization
│ └── rate_optimizer.py # ConstrainedRateOptimizer, dislocation analysis
├── compliance/ # Regulatory filing support
│ ├── rate_indication.py # RateIndicationSummary
│ ├── rate_filing.py # RateFilingPackage, anti-discrimination checks
│ └── serff_export.py # SERFFExporter (XML/CSV)
├── diagnostics/ # Actuarial diagnostics
│ ├── diagnostics.py # ActuarialDiagnostics, loss_ratio_validation, etc.
│ └── actuarial.py # actual_vs_expected, double_lift_chart, minimum_bias_check
├── credibility/ # Credibility weighting
│ ├── buhlmann.py # BuhlmannCredibility
│ └── limited_fluctuation.py # LimitedFluctuationCredibility
├── data/ # Data quality
│ └── quality.py # DataQualityChecker, AutoImputer
├── registry/ # Model persistence
│ └── model_store.py # ModelStore
├── explain/ # Model interpretability
│ └── change_explainer.py # ChangeExplainer
├── assumptions/ # Assumption management
│ ├── projection.py # AssumptionProjector, InflationAdjuster
│ └── assumption_helper.py # AssumptionHelper
├── config/ # Configuration
│ └── settings.py # settings
├── core/ # Event bus and fitting strategy
│ ├── events.py # ModelFittedEvent, ModelSavedEvent
│ └── strategy.py # FittingStrategy
└── utils/ # Utilities
├── data.py # Data loading, preparation
├── validation.py # Input validation
├── cross_validation.py # TimeSeriesSplit, cross_val_score
├── context.py # model_fitting_context
└── retry.py # retry_on_convergence
The package follows the standard actuarial practice of modeling claim frequency and severity separately:
Exposure is handled as an offset (not a predictor):
# Correct - offset parameter
freq_model.fit(data, formula='claim_count ~ age + vehicle', offset='exposure')
# Incorrect - don't include offset in formula
# freq_model.fit(data, formula='claim_count ~ age + vehicle + offset(log(exposure))')
For log-link models, relativities are exp(coefficient):
relativities = freq_model.get_relativities()
# age_group[18-25]: 1.50 means 50% higher frequency than base
# age_group[36-45]: 0.80 means 20% lower frequency than base
ActuaFlow is a solo-maintained project. External code contributions are not accepted at this time. However, feedback, bug reports, and feature requests are always welcome!
To report issues or request features:
[BUG REPORT] for bugs or [FEATURE REQUEST] for ideasSee CONTRIBUTING.md for details.
Mozilla Public License v2.0 (MPL-2.0)
ActuaFlow is licensed under the Mozilla Public License v2.0, an OSI-approved open-source license that:
Special Licensing Available: Implementation services, consulting, GUI software, and alternative licensing terms. Contact the author for details.
See LICENSING.md for comprehensive licensing information.
Author: Michael Watson (michael@watsondataandrisksolutions.com)
Copyright: (c) 2026–present Michael Watson
For complete details, see:
Licensing Options:
If you use ActuaFlow in academic research, please cite:
@software{actuaflow2025,
title = {ActuaFlow: Modern Actuarial Pricing Library},
author = {Watson, Michael},
year = {2025},
url = {https://github.com/WattyAI/actuaflow},
license = {MPL-2.0}
}
ActuaFlow is built on:
ActuaFlow - Modern tools for modern actuaries
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.3.1 | 15 Jul 2026 | 23 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 18:41 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:ca23a6df39c5e4488a42a41e87f6e89a2898090bfaee199adbc44c4da74e036d