Skip to main content

scelo for Python

Soft data → tools → hard data, in as few lines as it takes to be right.

scelo is the Scelo IDE's brain layer as a Python library: the same data typing, the same cleaning rules, the same life tables, reserving engine and forecast model that the desktop workbench runs, for actuaries who would rather write three lines of code than click through a workstation.

import scelo as sc

df  = sc.load("claims.csv")        # typed the way Scelo IDE types it
df  = sc.clean(df)                 # the IDE's safe cleaning ops, audited
res = sc.reserve(df)               # chain ladder, Mack, BF, ODP bootstrap
sc.report(res, to="pack.html")     # a board pack: numbers that carry their basis

That is a reserving exercise: triangle built from a long claims file with the origin, development and amount columns inferred, four methods, a standard error, a bootstrap distribution, and a report whose every table prints its basis, its caveats and a content hash.

Install

pip install scelo                  # numpy + pandas only
pip install "scelo[stats]"         # + scipy, statsmodels (GLMs via statsmodels, scipy fits)
pip install "scelo[life]"          # + lifelib 0.14.0 / modelx 0.32.0, the pair Scelo IDE ships
pip install "scelo[viz]"          # + matplotlib for the sc.plot_* family
pip install "scelo[all]"

The core depends on numpy and pandas alone and runs on the CPython that Scelo IDE bundles. Everything statistical has a numpy implementation and uses scipy / statsmodels when they are there (the numpy GLM is tested to agree with statsmodels to 1e-5).

The idea

Stata gets one thing right: describe, tab, regress are whole analyses in a word. pandas gets another: the frame is the lingua franca. scelo is both. Every function takes a DataFrame first, infers the columns it needs from their names (accident_year, AY, origin all mean origin), and returns a Table: a real pandas.DataFrame that also carries

  • title: what it is,
  • basis: one line of provenance (Gompertz–Makeham (illustrative) · i = 4 %),
  • notes: the things an actuary should know before trusting it,
  • provenance: a content hash, timestamp and audit trail once you sc.hard() it.

Print a Table and the notes print under it. Slice it, merge it, plot it: it is still pandas. t.df gives the plain frame back.

>>> sc.life_table().head(3)
   age        qx        px         lx         dx            Lx            Tx         ex
0   20  0.000250  0.999750  100000.00  24.963902  99987.518049  6.591309e+06  65.913088
1   21  0.000253  0.999747   99975.04  25.325403  99962.373399  6.491321e+06  64.929422
2   22  0.000257  0.999743   99949.71  25.732197  99936.844601  6.391359e+06  63.945747
— Life table · Gompertz–Makeham (illustrative)
  basis: Gompertz–Makeham (illustrative)
  · Mortality is Scelo's ILLUSTRATIVE Gompertz–Makeham basis (A = 0.00022, B = 2.7e-6, c = 1.124),
    not a published standard table: swap in your own qx column or parameters before relying on the figures.
  · Radix l(20) = 100,000; table closed at age 110 (qx set to 1). Lx uses the uniform-deaths approximation lx − ½dx.

Sixty-second tour

import scelo as sc

# ── soft data ────────────────────────────────────────────────────────────
df = sc.sample("dirty")            # the IDE's messy-intake demo (or sc.load("file.csv"))
sc.profile(df)                     # type, missing, unique, five-number summary, fences, top values
sc.describe(df)                    # Bessel sd, type-7 quantiles, G1/G2 shape, Jarque–Bera, ranked by CV
sc.tab(df, "Region")               # Stata's tab
sc.suggest(df)                     # the cleaning plan with evidence: 12 ops, 7 safe
clean = sc.clean(df)               # the 9 safe ops (trim, mojibake, missing markers, numbers, dates, booleans, sentinels …)
clean = sc.clean(df, "all")        # + dedupe, drop empty/constant, impute, cap outliers, snake_case: until clean
sc.combine(a, b)                   # append or join-left, decided from the schemas and keys, with the evidence

# ── life ─────────────────────────────────────────────────────────────────
sc.life_table(qx_df)               # age, qx, px, lx, dx, Lx, Tx, ex from any basis (qx / lx / deaths+exposure / Makeham)
sc.commutation(i=0.04)             # Dx Nx Cx Mx Rx Sx
sc.factors(i=0.04, n=10)           # äx ax Ax äx:n A¹x:n nEx Ax:n
sc.premium(i=0.04, product="term") # net premium per 1,000 SA, age × term grid
sc.ae(experience_df)               # actual vs expected by age band against a basis
sc.graduate(crude_qx, h=100)       # Whittaker–Henderson
sc.lee_carter(rates_df)            # SVD fit + random-walk-with-drift forecast, 95 % interval
sc.kaplan_meier(df)                # S(t), Greenwood SE, log-log bounds
sc.basicterm(model_points)         # lifelib BasicTerm_ME monthly projection, pure numpy
sc.scr_life(model_points)          # Solvency II life SCR: standard-formula shocks on that projection
sc.csm(model_points, ra=0.05)      # IFRS 17 general-model CSM and its coverage-unit roll-forward
sc.lifelib_run("basiclife", "BasicTerm_ME", model_points)   # the real lifelib model (pip install "scelo[life]")

# ── reserving ────────────────────────────────────────────────────────────
tri = sc.triangle(claims)          # origin × development, cumulative, from a long file
sc.chain_ladder(tri); sc.mack(tri); sc.bf(tri); sc.cape_cod(tri, premium); sc.bootstrap(tri, n=1000)
sc.reserve(claims)                 # all of the above in one table

# ── finance & risk ──────────────────────────────────────────────────────
sc.discount_curve({1: .03, 5: .04, 10: .05}); sc.smith_wilson(tenors, rates, ufr=.042)
sc.pv(cf, .05); sc.irr(cf); sc.annuity_certain(10, .05, due=True); sc.duration(cf, .05)
sc.aggregate_loss("poisson", "lognormal", lam=5, mu=8, sigma=1)   # Panjer / FFT / Monte Carlo
sc.fit(losses); sc.var(x, .995); sc.tvar(x, .995)
sc.credibility(df, "group", "loss_ratio"); sc.aggregate_scr({"mortality": 100, "lapse": 200})

# ── pricing & fairness ──────────────────────────────────────────────────
m = sc.glm(df, "claims ~ C(region) + age", "poisson", offset="exposure")   # base="first" for R / statsmodels reference levels
m.relativities(); m.predict(new); sc.lift(y, m.fitted)
sc.fairness(df, "y", "score", "group"); sc.fairness_audit(df, "score", "protected", ["age"])

# ── forecast & swarm ────────────────────────────────────────────────────
sc.wmtr("pension scheme with a weakening sponsor covenant")   # W(M,T,R) Monte Carlo, same numbers as the IDE
sc.council("…scenario…", subset=32)                            # convene the professional council (needs the swarm)
sc.society("…scenario…", size=200)                             # simulate a population's response

# ── charts (matplotlib) ─────────────────────────────────────────────────
sc.plot_rates(df, "country", "lapsed", exposure="exposure")   # rate by group, 95 % intervals
sc.plot_relativities(m); sc.plot_projection(bt); sc.plot_scr(scr); sc.plot_csm(csm); sc.plot_triangle(tri)

# ── hard data ───────────────────────────────────────────────────────────
t = sc.hard(sc.mack(tri).table, assumptions={"tail": 1.0})
sc.report(t, sc.life_table(), to="pack.html")                  # board pack with notes, hashes, audit trail
sc.audit()                                                     # everything the tools layer did this session

sc.cheatsheet() prints the one-screen version. df.sc.clean(), df.sc.triangle().sc.mack() give you the same functions as methods.

What comes from where

Area Source of truth inside Scelo Notes
Import typing, profiling, descriptive stats packages/scelo-core type-7 quantiles, Bessel sd, G1/G2, Jarque–Bera, CV ranking: identical
Cleaning (18 ops, thresholds, pass loop) apps/web/.../cleaning.ts same token sets, regexes and thresholds; suggest() is the banner
Combine (append / join-left, key detection) apps/web/.../combineData.ts first right match wins, _2 renames
Life tables, commutation, factors, premiums, A/E, model points packages/scelo-core/actuarialTables.ts the chat's "build a life table at 4 %"
Chain ladder, Mack, BF, ODP bootstrap apps/web/.../bridges/chainladderPython.ts Mack reproduces the published RAA figures (IBNR 52,135; SE 26,909)
BasicTerm projection apps/web/.../lifelibBasicTerm.ts Scelo's illustrative assumptions; lifelib_run for the real thing
WMTR forecast apps/swarm/src/shared/wmtr.ts bit-exact RNG; tables agree to 1e-14
Workspace bottleneck / active subspace apps/web/.../bridges/bottleneckPython.ts
Swarm client apps/swarm/src/server/index.ts council, society, augment, interventions, justifications

Lines of code

The same reserving study, three ways:

lines
pandas + numpy from scratch (triangle, factors, Mack MSE, ODP bootstrap) ≈ 120
chainladder ≈ 12
scelo sc.reserve("claims.csv") 1

and the caveats, the basis line and the hash come with it.

The swarm

Scelo IDE bundles the swarm (a Bun server on 127.0.0.1:3010): a stratified council of 8 professions × 16 MBTI types × 2 genders that deliberates over a scenario with the WMTR evidence injected, and a society simulator. sc.council, sc.society, sc.augment, sc.intervene and sc.justify talk to it with the standard library only; sc.connect(url) points elsewhere. The WMTR engine itself is ported, so sc.wmtr needs no server.

Testing

pip install "scelo[dev]" && pytest

The suite checks the IDE's own golden values (actuarial-table identities, the hand-computed descriptive statistics, the reserving fixture), the published RAA results, a TypeScript-generated WMTR fixture, numpy-vs- statsmodels GLM parity for five families, and every cleaning rule.

License

Scelo IDE Source-Available License v1.1 (LicenseRef-Scelo-IDE-1.1), the same license as the IDE: free to use, including commercially; a 3 % royalty on a Licensed Product's gross revenue above ZAR 1,000,000 lifetime. See LICENSE.

Intelligent Actuaries (Pty) Ltd · scelo@intelligentactuaries.com

Metadata

Release files for scelo 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for scelo 0.1.0
File Size Uploaded
scelo-0.1.0.tar.gz 282.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for scelo 0.1.0
File Interpreter ABI Platform
scelo-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 541.8 kB

Release files / scelo-0.1.0.tar.gz

Download URL scelo-0.1.0.tar.gz
Size 282.7 kB
Tags Source
SHA-256 checksum
How to use checksums
09252de40d4ab31dd34830cc10f9f0b0f83e0d7a92b966e2e0a37cfedec765b4
BLAKE2b-256 checksum
How to use checksums
2695bf8835874faf3569ca038fba8382ae2e222b3b54084735ba7560f1007a33
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

Release files / scelo-0.1.0-py3-none-any.whl

Download URL scelo-0.1.0-py3-none-any.whl
Size 259.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f16b6c71533333c14ed0956d79555ebd4a4e33bcb73c26c8fd7c8da1f6619660
BLAKE2b-256 checksum
How to use checksums
c3d0dcdd20bfdff78818a8b78f7131e8fce137d6b1c6918d085ff897eace6678
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page