Skip to main content

skrecsys

PyPI Python License: MIT

Recommender systems in the scikit-learn style, built on NumPy, SciPy and scikit-learn.

skrecsys is a prototype of the recommender estimator API proposed in docs/slep_recommender_systems.md: a RecommenderMixin with a common fit / predict / recommend contract, top-k ranking metrics, a warm-start cross-validation splitter and classical collaborative-filtering estimators.

The estimators are Python; the inner loops are not. The similarity kernels, the elastic-net and least-squares solvers, the BPR sampler, identifier encoding, sparse matrix assembly and top-k selection are all written in Rust and exposed through PyO3 as skrecsys._core. They parallelize with rayon and release the GIL while they run, and the neighbourhood models score and rank a batch of queries inside one kernel, without ever materializing a dense score matrix in NumPy. Nothing in the crate uses unsafe.

The installed library is small: four runtime dependencies, all of them ones a scikit-learn user already has — NumPy, SciPy, scikit-learn and joblib, plus typing-extensions on Python 3.11 only. pandas is optional, and only needed for the dataset loaders' as_frame=True.

Installation

pip install skrecsys

or with uv:

uv add skrecsys

Requires Python 3.11+, on Linux, macOS or Windows. The wheels carry the compiled extension, so installing needs no Rust toolchain; they are built against the stable ABI, which means one wheel per platform covers every supported interpreter. Building from a source checkout needs a Rust toolchain and maturin.

Usage

Training data is an array of shape (n_interactions, 2) of user and item identifiers, with an optional interaction value y.

import numpy as np
from sklearn.model_selection import GridSearchCV

from skrecsys.metrics import make_recommender_scorer, ndcg_at_k
from skrecsys.model_selection import WarmStartKFold
from skrecsys.recommendation import ItemKNNRecommender

X = np.array([["alice", "matrix"], ["alice", "alien"], ["bob", "matrix"], ...])

rec = ItemKNNRecommender(n_neighbors=50).fit(X)
items, scores = rec.recommend(["alice", "bob"], n_recommendations=5)

search = GridSearchCV(
    ItemKNNRecommender(),
    {"n_neighbors": [10, 50, 200], "shrink": [0.0, 10.0]},
    cv=WarmStartKFold(n_splits=5, shuffle=True, random_state=0),
    scoring=make_recommender_scorer(ndcg_at_k, k=10),
).fit(X)

Datasets

Loaders download public datasets once, cache them in ~/skrecsys_data (override with data_home= or SKRECSYS_DATA) and return them as (user_id, item_id) pairs plus ratings, like sklearn.datasets.

from sklearn.model_selection import cross_validate

from skrecsys.datasets import fetch_movielens_100k

X, y = fetch_movielens_100k(return_X_y=True)
cross_validate(
    ItemKNNRecommender(),
    X,
    y,
    cv=WarmStartKFold(n_splits=5, shuffle=True, random_state=0),
    scoring=make_recommender_scorer(ndcg_at_k, k=10),
)

# official u1-u5 / ua / ub splits, timestamps and user/movie metadata
ml = fetch_movielens_100k(subset="u1")
cross_validate(
    ItemKNNRecommender(),
    ml.data,
    ml.target,
    cv=[(ml.train_indices, ml.test_indices)],
    scoring=make_recommender_scorer(ndcg_at_k, k=10),
)

as_frame=True returns pandas objects and requires pip install skrecsys[pandas].

Leaderboard

MovieLens 100K, official ua split, k=10, default hyper-parameters. Regenerate with python benchmarks/leaderboard.py --write-readme.

Model NDCG@10 P@10 R@10 hit rate MAP MRR cat cov user cov mean pop novelty
SLIM 0.3024 0.2558 0.2558 0.9226 0.1621 0.6418 0.2226 1.0000 247.5139 8.65
BPR 0.2800 0.2467 0.2467 0.9226 0.1451 0.5782 0.3542 1.0000 228.5243 8.84
EASE 0.2767 0.2382 0.2382 0.9035 0.1444 0.5888 0.3030 1.0000 228.8724 8.79
RP3Beta 0.2762 0.2407 0.2407 0.9215 0.1414 0.5911 0.2417 1.0000 235.4201 8.78
BM25 0.2661 0.2292 0.2292 0.8918 0.1370 0.5788 0.1024 1.0000 293.0034 8.36
ItemKNN 0.2550 0.2200 0.2200 0.9173 0.1268 0.5624 0.2887 1.0000 217.0530 8.86
MostPopular 0.1331 0.1215 0.1215 0.7306 0.0545 0.3218 0.0530 1.0000 371.7549 7.97
ALS 0.0422 0.0408 0.0408 0.3160 0.0144 0.1119 0.1464 1.0000 155.0729 10.09

