sas7bdat-polars
A Polars IO plugin for reading SAS7BDAT files, backed by the
SIMD-accelerated sas7bdat Rust parser. It registers
a native IO source via polars.io.plugins.register_io_source, so scans are lazy and
support projection and predicate pushdown straight into the reader.
Installation
pip install sas7bdat-polars
To also get the standalone sas7bdat command (convert / info / head), install the extra:
pip install "sas7bdat-polars[cli]"
That pulls in sas7bdat-cli, a separate binary
wheel built from the same parser. It is kept separate on purpose: it carries no polars pin
and no Python floor, so CLI-only users do not inherit this package's constraints.
Version constraints
This wheel is tightly coupled to its build environment:
- Polars is pinned to
1.41.*. The extension shares the Polars Rust ABI (viapolars-ffi) with the in-processpolarspackage, so the installedpolarsmust match the version the wheel was built against. A mismatch is undefined behavior, not a graceful error. - Built against the CPython stable ABI (
abi3, minimum 3.12), so a singlecp312-abi3wheel runs on CPython 3.12 and newer.
Usage
import polars as pl
import sas7bdat_polars as sp
# Eager read — the ergonomic default. ALWAYS pass `columns`: SAS7BDAT is wide and
# row-oriented, so projecting the columns you need is the biggest speed-up.
df = sp.read_sas("data.sas7bdat", columns=["name", "age"])
df = sp.read_sas("data.sas7bdat", columns=["age"], n_rows=1_000_000) # bound I/O
df = sp.read_sas("data.sas7bdat", columns=["age"], predicate=pl.col("age") > 30)
# Lazy scan — returns a LazyFrame; filters/projections push down into the reader.
lf = sp.scan_sas("data.sas7bdat", columns=["name", "age"])
df = lf.filter(pl.col("age") > 30).collect()
# Header-only metadata (row/column count, encoding, size) without decoding the body.
info = sp.sas_info("data.sas7bdat") # {'n_rows': ..., 'n_columns': ..., 'encoding': ...}
# Hydrate value labels from a companion catalog.
lf = sp.scan_sas("data.sas7bdat", catalog_path="formats.sas7bcat")
# Inspect the Arrow schema without reading rows.
schema = sp.schema_for_file("data.sas7bdat")
Performance & threading
Benchmarked on a 2.1 GB / 4041-column file (warm cache): a full .collect() takes
~1.8 s (decodes every column) while read_sas(columns=[one]) takes ~0.04 s. The rules:
- Always project (
read_sas(columns=...)/scan_sas(columns=...)). Reading one column instead of all is ~50× on wide files and the biggest lever by far. - Bound huge reads with
n_rows=when you only need a peek — the reader's row limit stops after the first pages, cutting I/O. - Let the reader parallelise. It runs its own SIMD page decode across all cores;
tune with
set_scan_threads(n)(orSAS7BDAT_SCAN_THREADS). Do not throttle Polars' own pool (POLARS_MAX_THREADS) — it does not control the decoder and only starves the pipeline. (The library warns if it detects this mistake.) - Streaming works (
.collect(engine="streaming")): the reader isSend + Sync.
sp.set_scan_threads(8) # cap decode threads; set_scan_threads(0) resets to all cores
sp.scan_threads() # -> effective count
# Return character columns as Categorical (low-cardinality category codes).
lf = sp.scan_sas("survey.sas7bdat", categorical=True)
# SAS stores every numeric column as a float. Declare integer-coded columns
# (registry/category codes) explicitly to get Int64 out instead of Float64:
lf = sp.scan_sas(
"bef2020.sas7bdat",
schema_overrides={"KOEN": pl.Int64, "SOCIO13": pl.Int64, "HFAUDD": pl.Int64},
)
categorical=True casts every character column to Categorical in the lazy plan
(via Polars' own cast — equivalent to
sp.scan_sas(path).with_columns(pl.col(pl.String).cast(pl.Categorical))). The
benefit is downstream: group-by / join / sort on these columns run on u32
codes and are ~10–15× faster. It is not a read or memory win — Polars' String
is already compact, so casting adds a little to the read (~0.6s on a 2.5k-string-
column file) and uses more memory; only enable it when you'll group/join on the
string columns. (Contrast with the R binding's categorical=TRUE, where factor
is a read-speed and memory win.)
schema_overrides is applied at schema time, so the lazy schema and the collected
frame always agree, and the same override map yields the same dtypes for every file
of a register. Override names that don't exist in a given file are ignored, so a
register-wide map can be passed wholesale. If a file contains a value that violates
an Int64 override (non-integral or out of range), the scan fails with an error
naming the column, row, and value — it never silently falls back to Float64.
Supported override dtypes: Int64, Float64, Date, Datetime, Time, String,
Binary (numeric columns can only be re-typed to numeric/temporal dtypes, character
columns to String/Binary). Feature-detect with
sp.PLUGIN_CONTRACT_VERSION >= "sas7bdat_polars.v2".
License
MIT — see the repository for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sas7bdat_polars-0.7.0-cp312-abi3-win_amd64.whl.
File metadata
- Download URL: sas7bdat_polars-0.7.0-cp312-abi3-win_amd64.whl
- Upload date:
- Size: 5.3 MB
- Tags: CPython 3.12+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f67aca2210196766eca56d3f26de14ac1db2f5defae3a4428522adf2e3f127f
|
|
| MD5 |
585ef86501af181ed79a59411534a61e
|
|
| BLAKE2b-256 |
b130d06b12c265b5f23e7d303fad348190f5f850e837a2c69c991f6cf61a9d64
|
File details
Details for the file sas7bdat_polars-0.7.0-cp312-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: sas7bdat_polars-0.7.0-cp312-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 5.9 MB
- Tags: CPython 3.12+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bd27f0be93ad884d72d94d473f9d6fc55b517c80faf5a20989a2f389da53721
|
|
| MD5 |
828c60f06702a9f59ba48d994dd0213e
|
|
| BLAKE2b-256 |
8ea4c96673ed894d204c907e9e7c9704a2b53f7b59bc7f7f06fb33a078b31b2d
|
File details
Details for the file sas7bdat_polars-0.7.0-cp312-abi3-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: sas7bdat_polars-0.7.0-cp312-abi3-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 5.3 MB
- Tags: CPython 3.12+, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57e7884d454f709dc3caa42e3db5f2dae7ac08169055d38b27de5be9174cfd4c
|
|
| MD5 |
8cb44c885b3dd8050ea028c1b44f0799
|
|
| BLAKE2b-256 |
885f2af3e41f9752882f44a129f49c73daac798be7372cf7413843e7afb929e7
|
File details
Details for the file sas7bdat_polars-0.7.0-cp312-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: sas7bdat_polars-0.7.0-cp312-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 4.8 MB
- Tags: CPython 3.12+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f82858734596accf825b97da437457727291d0e8dd72b1ce4ccb8eb25a68e975
|
|
| MD5 |
24a62a2eae7f98a7d78ff45bd7657e34
|
|
| BLAKE2b-256 |
c8cf4ce5429c8f827d4d555e4aa60625f975c79ec627433c5f21f1376f3fb6c3
|