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:
Complete Working Example¶
Complete Life Insurance Model
import datetime
import polars as pl
from gaspatchio_core import ActuarialFrame
from gaspatchio_core.assumptions import Table
# Configure Polars for better terminal display
pl.Config.set_tbl_cols(-1) # Show all columns
pl.Config.set_tbl_width_chars(300) # Wider width to show full column names
pl.Config.set_fmt_str_lengths(15) # Limit string length for readability
pl.Config.set_tbl_rows(-1) # Show all rows
# Create sample policy data
model_data = {
"policy_id": ["P001", "P002", "P003"],
"age": [35, 42, 55],
"gender": ["M", "F", "M"],
"smoking_status": ["NS", "NS", "S"],
"sum_assured": [100000, 250000, 500000],
"issue_date": [
datetime.date(2022, 1, 15),
datetime.date(2021, 6, 1),
datetime.date(2023, 3, 10),
],
"valuation_date": [
datetime.date(2024, 12, 31),
datetime.date(2024, 12, 31),
datetime.date(2024, 12, 31),
],
"annual_premium": [1200, 2800, 8500],
"interest_rate": [0.03, 0.035, 0.04],
"policy_term_years": [20, 25, 15],
}
af = ActuarialFrame(model_data)
# Create mortality table
mortality_data = pl.DataFrame(
{
"age_last": [
35,35,35,35,37,37,37,37,42,42,42,42,45,45,45,45,55,55,55,55,56,56,56,56],
"sex_smoking": ["MNS","FNS","MS","FS","MNS","FNS","MS","FS","MNS","FNS","MS","FS", "MNS","FNS","MS","FS", "MNS","FNS","MS","FS","MNS","FNS","MS","FS"],
"mortality_rate": [0.001,0.0008,0.002,0.0015,0.0012,0.001,0.0025,0.0018,0.002,0.0015,0.004,0.003,0.0025,0.002,0.005,0.0035,0.008,0.006,0.015,0.012,0.009,0.007,0.018,0.014],
}
)
mortality_table = Table(
name="mortality_demo",
source=mortality_data,
dimensions={"age_last": "age_last", "sex_smoking": "sex_smoking"},
value="mortality_rate",
)
# Create lapse table
lapse_data = pl.DataFrame(
{
"policy_duration": [1, 2, 3, 4, 5],
"lapse_rate": [0.05, 0.08, 0.12, 0.15, 0.18],
}
)
lapse_table = Table(
name="lapse_demo",
source=lapse_data,
dimensions={"policy_duration": "policy_duration"},
value="lapse_rate",
)
# 1. DATE CALCULATIONS (Excel Functions)
af.days_in_force = af.valuation_date.excel.days(af.issue_date)
af.years_in_force = af.issue_date.excel.yearfrac(
af.valuation_date, basis="act/act"
).round(2)
af.age_at_valuation = (af.age + af.years_in_force).round(2)
# 2. ASSUMPTION TABLE LOOKUPS
af.sex_smoking = af.gender + af.smoking_status
af.age_last = af.age_at_valuation.floor()
af.policy_duration_int = af.years_in_force.floor()
af.mortality_rate = mortality_table.lookup(
age_last=af.age_last, sex_smoking=af.sex_smoking
).round(2)
af.lapse_rate = lapse_table.lookup(policy_duration=af.policy_duration_int).round(2)
# 3. PRESENT VALUE CALCULATIONS (Excel PV Function)
af.remaining_term = (af.policy_term_years - af.years_in_force).round(2)
af.pv_future_premiums = af.interest_rate.excel.pv(
nper=af.remaining_term, pmt=af.annual_premium
).round(2)
af.pv_sum_assured = (
af.sum_assured / (1 + af.interest_rate) ** af.remaining_term
).round(2)
af.npv = (af.pv_sum_assured + af.pv_future_premiums).round(2)
# 4. CASH FLOW PROJECTIONS (Vector Operations)
af.expected_claims = (af.mortality_rate * af.sum_assured).round(2)
af.expected_premiums = (af.annual_premium * (1 - af.lapse_rate)).round(2)
af.net_cash_flows = (af.expected_premiums - af.expected_claims).round(2)
print("\n=== FINAL RESULTS ===")
# Execute all calculations in one go (single query plan)
df = af.collect()
print(df)
print("\n=== SUMMARY METRICS ===")
summary_data = {
"total_sum_assured": df["sum_assured"].sum(),
"total_annual_premium": df["annual_premium"].sum(),
"average_age": round(df["age_at_valuation"].mean(), 2),
"average_mortality_rate": df["mortality_rate"].mean(),
"average_lapse_rate": df["lapse_rate"].mean(),
"total_npv": df["npv"].sum(),
}
summary_af = ActuarialFrame(summary_data)
print(summary_af.collect())
=== FINAL RESULTS ===
Complete results:
shape: (3, 25)
┌───────────┬─────┬────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬───────────┬───────────┬──────────┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┬───────────┐
│ policy_id ┆ age ┆ gender ┆ smoking_st ┆ sum_assure ┆ issue_date ┆ valuation_ ┆ annual_pre ┆ interest_r ┆ policy_ter ┆ days_in_fo ┆ years_in_f ┆ age_at_va ┆ sex_smoki ┆ age_last ┆ policy_du ┆ mortality ┆ lapse_rat ┆ remaining ┆ pv_future ┆ pv_sum_as ┆ npv ┆ expected_ ┆ expected_ ┆ net_cash_ │
│ --- ┆ --- ┆ --- ┆ atus ┆ d ┆ --- ┆ date ┆ mium ┆ ate ┆ m_yea… ┆ rce ┆ orce ┆ luatio… ┆ ng ┆ --- ┆ ration… ┆ _rate ┆ e ┆ _term ┆ _premi… ┆ sured ┆ --- ┆ claims ┆ premiu… ┆ flows │
│ str ┆ i64 ┆ str ┆ --- ┆ --- ┆ date ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ f64 ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ f64 ┆ --- ┆ --- ┆ --- │
│ ┆ ┆ ┆ str ┆ i64 ┆ ┆ date ┆ i64 ┆ f64 ┆ i64 ┆ i64 ┆ f64 ┆ f64 ┆ str ┆ ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ ┆ f64 ┆ f64 ┆ f64 │
╞═══════════╪═════╪════════╪════════════╪════════════╪════════════╪════════════╪════════════╪════════════╪════════════╪════════════╪════════════╪═══════════╪═══════════╪══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ P001 ┆ 35 ┆ M ┆ NS ┆ 100000 ┆ 2022-01-15 ┆ 2024-12-31 ┆ 1200 ┆ 0.03 ┆ 20 ┆ 1081 ┆ 2.96 ┆ 37.96 ┆ MNS ┆ 37.0 ┆ 2.0 ┆ 0.0 ┆ 0.08 ┆ 17.04 ┆ -15827.94 ┆ 60430.15 ┆ 44602.21 ┆ 0.0 ┆ 1104.0 ┆ 1104.0 │
│ P002 ┆ 42 ┆ F ┆ NS ┆ 250000 ┆ 2021-06-01 ┆ 2024-12-31 ┆ 2800 ┆ 0.035 ┆ 25 ┆ 1309 ┆ 3.58 ┆ 45.58 ┆ FNS ┆ 45.0 ┆ 3.0 ┆ 0.0 ┆ 0.12 ┆ 21.42 ┆ -41711.56 ┆ 119651.38 ┆ 77939.82 ┆ 0.0 ┆ 2464.0 ┆ 2464.0 │
│ P003 ┆ 55 ┆ M ┆ S ┆ 500000 ┆ 2023-03-10 ┆ 2024-12-31 ┆ 8500 ┆ 0.04 ┆ 15 ┆ 662 ┆ 1.81 ┆ 56.81 ┆ MS ┆ 56.0 ┆ 1.0 ┆ 0.02 ┆ 0.05 ┆ 13.19 ┆ -85825.5 ┆ 298057.64 ┆ 212232.14 ┆ 10000.0 ┆ 8075.0 ┆ -1925.0 │
└───────────┴─────┴────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴────────────┴───────────┴───────────┴──────────┴───────────┴───────────┴───────────┴───────────┴───────────┴───────────┴───────────┴───────────┴───────────┴───────────┘
=== SUMMARY METRICS ===
Portfolio summary:
shape: (1, 6)
┌──────────────────┬──────────────────┬─────────────┬──────────────────┬──────────────────┬───────────┐
│ total_sum_assur… ┆ total_annual_pr… ┆ average_age ┆ average_mortali… ┆ average_lapse_r… ┆ total_npv │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞══════════════════╪══════════════════╪═════════════╪══════════════════╪══════════════════╪═══════════╡
│ 850000 ┆ 12500 ┆ 46.78 ┆ 0.006667 ┆ 0.083333 ┆ 334774.17 │
└──────────────────┴──────────────────┴─────────────┴──────────────────┴──────────────────┴───────────┘
1. Load Model Points and Setup Assumptions¶
Model Setup
import datetime
import polars as pl
from gaspatchio_core import ActuarialFrame
from gaspatchio_core.assumptions import Table
# Configure Polars for better terminal display
pl.Config.set_tbl_cols(-1) # Show all columns
pl.Config.set_tbl_width_chars(300) # Wider width to show full column names
pl.Config.set_fmt_str_lengths(15) # Limit string length for readability
pl.Config.set_tbl_rows(-1) # Show all rows
# Create sample policy data
model_data = {
"policy_id": ["P001", "P002", "P003"],
"age": [35, 42, 55],
"gender": ["M", "F", "M"],
"smoking_status": ["NS", "NS", "S"],
"sum_assured": [100000, 250000, 500000],
"issue_date": [
datetime.date(2022, 1, 15),
datetime.date(2021, 6, 1),
datetime.date(2023, 3, 10),
],
"valuation_date": [
datetime.date(2024, 12, 31),
datetime.date(2024, 12, 31),
datetime.date(2024, 12, 31),
],
"annual_premium": [1200, 2800, 8500],
"interest_rate": [0.03, 0.035, 0.04],
"policy_term_years": [20, 25, 15],
}
af = ActuarialFrame(model_data) # (1)!
# Create mortality table
mortality_data = pl.DataFrame(
{
"age_last": [
35,35,35,35,37,37,37,37,42,42,42,42,45,45,45,45,55,55,55,55,56,56,56,56],
"sex_smoking": ["MNS","FNS","MS","FS","MNS","FNS","MS","FS","MNS","FNS","MS","FS", "MNS","FNS","MS","FS", "MNS","FNS","MS","FS","MNS","FNS","MS","FS"],
"mortality_rate": [0.001,0.0008,0.002,0.0015,0.0012,0.001,0.0025,0.0018,0.002,0.0015,0.004,0.003,0.0025,0.002,0.005,0.0035,0.008,0.006,0.015,0.012,0.009,0.007,0.018,0.014],
}
)
mortality_table = Table( # (2)!
name="mortality_demo",
source=mortality_data,
dimensions={"age_last": "age_last", "sex_smoking": "sex_smoking"},
value="mortality_rate",
)
# Create lapse table
lapse_data = pl.DataFrame(
{
"policy_duration": [1, 2, 3, 4, 5],
"lapse_rate": [0.05, 0.08, 0.12, 0.15, 0.18],
}
)
lapse_table = Table( # (3)!
name="lapse_demo",
source=lapse_data,
dimensions={"policy_duration": "policy_duration"},
value="lapse_rate",
)
- ActuarialFrame: Wraps Polars DataFrames with actuarial-specific functionality and Excel-like operations
- Multi-Dimensional Tables: Supports complex lookups with multiple dimensions. Keep source data in it's original format.
- Simple Curve Tables: One-dimensional assumption tables for straightforward lookups
2. Date Calculations Using Excel Functions¶
Date Calculations
# Calculate policy duration using Excel functions
af.days_in_force = af.valuation_date.excel.days(af.issue_date) # (1)!
af.years_in_force = af.issue_date.excel.yearfrac(
af.valuation_date, basis="act/act"
).round(2) # (2)!
af.age_at_valuation = (af.age + af.years_in_force).round(2) # (3)!
- Excel DAYS Function: Calculate exact days between dates
- Excel YEARFRAC Function: Calculate fractional years with actual/actual basis
- Precision Control: Round calculations to 2 decimal places for actuarial precision
3. Assumption Table Lookups¶
Rate Lookups
# Prepare lookup keys
af.sex_smoking = af.gender + af.smoking_status # (1)!
af.age_last = af.age_at_valuation.floor()
af.policy_duration_int = af.years_in_force.floor()
# Perform lookups
af.mortality_rate = mortality_table.lookup(
age_last=af.age_last, sex_smoking=af.sex_smoking
).round(2) # (2)!
af.lapse_rate = lapse_table.lookup(policy_duration=af.policy_duration_int).round(2) # (3)!
- Dynamic Key Creation: Build lookup keys by combining gender and smoking status
- Multi-Dimensional Lookup: Retrieve mortality rates using age and gender/smoking combinations
- Single Dimension Lookup: Retrieve lapse rates using policy duration only
4. Present Value Calculations Using Excel PV Function¶
Financial Calculations
# Calculate remaining term and present values
af.remaining_term = (af.policy_term_years - af.years_in_force).round(2)
af.pv_future_premiums = af.interest_rate.excel.pv(
nper=af.remaining_term, pmt=af.annual_premium
).round(2) # (1)!
af.pv_sum_assured = (
af.sum_assured / (1 + af.interest_rate) ** af.remaining_term
).round(2) # (2)!
af.npv = (af.pv_sum_assured + af.pv_future_premiums).round(2)
- Excel PV Function: Calculate present value of future premium payments using Excel's PV function
- Manual PV Calculation: Calculate present value of sum assured using discounting
5. Cash Flow Projections¶
Cash Flow Analysis
# Calculate expected cash flows
af.expected_claims = (af.mortality_rate * af.sum_assured).round(2)
af.expected_premiums = (af.annual_premium * (1 - af.lapse_rate)).round(2)
af.net_cash_flows = (af.expected_premiums - af.expected_claims).round(2)
6. Execute and Display Results¶
Results Output
# Execute all calculations in one go (single query plan)
df = af.collect() # (1)!
print(df)
# Calculate portfolio-level summary metrics
summary_data = {
"total_sum_assured": df["sum_assured"].sum(), # (1)!
"total_annual_premium": df["annual_premium"].sum(),
"average_age": round(df["age_at_valuation"].mean(), 2),
"average_mortality_rate": df["mortality_rate"].mean(),
"average_lapse_rate": df["lapse_rate"].mean(),
"total_npv": df["npv"].sum(),
}
summary_af = ActuarialFrame(summary_data) # (2)!
print(summary_af.collect())
- Column Operations: Perform aggregations on individual columns (sum, mean) rather than whole frame operations
- Single Execution: Execute all calculations in one query plan for optimal performance
5. Advanced Features¶
Gaspatchio supports advanced actuarial modeling features:
- Vector Operations: Generate projection timelines with list columns for efficient calculations
- Excel Function Compatibility: Built-in support for Excel functions like YEARFRAC, date serialization, and financial calculations
- Mathematical Functions: Support for power, logarithmic, and financial functions
- Element-wise /Vector based Operations: Automatic broadcasting across list columns
- High Performance: Rust-powered engine with SIMD and parallel processing
- Memory Efficient: Lazy evaluation and optimized memory usage for large datasets