Measured on Apple M4 Pro (12 usable cores), macOS-26.6.2-arm64-arm-64bit-Mach-O, CPython 3.14.3, numpy 2.5.3, skrecsys 0.3.0, _core built in release mode. The quality table above is deterministic and portable; the timings below are not comparable across machines or builds.

Wall clock per call. Each operation is sampled until it has spent 20s or reached its cap (1000 fits, 1000 recommend calls), after untimed warm-up calls (1 fit, 10 rank) so that no sample pays for a cold start; the samples columns say how many each row actually got, which is why a slow model shows fewer. The batch columns say what a single call processed: one fit covers the whole training split, and one recommend call ranks the entire catalog for every held-out user at once, so these are throughput numbers rather than single-request latency. Fit reports the spread a handful of samples can resolve; ranking is sampled often enough for nearest-rank quantiles, each of which is a call that really happened. Compare min across machines and watch max for the variance a run saw.

Model fit batch (interactions) fit samples fit min fit median fit max rank batch (users x items) rank samples rank mean rank median rank q95 rank q99
SLIM 90570 111 165 ms 181 ms 201 ms 943 x 1680 1000 2 ms 2 ms 2 ms 2 ms
BPR 90570 16 1.29 s 1.29 s 1.42 s 943 x 1680 1000 1 ms 1 ms 2 ms 2 ms
EASE 90570 691 27 ms 29 ms 49 ms 943 x 1680 788 25 ms 25 ms 28 ms 29 ms
RP3Beta 90570 1000 8 ms 8 ms 13 ms 943 x 1680 1000 2 ms 2 ms 2 ms 2 ms
BM25 90570 1000 7 ms 7 ms 26 ms 943 x 1680 1000 1 ms 1 ms 1 ms 1 ms
ItemKNN 90570 1000 6 ms 6 ms 27 ms 943 x 1680 1000 2 ms 2 ms 3 ms 3 ms
MostPopular 90570 1000 1 ms 1 ms 1 ms 943 x 1680 1000 1 ms 1 ms 1 ms 1 ms
ALS 90570 60 328 ms 332 ms 366 ms 943 x 1680 1000 2 ms 2 ms 2 ms 2 ms

Every model uses its default hyper-parameters, so this ranks the library's baselines, not the best each method can do. R@10 equals P@10 because the ua split holds out exactly 10 items per user, and user cov is 1 by construction: recommend raises rather than return a short list. The beyond-accuracy columns are the interesting ones — MostPopular has the highest mean pop and the lowest novelty, and BM25 buys its ranking score by covering a tenth of the catalog where EASE covers a third. SLIM leads on every ranking metric while keeping a fifth of the catalog reachable, at a similarity matrix a quarter as dense as RP3Beta's. BPR is the only model here trained on pairwise preferences rather than fitted in closed form, and it buys the second-best ranking with the widest catalog coverage of all; it is also the slowest to fit, and the entry is seeded, which pins it to a single thread.

The second table times the same runs. Both operations are batched — one fit over the whole training split, one recommend call scoring all 943 held-out users against all 1680 items the model saw — so these are throughput numbers, and the per-request latency of a single user is a different measurement. Ranking separates the models by what they have to touch rather than by how good they are: the neighbourhood models go through a kernel that accumulates a query, drops what the user has seen and keeps the best ten before moving on, so no score matrix is ever built and they finish in 1–2 ms; the factor models multiply a handful of factors per item and land in the same place; EASE is the outlier at 25 ms because its similarity matrix is dense and every query is scored against the whole of it. The max column is where a run's variance shows — ItemKNN and BM25 both fit in 6–7 ms but have a slowest sample four times that. How many samples a row gets is the budget's doing, which is why BPR at 1.3 s a fit gets sixteen and MostPopular gets its full thousand; --budget and --repeat buy more.

Scope

Module Contents Status
skrecsys RecommenderMixin, is_recommender implemented
skrecsys.metrics precision_at_k, recall_at_k, ndcg_at_k, average_precision_at_k, reciprocal_rank_at_k, hit_rate_at_k, make_recommender_scorer implemented
skrecsys.metrics catalog_coverage_at_k, user_coverage_at_k, mean_popularity_at_k, novelty_at_k, item_popularity implemented
skrecsys.datasets fetch_movielens_100k, get_data_home, clear_data_home implemented
skrecsys.model_selection WarmStartKFold implemented
skrecsys.utils.estimator_checks check_recommender, yield_recommender_checks implemented
skrecsys.recommendation MostPopularRecommender, ItemKNNRecommender, AlternatingLeastSquares, BM25Recommender, EASE, RP3Beta, SLIMElasticNet, BayesianPersonalizedRanking implemented
uv run python benchmarks/leaderboard.py                  # print the leaderboard
uv run python benchmarks/leaderboard.py --write-readme   # regenerate the tables above
uv run python benchmarks/leaderboard.py --repeat 20 --rank-repeat 5000   # tighter timings

Development

Native kernels (for example the libFM-style ALS solver) are written in Rust under rust/ and built by maturin as skrecsys._core, so development needs a Rust toolchain (rustup or your package manager). uv sync rebuilds the extension when Rust sources change.

git clone https://github.com/mrk-andreev/skrecsys.git
cd skrecsys
uv sync
uv run pytest
uv run pytest -m benchmark  # quality benchmarks on real datasets (downloads data)
LIBFM_BIN=/path/to/libfm/bin/libFM uv run pytest -m benchmark -k libfm  # ALS vs libFM
uv run --group reference pytest -m benchmark -k implicit  # BM25 vs implicit
uv run pytest -m benchmark -k rectools  # EASE vs RecTools (needs network on first run)
uv run pytest -m benchmark -k dacrema  # RP3Beta and SLIMElasticNet vs their reference framework (needs network)
uv run pytest -m benchmark -k cornac   # BayesianPersonalizedRanking vs Cornac (needs network on first run)
uv run ruff check
uv run ty check
cargo test
cargo clippy --all-targets -- -D warnings

The release profile uses fat LTO and a single codegen unit. [lib] crate-type deliberately lists only cdylib: adding rlib back would make Cargo drop LTO silently, because it cannot link-time-optimize a unit that also emits an rlib.

python scripts/pgo.py rebuilds the extension with profile-guided optimization, training on a short leaderboard run. It is opt-in and not part of uv sync or the release workflow: an interleaved A/B on an Apple M4 Pro put the kernels within a few percent either way for a 3% smaller binary and roughly triple the build time, which is not a trade worth making by default. Tight numeric loops with predictable branches give PGO little to work with, but that result is specific to this workload and micro-architecture, so the script is there to re-measure with.

Releasing

  1. Bump the version: uv version --bump patch (or minor / major).
  2. Commit, then tag and push: git tag v$(uv version --short) && git push --tags.
  3. The Release GitHub Actions workflow builds and publishes to PyPI via Trusted Publishing.

License

MIT

Release files for skrecsys 0.3.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 skrecsys 0.3.0
File Size Uploaded
skrecsys-0.3.0.tar.gz 210.4 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for skrecsys 0.3.0
File
skrecsys-0.3.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
skrecsys-0.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
skrecsys-0.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details
skrecsys-0.3.0-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details

Total release size:3.5 MB

Release files / skrecsys-0.3.0.tar.gz

Download URL skrecsys-0.3.0.tar.gz
Size 210.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d098e9e75c43a33fd1a0b49d91f9c0fbbfe6ff8c178df71ad152f2ef5416d1e1
BLAKE2b-256 checksum
How to use checksums
0a5057700a540c9f94b7f867d17395ae17c4617d00bca077626fab68c39ddb40
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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}

Release files / skrecsys-0.3.0-cp311-abi3-win_amd64.whl

Download URL skrecsys-0.3.0-cp311-abi3-win_amd64.whl
Size 858.4 kB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
c9c673e2824a840701d55ce2b71803f66a4cc9547d672dc4e7347e86c375f9e0
BLAKE2b-256 checksum
How to use checksums
9629eaea92293a93e34cd7511d127d5f4f809816d9fe157115a01f3e1d43306b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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}

Release files / skrecsys-0.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL skrecsys-0.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.1 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
32414a5a1a8cbd7b4a6c1a8979d040f49b9fd27e0b8f63b06179d77d4fc9da53
BLAKE2b-256 checksum
How to use checksums
fda5cb0453cdd7f7922e5a7d645c209ec247e1aaadc4c2b3b394e2f14ba7b5f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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}

Release files / skrecsys-0.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL skrecsys-0.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 704.0 kB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
2582dde2fcaa0ea8c2de5f98c5949a829df17c018072c8dca0a727688f91bd8e
BLAKE2b-256 checksum
How to use checksums
85044f78eb1d21c30dee481f9f6111a98489457b3363275ca279ea39f95af415
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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}

Release files / skrecsys-0.3.0-cp311-abi3-macosx_11_0_arm64.whl

Download URL skrecsys-0.3.0-cp311-abi3-macosx_11_0_arm64.whl
Size 617.6 kB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a736e7c3d1d780d0c503c81d7bb0313630f995bb8fbe0581ff99a1f7fec978dd
BLAKE2b-256 checksum
How to use checksums
7badd016ba699241786273790213658043f4b6b411a9cb6335fe44c4b079c245
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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}

Release history Release notifications | RSS feed

This release

0.3.0 This release

5 release files

0.2.0

5 release files

0.1.0

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