Gaspatchio¶
Gaspatchio is an actuarial modelling framework, currently in development, that allows you to build and run actuarial models in pure Python.
Simple, Fast and Best Served Cold¶
- Simple: Gaspatchio is a python framework for building actuarial models, with a focus on simplicity and ease of use. You can integrate python you already have, or use the built-in functions to build your models.
- Fast: Gaspatchio is designed to be fast, with a focus on performance and efficiency. Underneath the hood, Gaspatchio is written in Rust, which is a compiled language that is known for its speed and efficiency.
- Best Served Cold: Gaspatchio is build on top of Polars, which is a powerful and efficient data processing library. This means that Gaspatchio can handle large datasets, complex models and uses SIMD, parallel processing and GPUs to its advantage.
Simple Life Insurance Model Walkthrough¶
Here's a simple life insurance model that demonstrates Gaspatchio's key features:
1. Load Model Points and Setup Assumptions¶
Model Setup
import gaspatchio_core as gs
import polars as pl
from gaspatchio_core import ActuarialFrame
from gaspatchio_core.assumptions import MeltDimension
# Load model points data
af = ActuarialFrame("model-points.parquet")
# Setup mortality table with multi-dimensional lookups
mortality_df = pl.read_parquet("assumptions/mortality.parquet")
mortality_table = gs.Table(
name="mortality_rates",
source=mortality_df,
dimensions={
"age-last": "age-last",
"variable": MeltDimension(
columns=["MNS", "FNS", "MS", "FS"], name="variable"
),
},
value="mortality_rate",
)
# Setup lapse table - simple curve table
lapse_df = pl.read_parquet("assumptions/lapse.parquet")
lapse_table = gs.Table(
name="lapse_rates",
source=lapse_df,
dimensions={"policy duration": "policy duration"},
value="lapse rate",
)
2. Create Projection Timeline¶
Projection Setup
def setup_ages(af: ActuarialFrame) -> ActuarialFrame:
max_age = 101
af["num_proj_months"] = (max_age - af["age"]) * 12
# Create projection timeline using vector operations
af["proj_months"] = af.fill_series(af["num_proj_months"], 0, 1)
af["month"] = af["proj_months"]
af["proj_years"] = af["proj_months"] / 12
# Update age with monthly increment
af["age"] = af["age"] + (af["proj_months"] / 12)
# Calculate fractional policy duration list (overwrites original scalar)
af["policy duration"] = af["policy duration"] + (af["proj_months"] / 12)
# Calculate integer duration for lookups (mimics Excel's ROUNDDOWN)
af["policy_duration_as_int"] = af["policy duration"].floor()
# Use floor to get age last for mortality lookups
af["age-last"] = af["age"].floor()
return af
3. Multi-Dimensional Assumption Lookups¶
Rate Calculations
def mortality_rate(af: ActuarialFrame) -> ActuarialFrame:
# Combine gender and smoking status for lookup
af["variable"] = af["gender"] + af["smoking status"]
# Use the table's lookup method with cleaner syntax
af["mortality rate"] = mortality_table.lookup(
{"age-last": af["age-last"], "variable": af["variable"]}
)
return af
def lapse_rate(af: ActuarialFrame) -> ActuarialFrame:
# Use the table's lookup method with dictionary syntax
af["lapse rate"] = lapse_table.lookup(
{"policy duration": af["policy_duration_as_int"]}
)
return af
def premium_rate(af: ActuarialFrame) -> ActuarialFrame:
af["premium rate"] = premium_table.lookup(
{"age-last": af["age-last"], "variable": af["variable"]}
)
return af
4. Actuarial Calculations¶
Probability Calculations
def probability_in_force(af: ActuarialFrame) -> ActuarialFrame:
# Monthly persistence probability
af["monthly_persist_prob"] = (1 - af["mortality rate"] / 12) * (1 - af["lapse rate"] / 12)
# Cumulative probability in force using list operations
af["P[IF]"] = pl.col("monthly_persist_prob").cum_prod().shift(1).fill_null(1.0)
return af
5. Cash Flow Projections and Discounting¶
Cash Flow Model
def calculate_cashflows(af: ActuarialFrame, interest_rate: float) -> ActuarialFrame:
# Premium and claims cash flows
af["premium cashflow"] = af["premium rate"] / 12 * af["P[IF]"] * af["sum_assured"] / 1000
af["claims cashflow"] = af["P[death]"] * af["sum_assured"]
# Discount factors using vectorized operations
monthly_rate = interest_rate / 12.0
af["discount rate"] = pl.col("proj_months").pow(1.0 / (1.0 + monthly_rate))
# Present value calculations
af["pv_premiums"] = af["premium cashflow"] * af["discount rate"]
af["pv_claims"] = af["claims cashflow"] * af["discount rate"]
return af
6. Excel Function Compatibility¶
Excel-Compatible Date and Financial Functions
def setup_time_calculations(af: ActuarialFrame) -> ActuarialFrame:
# Calculate effective date from Excel serial numbers
af["effective_date"] = af["Policy Cover Effective date"].excel.from_excel_serial()
# Excel YEARFRAC function - calculates fraction of year between two dates
# YEARFRAC(start_date, end_date, [basis]) where basis 0 = US 30/360 convention
af["year_frac"] = af["effective_date"].excel.yearfrac(af["date"], 0)
# Build additional time calculations from year fractions
af["year"] = af["year_frac"].ceil()
af["month"] = (af["year_frac"] % 1 * 12).ceil()
return af
Key Features Demonstrated¶
- High Performance: Rust-powered engine with SIMD and parallel processing
- Actuarial DSL: Domain-specific language designed for actuarial modeling
- Excel Function Compatibility: Built-in support for Excel functions like YEARFRAC, date serialization, and financial calculations
- Flexible Data Types: Seamless handling of scalar and vector (list) columns
- Memory Efficient: Lazy evaluation and optimized memory usage for large datasets
- Type Safety: Strong typing with automatic casting and validation
- Vectorized Operations: Perform calculations across millions of policy-months efficiently
This model runs efficiently on datasets with 100k+ policies and 1,200+ projection months per policy, demonstrating Gaspatchio's ability to handle enterprise-scale actuarial modeling workloads.