Lightweight Streamlit library for interactive recommender system demos
Project description
streamlit-recommenders
Turn a trained recommender into an interactive, inspectable web demo in a few lines of Python.
Why streamlit-recommenders?
Reproducibility in recommender systems research usually stops at aggregate offline metrics. Whether a model actually behaves sensibly — for individual users, across parameter settings, against baselines — stays invisible unless someone builds a demo, and building demos is frontend work most researchers don't want to do.
streamlit-recommenders is a thin presentation layer between your recommendation model and an interactive Streamlit app. You bring a trained model (or just a function returning ranked item ids); the library handles the UI, caching, session state, side-by-side model comparison, and light evaluation:
- Inspect per-user behavior — pick any user, see their history and live recommendations, click items to simulate session feedback.
- Probe parameter sensitivity — expose model parameters as sidebar widgets and watch recommendations react.
- Compare models side by side — pass a dict of named models; all receive the same user and session context.
- Attach light evaluation — ranking metrics (HR, Recall, NDCG, MRR, coverage), overlap heatmaps, score distributions, and markdown/LaTeX appendix content below the demo.
- Stay lightweight — training frameworks (RecBole, Cornac, LensKit, ...) remain external; the demo loads only your exported artifacts or callable.
The goal: make interactive model inspection a standard, low-effort artifact to publish alongside a paper.
Installation
pip install streamlit-recommenders
A single install pulls in everything the library and examples need (including scipy for baseline training and kagglehub for the goodbooks-10k download). A dev extra adds tooling for contributors:
pip install "streamlit-recommenders[dev]" # pytest, build, twine
From source:
git clone https://github.com/vaclavstibor/streamlit-recommenders.git
cd streamlit-recommenders
pip install -e ".[dev]"
Quick start
Create app.py:
import streamlit_recommenders as sr
ITEMS = sr.load_items("data/items.csv")
INTERACTIONS = sr.load_interactions("data/interactions.csv")
POPULARITY = INTERACTIONS["item_id"].value_counts().index.tolist()
def get_recommendations(user_id, k, alpha=0.5, session_items=None, **params):
"""Return an ordered list of item ids (here: popularity, minus session items)."""
exclude = set(session_items or [])
return [item_id for item_id in POPULARITY if item_id not in exclude][:k]
sr.run(
get_recommendations=get_recommendations,
items=ITEMS,
interactions=INTERACTIONS,
layout="rows",
params={"alpha": sr.slider("alpha", 0.0, 1.0, 0.5)},
)
Then run:
streamlit run app.py
Replace the function body with your model and you have an inspectable demo. For compare mode, pass a dict of models — params can be global or scoped to a model label:
sr.run(
get_recommendations={"Ours": ours, "EASE": ease},
items=ITEMS,
interactions=INTERACTIONS,
params={
"num_recs": sr.selectbox("Number of recommendations", [5, 10, 20], index=1),
"Ours": {"alpha": sr.slider("alpha", 0.0, 1.0, 0.5)},
},
)
Three ways to plug in a recommender
The library owns the interactive layer; you provide recommendations in whichever of these fits your workflow:
- Bring your trained model / weights. Export your model to plain arrays (or keep the object) and expose
get_recommendations(). The lead demo usessr.ArtifactRecommender, which loads exported.npzweights. Subclasssr.BaseRecommender(implementscores()) for custom trained models. - Start from a reference baseline. ItemKNN, EASE, and Sequential CF are defined in
examples/reference_recommenders.pyby subclassingsr.BaseRecommender— copy one and fit it in memory withModel.from_interactions(train, items). These are reference implementations to adapt, not library imports. - Thin function adapter. Skip classes entirely and write a plain
def get_recommendations(user_id, k, session_items=None, **params): ...around any external scorer or API.
All three return ordered item ids and drop straight into sr.run(...).
What you write vs. what the library does
| You | Library |
|---|---|
get_recommendations(user_id, k, **params) or RecommenderProtocol |
Adapter, cache, sidebar params, session feedback |
items, interactions, optional users / test |
Dataset, validation, user/session UI |
Optional intro() / body() callbacks |
Markdown/math above recs; metrics, plots, tables below recs |
| Dict of models for compare | Stacked rows + shared Get Recommendations |
Data standard
The core data contract is pandas-first and intentionally small:
| Table | Required | Recommended / optional |
|---|---|---|
items |
item_id |
title, image_url, description, genres, year, tmdb_id, imdb_id, poster_path |
users |
user_id |
segment/profile columns |
interactions, train, test |
user_id, item_id |
rating, timestamp |
Missing posters are fine: item cards fall back to a bundled placeholder image. Keep protected data out of git under data/<dataset-name>/; see data/README.md.
Dataset preparation
Two dataset families are supported out of the box, each one command away. A completion manifest prevents accidental re-downloads, and every prepared folder ends up in the same standard layout (items.csv, interactions.csv, ...) under data/<dataset-name>/.
Keys and paths — .env or inline
Keys (TMDB_API_KEY / TMDB_BEARER_TOKEN, KAGGLE_USERNAME / KAGGLE_KEY) and the SR_DATA_DIR path can be provided two equivalent ways:
.envfile (recommended — keeps the showcase commands clean). Copy.env.exampleto.envin the repo root and fill it in. The preparation CLI loads.envautomatically, so key-based commands need no prefix:python -m streamlit_recommenders.data.prepare --dataset ml-latest-small --with-posters
- Inline environment variables. Prefix the command, as shown throughout the examples below:
TMDB_API_KEY=your_key python -m streamlit_recommenders.data.prepare --dataset ml-latest-small --with-posters
Real environment variables take precedence over .env. Note: only the preparation CLI auto-loads .env. The example Streamlit apps read SR_DATA_DIR from the shell environment, so either export it once (set -a && source .env && set +a) or pass it inline (SR_DATA_DIR=data/ml-latest-small streamlit run ...).
Movies — MovieLens
Supported variants: ml-latest-small, ml-latest, ml-25m, ml-32m (downloaded directly from GroupLens, no account needed):
python -m streamlit_recommenders.data.prepare --dataset ml-latest-small
MovieLens ships no artwork. To enrich items with TMDB posters and plot descriptions, get a free API key at themoviedb.org and add --with-posters:
TMDB_API_KEY=your_key python -m streamlit_recommenders.data.prepare --dataset ml-latest-small --with-posters
TMDB_BEARER_TOKEN is accepted as an alternative; --poster-limit 1000 caps the download for a quick first run. Re-running with --with-posters on an already-prepared dataset only performs the enrichment.
Books — goodbooks-10k
Book covers ship as URLs inside the dataset itself, so no image enrichment is needed. The
download comes from Kaggle via kagglehub, which ships
with the library:
python -m streamlit_recommenders.data.prepare --dataset goodbooks # or goodbooks-10k
SR_DATA_DIR=data/goodbooks-10k streamlit run examples/compare_models_rows.py
The dataset is public, so kagglehub usually needs no credentials. If your environment does
require them (the same way MovieLens posters need TMDB_API_KEY), authenticate Kaggle with a
~/.kaggle/kaggle.json token or the KAGGLE_USERNAME / KAGGLE_KEY environment variables.
Alternatively, skip Kaggle entirely by placing books.csv / ratings.csv into
data/goodbooks-10k/ yourself.
Bring your own domain
Any domain works as long as you produce the standard tables (see Data standard): put items.csv and interactions.csv in a folder and point the examples at it with SR_DATA_DIR=data/your-dataset.
Layouts
| Layout | Display |
|---|---|
rows |
One horizontal row of clickable poster cards with side scroll |
grid |
Catalog-style clickable poster grid with configurable rows and columns |
cards |
Swipe deck: one card at a time with Like / Dislike / Skip; refreshes after swipes_per_refresh swipes |
rows and grid use clickable poster cards (hover title, description tooltip). Compare mode (get_recommendations={...}) always uses rows; the cards swipe deck is single-model.
Session UX
- Click item cards to add them to Selected this session; click selected cards again to unselect
- In the swipe deck, Like / Dislike / Skip each card; dislikes appear in a Disliked this session strip
- Click Get Recommendations to refresh all compared models with current selections
- Read state in
body():sr.selected_items(),sr.disliked_items(),sr.displayed_items(label),sr.current_user(),sr.param_value("alpha")
How recommendations are built
The evidence a model scores against is the user profile — the union of the selected user's history and their current-session interactions:
- History. For a dataset user, their past interactions (
interactionstable) form the starting profile. The Try yourself session user starts empty, so it is driven purely by what you click. - Session interactions. Clicking a card (or swiping Like) adds an item to
session_items; swiping Dislike records it withsentiment: "dislike"; Skip only marks the card as seen. These accumulate in the session state as you go. - Refresh. On Get Recommendations, the library passes the merged evidence to your model
as
session_items(plusselectionsmetadata). It also unions history with session likes to avoid re-recommending already-seen items (effective_seen). So each refresh folds everything you have done so far into the profile. - Scoring & weighting. How the profile becomes a ranking is your model's business. The
bundled
ArtifactRecommenderbuilds a binary profile vector overhistory + session_itemsand multiplies it by the exported weight matrix; the optional History window control caps how many recent items count, and Sequential CF scores from the last item only.
Feedback is a first-class part of the contract: session_items carries likes, selections
carries dislike sentiment, and skips are excluded. The reference recommenders only exclude
disliked/skipped items, but a custom model can read the dislike sentiment as a negative signal —
see examples/swipe_deck_cards.py. Full shapes are in
docs/CONTRACTS.md.
Built-ins
| Area | API |
|---|---|
| Data | Dataset, ColumnMap, load_dataset(), load_dataset_from_paths(), validate_dataset() |
| Recommenders | BaseRecommender, ArtifactRecommender, load_artifacts() — ItemKNN / EASE / Sequential CF are reference implementations in examples/reference_recommenders.py, not library imports |
| Metrics | evaluate(), hit_rate_at_k(), recall_at_k(), ndcg_at_k(), mrr_at_k(), coverage() |
| Viz | dataset_info(), recommendation_overlap_matrix(), plot_overlap_heatmap(), plot_metric_comparison(), plot_ranked_items(), plot_score_distribution() |
Baseline story
Use three baseline families by default:
- ItemKNN for classic item-item collaborative filtering.
- EASE for a strong shallow linear implicit-feedback baseline.
- Sequential CF for timestamped next-item behavior.
Lightweight reference implementations live in examples/reference_recommenders.py (copy-and-adapt, not shipped in the package), and examples/train_baseline_artifacts.py exports them as .npz artifacts. For full training, use any training code externally and export pure artifacts that match the same get_recommendations() contract.
To train the three baseline artifacts and inspect them:
# Optionally enrich with TMDB posters/descriptions first (see Dataset preparation):
TMDB_API_KEY=your_key python -m streamlit_recommenders.data.prepare --dataset ml-latest-small --with-posters
python examples/train_baseline_artifacts.py --data data/ml-latest-small
SR_DATA_DIR=data/ml-latest-small streamlit run examples/compare_models_rows.py
The training script reads standard items.csv/interactions.csv, or raw MovieLens-style movies.csv/ratings.csv, creates train/test splits if needed, and writes pure .npz artifacts for ItemKNN, EASE, and Sequential CF. The Streamlit demo loads only those arrays, not the training code.
Examples
Examples live in the GitHub repository (not in the wheel):
| File | Pattern |
|---|---|
reference_recommenders.py |
Way 2: define models by subclassing BaseRecommender (ItemKNN / EASE / Sequential CF), fit in memory, and compare |
compare_models_rows.py |
Lead demo (way 1): compare ItemKNN, EASE, and Sequential CF artifacts in rows layout |
swipe_deck_cards.py |
Single-model swipe deck (cards layout) with like/dislike/skip; wraps EASE in a feedback-aware model that uses dislikes as a negative signal |
compare_models_grid.py |
Grid layout with a sidebar model selector (one artifact at a time) |
train_baseline_artifacts.py |
Train/export SciPy/NumPy baseline artifacts under data/<dataset-name>/artifacts |
Citation
If you use streamlit-recommenders in your research, please cite:
@software{stibor2026streamlitrecommenders,
author = {Stibor, V{\'a}clav and Van{\v c}ura, Vojt{\v e}ch and Pe{\v s}ka, Ladislav},
title = {StreamlitRecommenders: Towards Recommendation Inspectability as a New Reproducibility Standard},
year = {2026},
url = {https://github.com/vaclavstibor/streamlit-recommenders},
version = {0.1.0}
}
License
MIT License — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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 streamlit_recommenders-0.1.0.tar.gz.
File metadata
- Download URL: streamlit_recommenders-0.1.0.tar.gz
- Upload date:
- Size: 88.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42c1fbd83d8ee3f0019aa90c47be73c5bbea28b1036d56f9df2254b635036ba3
|
|
| MD5 |
db05ad7044bc1e78439b7776a0b3830f
|
|
| BLAKE2b-256 |
d62a1e627921e9bf3228ccdf2761f59a1b15499530b102d5df2278cf38cf96da
|
Provenance
The following attestation bundles were made for streamlit_recommenders-0.1.0.tar.gz:
Publisher:
publish.yml on vaclavstibor/streamlit-recommenders
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
streamlit_recommenders-0.1.0.tar.gz -
Subject digest:
42c1fbd83d8ee3f0019aa90c47be73c5bbea28b1036d56f9df2254b635036ba3 - Sigstore transparency entry: 2182357596
- Sigstore integration time:
-
Permalink:
vaclavstibor/streamlit-recommenders@387dbef871ccc37c9e4e04e2a480bb721f69bbd0 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vaclavstibor
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@387dbef871ccc37c9e4e04e2a480bb721f69bbd0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file streamlit_recommenders-0.1.0-py3-none-any.whl.
File metadata
- Download URL: streamlit_recommenders-0.1.0-py3-none-any.whl
- Upload date:
- Size: 90.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb8b65c7bf81eb5111dd027a4da21671e662339e0a36c0e621a02d8f406dda17
|
|
| MD5 |
fa093926ffab6f54427a0d927177cf2b
|
|
| BLAKE2b-256 |
3d4e18db4e7986e0a22c50f0a1c1276a118d4704431eb43b38d24d58fabb24f7
|
Provenance
The following attestation bundles were made for streamlit_recommenders-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on vaclavstibor/streamlit-recommenders
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
streamlit_recommenders-0.1.0-py3-none-any.whl -
Subject digest:
fb8b65c7bf81eb5111dd027a4da21671e662339e0a36c0e621a02d8f406dda17 - Sigstore transparency entry: 2182357870
- Sigstore integration time:
-
Permalink:
vaclavstibor/streamlit-recommenders@387dbef871ccc37c9e4e04e2a480bb721f69bbd0 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vaclavstibor
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@387dbef871ccc37c9e4e04e2a480bb721f69bbd0 -
Trigger Event:
release
-
Statement type: