Open-source project
NicolasHug/Surprise avatar
NicolasHug/Surprise

Surprise: a scikit for explicit-rating recommender experiments

A Python scikit for building and analyzing recommender systems

6,815 stars1,050 forksPythonBSD-3-Clause

At a glance

What is it?
Surprise wraps dataset loading, prediction algorithms and cross-validation into a small scikit-style API. It is a research and teaching tool for explicit ratings, not a production serving layer, and its own README draws that boundary.
Who is it for?
Adopt Surprise if your data is explicit ratings and your job is to compare algorithms, tune parameters or teach how matrix factorization works. Do not adopt it if you need implicit feedback, content features or an online serving path; the README states it supports none of those.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 108 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap Surprise fills: explicit ratings, not clicks

Most recommender tutorials assume you already have a tidy ratings matrix and a way to score it. Surprise supplies both. It is a Python scikit, in the SciPy sense of a small toolbox with a consistent interface, aimed at explicit rating data: a user, an item, a numeric score. The README is explicit that it does not support implicit ratings or content-based information. That single sentence defines the project's audience. If your signal is a purchase, a play, a dwell time or a click, this library is not built for it. If your signal is a star rating, a thumbs score or a survey response, the shape of the problem matches what the library models. The README frames the design around three goals: giving users control over experiments, removing the pain of dataset handling, and shipping ready-to-use prediction algorithms plus evaluation tools. That is a research and analysis remit. Nothing in the material describes a serving layer, an API endpoint or an online update path, so treat it as the place where you decide which algorithm to trust, not the place where you answer a user request.

What the algorithm set actually covers

The README lists baseline algorithms, neighborhood methods, and matrix factorization variants: SVD, PMF, SVD++ and NMF. Similarity measures include cosine, MSD and Pearson. That is a conventional explicit-feedback menu, and the interesting detail is the spread in cost rather than the spread in accuracy. The project's own benchmark table, run on 5-fold cross-validation over Movielens 100k on an Intel i5 11th Gen at 2.60GHz, shows SVD at RMSE 0.934 and MAE 0.737 in about six seconds, NMF at RMSE 0.963 and MAE 0.758 also in about six seconds, Slope One at RMSE 0.946 and MAE 0.743 in about nine seconds, and SVD++ at RMSE 0.919 and MAE 0.721 but taking roughly one minute twenty-two seconds with cache_ratings=True and one minute thirty-nine with it False. The accuracy gain from SVD to SVD++ is small next to the time multiplier. Anyone choosing between them on this table alone is trading a large amount of compute for a few thousandths of RMSE. That is the kind of trade-off the library exists to make visible, and it is a reason to run the benchmark example yourself rather than trust a default.

The mechanism: a Dataset object, a splitter, a grid

The data flow is scikit-learn's, adapted to ratings. You load a Dataset, either built-in (the README names Movielens and Jester) or custom from a file, then hand it to an algorithm object, then hand both to an evaluation routine. Splitting is not something you write by hand: cross-validation iterators are provided, and the README says they are inspired by scikit-learn's tools. Parameter search follows the same pattern through GridSearchCV over a set of parameters. The README's opening example is the whole loop in nine lines: import SVD, Dataset and cross_validate, load ml-100k, instantiate SVD, then call cross_validate with measures ['RMSE', 'MAE'] and cv=5. The printed output shows per-fold RMSE, MAE, fit time and test time with a mean and standard deviation column. That last part matters more than it looks. A single train-test split on rating data is noisy, and reporting a standard deviation across folds is what stops you from declaring victory on a 0.002 difference. The custom algorithm path is documented separately, so the intended extension point is a new predictor class rather than a fork of the evaluation code.

Getting it running, and the version constraint

The repository carries a Python 3.8+ badge, and the README's example is the fastest way to confirm an install works. The snippet is: from surprise import SVD, from surprise import Dataset, from surprise.model_selection import cross_validate, then data = Dataset.load_builtin('ml-100k'), algo = SVD(), and cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=5, verbose=True). The built-in loader downloads the dataset if it is not already present, which means the first run needs network access and a writable cache location. That is a real constraint in an offline or air-gapped environment, and the README does not describe an offline fallback beyond using your own custom dataset. The custom dataset path is documented under getting started, and it is the route you take for anything that is not Movielens or Jester. Note also that the README's output block is a sample, not a guarantee: the numbers in it come from one machine, and the benchmark section states its own hardware. Reproducing either on different hardware will give different times.

Where Surprise is the wrong tool

The clearest failure mode is stated by the project itself: no implicit ratings, no content-based information. A system that recommends from clickstreams, session logs or item metadata is outside the model, and no amount of parameter tuning fixes that. There is a second, less obvious limit. The evaluation machinery is built around predicting a held-out rating, so the metrics it reports are RMSE and MAE. Those measure how close a predicted score is to a withheld score. They do not measure whether the top of a ranked list is good, which is what a user actually sees. If your question is ranking quality rather than rating accuracy, the default measurement loop answers a different question than the one you asked. The benchmark table also implies a scaling boundary: SVD++ on Movielens 100k already costs over a minute per cross-validation run on the stated laptop, and the README's benchmark only goes up to the 1M dataset. Nothing in the material describes distributed training or incremental updates, so treat dataset size as something to test early rather than assume.

The alternative to weigh: scikit-learn's own estimators

The obvious comparison is scikit-learn, which Surprise explicitly borrows its cross-validation and GridSearchCV idioms from. The difference is in the default model. scikit-learn gives you general estimators and a matrix of user-item ratings is just a sparse matrix to them, so you supply the factorization or the nearest-neighbor logic yourself. Surprise supplies the recommender-specific pieces: rating-aware dataset loading, similarity measures such as cosine, MSD and Pearson, and implementations of SVD, PMF, SVD++, NMF and Slope One that already know what a user-item rating means. If you are already deep in a scikit-learn pipeline and only need one estimator, importing a second library for the model layer adds a dependency for convenience you may not need. If you are starting from a ratings file and want the standard algorithms plus fold-based evaluation without writing them, Surprise removes that work. The two are not exclusive, since the interfaces are close enough to sit side by side.

Licence, maintenance and what to check before you commit

Surprise is BSD-3-Clause, a permissive licence that allows use in closed products provided the copyright notice and disclaimer are retained. That is a summary of the identifier, not legal advice; read the licence text and your own obligations. The repository is not archived and its last push is dated 2026-05-30, so the project is active by that measure, though no recent releases were retrieved for this review, which means you should check the package index and the changelog for the version you intend to pin rather than assume a cadence. The upgrade cost is mostly the usual Python one: the 3.8+ floor, and the fact that a pinned version is what protects you from an interface change in the model_selection module. The README also points to a JOSS paper via DOI, which is a reasonable place to look for the algorithms' formal description if you need to cite or verify a method. Verify three things before adopting: that your Python is 3.8 or newer, that the built-in loaders can reach their download sources from your environment, and that RMSE and MAE are the metrics your decision actually depends on. If the third fails, the library's evaluation tools are the wrong instrument even if its algorithms are the right ones.

Editorial conclusion

Adopt Surprise if your data is explicit ratings and your job is to compare algorithms, tune parameters or teach how matrix factorization works. Do not adopt it if you need implicit feedback, content features or an online serving path; the README states it supports none of those. Before committing, check the Python version against the 3.8+ badge, confirm the built-in Movielens and Jester loaders still download in your environment, and run the README's cross_validate snippet on your own ratings file to see whether the RMSE and MAE it reports are meaningful for your scale.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. NicolasHug/Surprise on GitHub
  4. Project website
  5. README
Community notes

Community notes