saberkit
Live sabermetrics for marimo notebooks, computed in Rust: the "plus" family of baseball statistics (OPS+, sOPS+, tOPS+, ERA+, wRC+), the FanGraphs "minus" family (ERA-, FIP-, xFIP-), and Baseball-Savant-style league percentile rankings — the 1–100 "bubbles".
Fetch a season, compute everything, and drive it with sliders:
import marimo as mo
import saberkit
season = mo.ui.dropdown(options=list(saberkit.compute.SEASONS), value=2024)
park_factor = mo.ui.slider(start=85, stop=115, value=100)
table = saberkit.compute.batting_table(
saberkit.ingest.load_batting(season.value),
league="season",
season=season.value,
park_factor=float(park_factor.value),
)
Every rate stat, OPS+, wRC+, and the percentile ranks recompute reactively as
the controls move. Two ready-to-run notebooks ship in
notebooks/ — a Savant-style percentile explorer and a
pitcher scout for ERA-/FIP-:
pip install "saberkit[marimo,data]" # data adds pybaseball for live fetching
marimo edit notebooks/percentile_explorer.py # runs offline without `data` too
Also a fast batch library
Pass Arrow arrays and you get Arrow back through the
PyCapsule interface
— polars.Series, pyarrow.Array, and pandas ArrowDtype columns all pass
with no copy and no conversion. Pass plain numbers and you get a number:
>>> round(saberkit.obp(h=150, bb=60, hbp=5, ab=500, sf=5), 4)
0.3772
league = saberkit.LeagueContext.for_season(2024)
rating = saberkit.wrc_plus(0.390, ctx=league, park_factor=102)
saberkit has zero required runtime dependencies — not even pyarrow.
The notebook layer (ingest, compute) imports polars lazily; scalar use
needs nothing installed at all.
Install
pip install saberkit # core library, no dependencies
pip install "saberkit[marimo]" # adds marimo + altair + polars for notebooks
pip install "saberkit[data]" # adds pybaseball helpers for fetching data
Requires Python 3.11+.
Design
Undefined is not an error. A player with zero at-bats has no batting
average. Every statistic returns None — an Arrow null in batch mode — rather
than raising, returning NaN, or returning infinity. Nulls propagate
elementwise, and the table layer extends the rule to whole populations: filter
a dashboard until nobody qualifies and the percentile columns come back
all-null instead of crashing your cell.
Misconfiguration is an error. A null player row is normal; a NaN league
constant silently poisons every result it touches. Bad constants and mismatched
array lengths raise saberkit.SaberError (a ValueError subclass).
League baselines are inputs, not guesses. compute.batting_table makes
you say which population defines "league average": bundled season constants
(league="season"), the data on screen (league="sample"), or your own
LeagueContext. Whether pitchers' hitting counts is a methodological choice,
and it stays with you.
Park factors are on the 100-scale everywhere (100 = neutral), matching how Baseball-Reference and FanGraphs publish them.
Layout
The statistical core is a separate crate that depends on nothing but
thiserror — no Python, no Arrow, no I/O:
| Crate / package | Role |
|---|---|
crates/saberkit-core |
The statistics. Pure Rust, testable with cargo test, no interpreter needed. Usable standalone from Rust. |
crates/saberkit-py |
The only crate that knows Python exists. Arrow conversion and PyO3 bindings; contains no statistics. |
python/saberkit/ingest.py |
Fetch-and-reshape: publisher column names → canonical polars frames. No formulas. |
python/saberkit/compute.py |
One-call tables: wires columns into the Rust-backed functions and reattaches results. No formulas. |
notebooks/ |
Shipped marimo apps built on the above. |
Development
cargo test -p saberkit-core # statistics, no Python involved
cargo test -p saberkit-py --no-default-features # binding layer (cast, nulls, chunks)
cargo clippy --workspace -- -D warnings
maturin develop -E dev # build + install into the active venv
pytest # incl. headless runs of every notebook
SABERKIT_OFFLINE=1 marimo edit notebooks/pitcher_explorer.py # notebooks without network
Maintainers should follow RELEASING.md for versioning,
artifact verification, trusted-publisher setup, and tagging.
What's implemented
| Group | Statistics |
|---|---|
| Rate stats | obp slg avg ops iso babip woba fip xfip era total_bases singles |
| Plus family | ops_plus sops_plus tops_plus era_plus wraa wrc wrc_plus |
| Minus family | era_minus fip_minus xfip_minus |
| Percentiles | LeagueDistribution percentile_ranks batter_qualifier pitcher_qualifier |
| Innings | ip_to_outs outs_to_innings |
| League context | LeagueContext.from_totals LeagueContext.for_season (2010–2025) |
| Tables | compute.batting_table compute.pitching_table |
Accuracy
Statistics that depend only on their inputs — OBP, SLG, wOBA, FIP, sOPS+, tOPS+ — reproduce published figures exactly. Those involving park factors (OPS+, ERA+, and the minus family) approximate the publishers' internal pipelines, which use regressed multi-year park factors; expect agreement to within a point or two. Each function's docstring says which it is.
Innings pitched
Baseball records innings in a notation where the digit after the decimal counts
outs: 190.1 is 190⅓ and 190.2 is 190⅔. Reading those as decimals
understates the denominator by about 0.7% — enough to shift a FIP in the third
decimal and quietly break comparisons against published figures. saberkit
converts through outs internally and rejects impossible values like 190.4.
Season constants and provenance
LeagueContext.for_season(year) supplies completed-season MLB constants for
2010–2025. They are saberkit constants derived from Retrosheet regular-season
play-by-play—not copies of FanGraphs' Guts! table. The population is all MLB
batters, including pitcher batting where it occurred. The generator measures
each season's RE24 matrix, derives linear event weights relative to outs, scales
wOBA to league OBP, and computes the FIP constant from league totals.
The derivation is reproducible with
scripts/generate_season_constants.py.
Every season row records a SHA-256 digest of its source event files and the
generated module records the exact Chadwick Bureau Retrosheet revision. Raw
Retrosheet data is not packaged. See NOTICE for attribution.
Because the methodology and population are explicit, these constants can
differ slightly from publisher-specific tables. In-progress seasons are never
bundled; requesting an unsupported year raises SaberError.
Data sources
saberkit.data wraps pybaseball's FanGraphs scrapers behind the optional
data extra; saberkit.ingest reshapes their output — and any polars/
pandas/pyarrow frame you already have — into canonical lowercase columns.
Scraped sites change without notice, so treat fetching as convenience, not
contract, and pin snapshots for anything reproducible. The shipped notebooks
fall back to committed fixtures when offline (SABERKIT_OFFLINE=1 forces it).
Not yet included
- Published wheels. CI builds candidate wheels for Linux x86_64/aarch64, macOS Intel/Apple Silicon, and Windows x86_64, but no PyPI release has been published yet.
- Park factors. You supply them.
saberkitdoes not compute or bundle any.
License and data attribution
MIT OR Apache-2.0
Bundled season constants are derived from Retrosheet data under its attribution
terms; see NOTICE.
Metadata
Release files for saberkit 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| saberkit-0.1.0.tar.gz | 71.2 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| saberkit-0.1.0-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details |
| saberkit-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| saberkit-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| saberkit-0.1.0-cp311-abi3-macosx_11_0_arm64.whl | CPython 3.11 | abi3 | macOS 11.0+ ARM64 | Details |
| saberkit-0.1.0-cp311-abi3-macosx_10_12_x86_64.whl | CPython 3.11 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 15.6 MB
Release files / saberkit-0.1.0.tar.gz
| Download URL | saberkit-0.1.0.tar.gz |
|---|---|
| Size | 71.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ea7d4e3ed9185e7b91382aa6ca2d57b9c1328ba75026c9bef42b631245b94e95
|
|
BLAKE2b-256 checksum How to use checksums |
eeb0f019d9151b4a02f055fa64cae612164f1fe541b94bdc23f684665da66ed9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency logRelease files / saberkit-0.1.0-cp311-abi3-win_amd64.whl
| Download URL | saberkit-0.1.0-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 3.3 MB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
7d149357c62faadf8b0ee9fd93ff07a4dfe89b2e801cf75dec61888114c88eae
|
|
BLAKE2b-256 checksum How to use checksums |
cfded1addf8075aa1026b1fae3200b8c73b4cd11c6d82615d548896ff6f0329b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency logRelease files / saberkit-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | saberkit-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.3 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
864fd294840ec2e507343ec36460aa24976d4798cc93758c24eaf75864ac0f5d
|
|
BLAKE2b-256 checksum How to use checksums |
16fd7eceb3bad311fbd8e00c370407844ffe4120a2973641cc87171017a94460
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency logRelease files / saberkit-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | saberkit-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
c605a007fd914f584e308ed8615358e5cd30ee444cdc8f89cf37abea27d91e9c
|
|
BLAKE2b-256 checksum How to use checksums |
275458c9f9532bc6eb465240ddd7352988b42f07789f4348265ab9b46f784686
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency logRelease files / saberkit-0.1.0-cp311-abi3-macosx_11_0_arm64.whl
| Download URL | saberkit-0.1.0-cp311-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.9 MB |
| Tags | CPython 3.11 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
31d57eafa780c313cf29628683dab929af42fc2b36eabd0d7b6509c49962ee26
|
|
BLAKE2b-256 checksum How to use checksums |
5577c399e3300a45a59dfdd0b45e4c424d7f05224fbbf18bf9df0e3f8592ca66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency logRelease files / saberkit-0.1.0-cp311-abi3-macosx_10_12_x86_64.whl
| Download URL | saberkit-0.1.0-cp311-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.11 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
3730b78479542dc8cf15f6c7605bc806ad15685408a1ed21dbc72afb5f855f16
|
|
BLAKE2b-256 checksum How to use checksums |
90c1be1d7cdfa270511a2450fd5637ae879bfae3fdb0979070c99dacf5a023fe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.
Transparency log