Skip to content

pdokoupil/grouprec

Repository files navigation

grouprec

Group recommender systems for Python — results aggregation and profile aggregation as first-class citizens behind one API.

Python versions License: MIT GitHub stars CI codecov docs code style: ruff

Keywords: group recommendation · group recommender systems · preference aggregation · collaborative filtering · fairness · Python.

Why this exists

One library for both kinds of group recommender — deep-learning SOTA models (AGREE, GroupIM, ConsRec, …) and classic, fairness-oriented aggregators (GFAR, EP-FuzzDA, RLProp/LTP, …) — behind one API, so you compare them head-to-head on the same data, splits, and metrics.

Group recommendation has split into two communities that rarely compare against each other (Peska et al., Bridging the Rift, UMAP 2025); general-purpose toolkits (LensKit, RecBole, Cornac, …) are single-user. grouprec bridges that — swap a results-aggregator for a deep model, score the same recommender under coupled vs decoupled, and put both in one leaderboard. See the design & the rift.

Install

pip install -e ".[full]"

Quickstart

import grouprec as gr
from grouprec import GroupRecommender, evaluate
from grouprec.backends import EASE          # or implicit_als(), Popularity(), lenskit(...)

data   = gr.make_blobs_dataset(seed=0)       # or gr.datasets.load("ml-1m") / your own Dataset
groups = gr.groups.synthetic(data, kind="similar", size=4, n=1000, metric="pearson")
folds  = gr.split.crossval(data, k=5, seed=0)

rec = GroupRecommender(EASE(), gr.aggregators.get("GFAR"), normalize="minmax")

report = evaluate(rec, data, groups, folds,
                  protocol=["coupled", "decoupled"],            # score both at once
                  metrics=["ndcg@10", "recall@10"],
                  group_aggregations=["mean", "min", "minmax"])  # the fairness lens
print(report.pivot())

Aggregators are plain numpy and usable standalone on a (n_members, n_items) matrix:

import numpy as np
from grouprec.aggregators import get
get("LTP").aggregate(np.array([[5., 3., 1.], [2., 3., 4.]]), k=2)

Bring your own recommender (wide algorithm coverage via adapters)

We don't reimplement single-user recommenders — we wrap the established frameworks so you inherit their entire model zoos, plus a few dependency-free built-ins. Any object with fit(dataset) + score(users, items=None) is a backend.

Backend How Algorithms
Built-in (no extra deps) Popularity, EASE, ItemKNN, Random popularity, EASE^R, item-kNN
implicit [implicit] gr.backends.implicit_als(factors=64), implicit_bpr(...) ALS, BPR
LensKit [lenskit] gr.backends.lenskit(ImplicitMFScorer(...)) ImplicitMF, BiasedMF, ItemKNN, UserKNN, EASE, SLIM, …
RecBole [recbole] gr.backends.recbole(model, dataset) (experimental) the full RecBole zoo

Deep group models ([torch])

Lazily imported, so import grouprec never pulls torch:

from grouprec.models import AGREE, GroupIM, ConsRec, make_synthetic_group_data
gd = make_synthetic_group_data(seed=0)
model = ConsRec(gd.groups, gd.group_interactions).fit(gd.dataset)
model.recommend(gd.groups[0], k=10)          # paradigm="profile" -> coupled group-level

Model zoo: NCFGroup, AGREE, GroupIM (InfoMax SSL), ConsRec (overlap/hypergraph/LightGCN consensus), HyperGroup (hierarchical hyperedge embedding), HHGR (double-scale self-supervised hypergraph), AlignGroup (InfoNCE member/group alignment) — each reviewed against its original repo. They plug into benchmark(..., level="sampled") and share the same coupled protocol as results-aggregators, so both families compare side by side.

Datasets (license-aware)

data = gr.datasets.load("ml-1m")             # auto-fetched, cached, parsed
data = gr.datasets.k_core(data, k=5)         # k-core / binarize / min-count preprocessing
print(gr.datasets.info("kgrec").license)     # every entry carries license + citation
gd   = gr.datasets.load_consrec("path/CAMRa2011")          # explicit-group benchmark
gd   = gr.datasets.load_yin(gr.datasets.fetch_yin(accept_license=True), "yelp")
Policy Datasets Behavior
auto MovieLens 100K/1M/25M/32M/latest/latest-small fetched for your own use (GroupLens permits research download; redistribution terms differ by release — 100K/1M forbid it, 25M/latest allow it under same terms)
auto_nc KGRec, Last.fm, Yelp-LA, Douban-SH non-commercial — fetched only after accept_license=True, citations surfaced
manual CAMRa2011, Mafengwo, Weeplaces redistribution unclear — load prints where to download

Licensing: The library code is released under MIT. Datasets are not relicensed by the library and remain subject to their upstream terms. The library does not bundle dataset files; for datasets with access restrictions or non-commercial terms, loading is enabled only when the user accepts the dataset-specific license or follows the upstream download instructions. Plus from_huggingface(...) / from_path(...) for anything else.

Benchmark & the rift

Score the same recommender under coupled vs decoupled and put both families on one board:

res = gr.benchmark(recs, tasks, protocols=["coupled", "decoupled"], metrics=["ndcg@10"])
res.to_csv("leaderboard.csv")                # tidy long-format
res.leaderboard("ndcg", k=10, protocol="coupled")     # ranking flips vs "decoupled"

Explore a group recommendation end-to-end in the interactive inspector — built on the 20-core of MovieLens ml-latest (204,257 users / 23,290 items / 32.2M ratings); every ranking is a real grouprec call (how it's built; regenerate with grouprec-build-inspector, needs [torch] — add --small for a quick, much smaller page off ml-latest-small).

What's inside

  • Aggregators (numpy core): ADD AVG LMS MUL MPL AVGNM BDC FAI, GFAR GreedyLM PAR SPGreedy EPFuzzDA (fairness), RLProp LTP PeriodicFAI EPFuzzDAWeighted SDAA SIAA (sequential).
  • Group formation: gr.groups.synthetic(kind="random|similar|divergent|outlier").
  • Evaluation: coupled / decoupled / sampled protocols; metrics at the per-member (ndcg/recall/hr/ar/… × mean/min/minmax/jain/zero) and per-list (novelty, list_coverage, register_list_metric) levels.
  • Reproducibility: gr.Experiment (seed + env + git SHA/dirty/diff + citations), gr.set_seed, gr.cite("ConsRec").

Extras at a glance

gr.cite("ConsRec")                            # BibTeX for any implemented method
gr.collect_citations(rec, dataset)            # auto-collect cites for what a run used
with gr.Experiment("run1", seed=42, cite=[rec, dataset]) as exp:  # cites auto-resolved
    ...                                       # writes runs/run1-<ts>/ on exit
gr.eval.register_metric("coverage", fn)       # custom per-member metric
gr.eval.register_list_metric("ild", fn)       # custom per-list metric

Contributing — researchers especially welcome

Published a group-rec method? Add it (small PR — CONTRIBUTING.md has copy-paste recipes for an aggregator / deep model / dataset / metric), or just link your repo in a port-request issue and we'll port it. Every method ships with its citation, so your work is credited wherever the library is used. Students & practitioners welcome too (good-first-issue).

Citation

If you use grouprec, please cite it (CITATION.cff) and the relevant method (gr.cite(...)). Docs: https://pdokoupil.github.io/grouprec.

A demo-track paper describing this toolkit is under review at RecSys 2026:

Patrik Dokoupil, Ludovico Boratto, and Ladislav Peska. GroupRec: A Unified Toolkit for Reproducible and Inspectable Group Recommendation Research.

License

MIT. Datasets retain their own licenses (see Datasets above).

About

GroupRec: A Unified Toolkit for Reproducible and Inspectable Group Recommendation Research

Topics

Resources

License

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages