# scikit-surprise: explicit-rating recommender systems in Python

> Surprise is a Python scikit for building and evaluating recommender systems over explicit rating data. It gives you the algorithms, the cross-validation machinery and a CLI, but it deliberately ignores implicit feedback and content features.

**NicolasHug/Surprise** — A Python scikit for building and analyzing recommender systems

- Repository: https://github.com/NicolasHug/Surprise
- Website: http://surpriselib.com
- Stars: 6,818 · Forks: 1,049
- Language: Python
- License: BSD-3-Clause
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/nicolashug-surprise

## The gap scikit-surprise fills: explicit ratings, controlled experiments

Most recommender tutorials hand you a matrix and a loss function and leave the evaluation to you. scikit-surprise takes the opposite position. Its stated design goal is to give users control over their experiments, and the library is organized around that: built-in loaders for Movielens and Jester, a Dataset abstraction that also accepts custom files and pandas DataFrames, and cross-validation iterators modeled on scikit-learn's.

The audience is narrow and specific. If you have a table of (user, item, rating) triples and you want to know whether SVD beats k-NN on your data, this is the shortest path. The README says the library deals with explicit rating data, and it repeats the boundary later: surprise does not support implicit ratings or content-based information. That sentence is the whole scoping decision. A click log is not a rating. A product description is not a rating. If either is your only signal, the library's core abstractions do not apply to you.

## How the algorithm zoo and the evaluation loop fit together

The repository separates three concerns. First, data: Dataset.load_builtin fetches a known dataset, while custom data comes in through readers or a DataFrame. Second, prediction: the prediction_algorithms package holds baseline algorithms, neighborhood methods, and matrix factorization variants including SVD, PMF, SVD++ and NMF, plus similarity measures such as cosine, MSD and Pearson. Third, evaluation: model_selection provides cross-validation and GridSearchCV-style parameter search.

The mechanism behind the factorization algorithms is the standard latent-factor decomposition, and the documentation points out every detail of each algorithm rather than hiding it behind a fit call. The practical consequence is that the same data object can be fed to any algorithm, so swapping SVD for k-NN is a one-line change and the comparison stays honest because the folds are identical.

That last property is worth more than it sounds. The repository's own benchmark table uses the same folds for every algorithm, which is the only way the RMSE column means anything. If you build your own comparison harness, copy that discipline.

## Installing scikit-surprise and running a first cross-validation

The package name on PyPI is scikit-surprise, which is not the same as the import name. The project's build system requires Cython and numpy at build time, so installation compiles C extensions; expect a compiler toolchain on the machine doing the install.

```bash
pip install scikit-surprise
```

After installation, the importable package is surprise. The README's own example loads Movielens 100k, which downloads on first use, runs five-fold cross-validation on SVD, and prints RMSE and MAE per fold.

```python
from surprise import SVD
from surprise import Dataset
from surprise.model_selection import cross_validate

data = Dataset.load_builtin('ml-100k')
algo = SVD()
cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=5, verbose=True)
```

The output is a table with one row per metric, five fold columns, a mean and a standard deviation, plus fit and test times. The README's sample output shows RMSE means around 0.9355 and MAE around 0.7375 for SVD on ml-100k. Treat those as the README's published numbers, not as a target you must hit; your data will differ.

For your own data, the examples directory contains load_from_dataframe.py, load_custom_dataset.py and train_test_split.py, which are the files to read before writing a loader from scratch.

## The command line entry point and what it is for

pyproject.toml declares a console script named surprise, mapping to surprise.__main__:main. That means the library is not Python-only in practice: you can drive a training or evaluation run from a shell without writing a script.

The README and the pyproject.toml excerpt do not document the CLI's subcommands or flags, so the honest statement is that the entry point exists and its arguments are undocumented in what the project publishes. If you plan to depend on the CLI in a pipeline, check `surprise --help` against the version you install before designing around it. The Python API is the better-documented surface, and the examples directory is where the working patterns live.

## Where scikit-surprise is the wrong tool

Three limitations matter more than the rest.

Explicit ratings only. The README states that implicit ratings and content-based information are not supported. If your production signal is a purchase, a view or a dwell time, you are looking at implicit feedback, and you would be encoding it as a fake rating, which changes what the model learns. That is not a small adaptation.

Offline metrics are rating-prediction metrics. RMSE and MAE measure how well you predict a held-out rating. They do not measure whether the top of a ranked list is good. The examples directory does include precision_recall_at_k.py and top_n_recommendations.py, so ranking evaluation exists, but the default cross_validate output is a regression error, and it is easy to optimize it while making the ranking worse.

Random splits leak. The examples include split_data_for_unbiased_estimation.py, and its existence is a warning: a plain random train/test split on rating data lets the same user appear on both sides, which flatters the score relative to a time-based split. The library gives you the tool to do this correctly; it does not force you to use it.

## How it differs from implicit-feedback libraries

The natural comparison is with libraries built for implicit feedback, such as implicit. The difference is not a matter of degree; it is a different problem formulation. An implicit-feedback library treats the absence of an interaction as weak negative evidence and optimizes a ranking or confidence-weighted objective over the full user-item matrix. scikit-surprise treats a missing rating as unknown and optimizes prediction error on the observed entries.

Practically, that means with implicit you can train on interaction counts with no rating column at all, and you evaluate with ranking metrics by construction. With scikit-surprise you need a rating column, and you get regression metrics by default. If you have both a rating column and implicit events, the interesting question is whether the explicit signal is dense enough to be worth the restriction. If it is sparse, the implicit formulation usually wins on data volume alone.

## Maintenance, licence and the upgrade path

The repository is not archived and the last push was on 2026-05-30. The project is BSD-3-Clause licensed, with the licence text in LICENSE.md and declared in pyproject.toml as an OSI-approved BSD licence. For most users that means permissive use, modification and redistribution with the copyright notice retained; the usual caveat applies that this is a description of the licence, not legal advice, and that bundled datasets such as Movielens and Jester carry their own terms from their original providers.

On upgrades, pyproject.toml sets requires-python to >=3.10 and lists classifiers through Python 3.14, with runtime dependencies joblib>=1.4.0, numpy>=1.24.1 and scipy>=1.10.0. The build backend pins Cython>=3.1.0 and numpy>=2 at build time, with a comment noting the resulting wheel should still work with numpy below 2. That build-time numpy pin is the most likely source of installation friction: a mismatched toolchain, not a mismatched runtime.

The release process in setup.py is manual and detailed, covering changelog updates, a tag push that triggers wheel builds, a twine upload, and a separate conda-forge feedstock update. No release list is published in the repository root, so check PyPI or CHANGELOG.md for the version you are pinning.

## Conclusion

Adopt scikit-surprise if your data is a user-item-rating table and your goal is to compare factorization or neighborhood algorithms under honest cross-validation. Do not adopt it if your signal is clicks, views or purchases with no rating column, or if you need item text, images or user demographics in the model, because the README states plainly that implicit ratings and content-based information are unsupported. Before committing, verify three things: that pip install scikit-surprise builds the Cython extensions on your target Python (3.10 through 3.14 per pyproject.toml), that Dataset.load_from_df accepts your column layout, and that your evaluation split matches your deployment question rather than the default random split.

## FAQ

### How do I install the surprise library in Python?

Install it from PyPI with pip install scikit-surprise. The import name is surprise, not scikit-surprise, and the build requires Cython and numpy because the package compiles C extensions.

### How do I use surprise for a first recommender model?

The README's example loads a built-in dataset with Dataset.load_builtin, instantiates an algorithm such as SVD, and calls cross_validate with measures=['RMSE', 'MAE'] and cv=5. The output is a per-fold table with mean and standard deviation.

### Does scikit-surprise support implicit ratings?

No. The README states that surprise does not support implicit ratings or content-based information, so the library expects explicit rating values.

### Which Python versions does scikit-surprise support?

pyproject.toml sets requires-python to >=3.10 and lists classifiers for Python 3.10 through 3.14.

### What is the licence for scikit-surprise?

The project is BSD-3-Clause, declared in pyproject.toml and shipped as LICENSE.md in the repository.

## Sources

- [Issues](https://github.com/NicolasHug/Surprise/issues)
- [License: BSD-3-Clause](https://github.com/NicolasHug/Surprise/blob/master/LICENSE)
- [NicolasHug/Surprise on GitHub](https://github.com/NicolasHug/Surprise)
- [Project website](http://surpriselib.com)
- [README](https://github.com/NicolasHug/Surprise/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nicolashug-surprise
