Skip to content

Scoring

Functions for computing quality signals and comparing professors. All take list[Rating] as input (from get_all_ratings) and return typed dataclasses.


Weight presets

WEIGHT_PRESETS is a dict of named weight configurations for compute_score. Pass any entry directly to the weights parameter:

from rmp_api import WEIGHT_PRESETS, compute_score

score = compute_score(ratings, weights=WEIGHT_PRESETS["best_teacher"])
Key What it emphasizes
"overall" Balanced default
"best_teacher" Teaching quality; ignores difficulty
"easiest" Easiness (low difficulty)
"rigorous" Harder courses score higher (negative easiness weight)

Custom weights

Supply any subset of these keys. Missing keys default to 0. Values should sum to approximately 1.0. The resulting composite_score is clamped to [0, 1].

score = compute_score(ratings, weights={
    "recency_rating":   0.4,
    "would_take_again": 0.3,
    "easiness":         0.2,
    "reliability":      0.1,
})

Custom weights only affect composite_score. All other signals (raw_avg_rating, reliability_score, etc.) are always computed regardless of weights.


Sorting and comparison

When using compare_professors, pass a SortBy enum value or its string equivalent to choose the ranking field. Higher values always rank first -- to rank by easiness, use SortBy.EASINESS_SCORE rather than SortBy.AVG_DIFFICULTY.

from rmp_api import SortBy, compare_professors

comparison = compare_professors(professors, sort_by=SortBy.WOULD_TAKE_AGAIN_PCT)

Reference

Scoring functions: compute quality signals and composite scores from rating lists.

compute_score(ratings, weights=None, half_life_days=365.0)

Compute all quality signals for a professor from their Rating list.

Aggregates raw stats, derives recency-weighted and reliability signals, then combines them into a single composite_score in [0, 1].

Parameters:

Name Type Description Default
ratings list[Rating]

Output of :func:~client.get_all_ratings or :func:~client.get_ratings_page.

required
weights dict[str, float] | None

Score component weights. Use a :data:WEIGHT_PRESETS entry or supply a custom dict with keys recency_rating, would_take_again, easiness, reliability. Values should sum to ~1.0; composite is clamped to [0, 1]. Defaults to WEIGHT_PRESETS["overall"].

None
half_life_days float

Recency decay half-life. At 365 days, a one-year-old rating contributes half the weight of a brand-new one.

365.0

Returns:

Type Description
ProfessorScore

class:ProfessorScore with all signals populated. Returns a

ProfessorScore

zero-valued instance if ratings is empty.

compute_score_over_time(ratings, period=TimePeriod.YEAR, weights=None, half_life_days=365.0)

Bucket ratings by time period and compute a :class:ProfessorScore per bucket.

Useful for detecting whether a professor has improved or declined over time. Buckets with zero dated ratings are skipped. Buckets are returned oldest -> newest.

Parameters:

Name Type Description Default
ratings list[Rating]

Output of :func:~client.get_all_ratings or similar.

required
period TimePeriod | str

Bucketing granularity. Use :class:~models.TimePeriod members or the equivalent plain strings (StrEnum — both work):

  • :attr:~models.TimePeriod.YEAR / "year""2023".
  • :attr:~models.TimePeriod.SEMESTER / "semester""2023-Spring" / "2023-Fall".
  • :attr:~models.TimePeriod.QUARTER / "quarter""2023-Q1""2023-Q4".
YEAR
weights dict[str, float] | None

Passed through to each :func:compute_score call.

None
half_life_days float

Recency decay half-life passed to each :func:compute_score call.

365.0

Returns:

Type Description
ScoreTimeline

class:ScoreTimeline with periods sorted oldest -> newest,

ScoreTimeline

a trend slope (positive = improving), and total_span_years.

ScoreTimeline

Returns an empty timeline if no ratings have parseable dates.

Raises:

Type Description
ValueError

If period is not a valid :class:~models.TimePeriod value.

compute_split_score(ratings, weights=None, half_life_days=365.0)

Compute professor scores split by online vs. in-person delivery format.

Runs :func:compute_score three times — once per subset and once for all ratings combined — so each :class:ProfessorScore reflects only the ratings relevant to that format.

Parameters:

Name Type Description Default
ratings list[Rating]

Output of :func:~client.get_all_ratings or :func:~client.get_ratings_page.

required
weights dict[str, float] | None

Passed through to each :func:compute_score call. See its docs for valid keys and defaults.

None
half_life_days float

Recency decay half-life passed to each :func:compute_score call.

365.0

Returns:

Type Description
SplitScore

class:SplitScore with online, in_person, and combined

SplitScore

fields. Subsets with zero ratings return a zero-valued

SplitScore

class:ProfessorScore.

compare_professors(professors, sort_by=SortBy.COMPOSITE_SCORE, weights=None, half_life_days=365.0)

Compute and rank scores for multiple professors side-by-side.

Each professor's full :class:ProfessorScore is computed from their ratings, then professors are ranked best -> worst on a chosen signal.

Parameters:

Name Type Description Default
professors dict[str, list[Rating]] | list[tuple[str, list[Rating]]]

Professors to compare, supplied as either:

  • dict[str, list[Rating]]{"Prof A": ratings_a, ...}
  • list[tuple[str, list[Rating]]][("Prof A", ratings_a), ...] (preserves insertion order when labels duplicate).
required
sort_by SortBy | str

:class:~models.ProfessorScore field to rank by. Use :class:~models.SortBy members or the equivalent plain strings (StrEnum — both work). Higher is always ranked first — to rank by easiness (lower difficulty = better) use :attr:~models.SortBy.EASINESS_SCORE instead of :attr:~models.SortBy.AVG_DIFFICULTY.

COMPOSITE_SCORE
weights dict[str, float] | None

Weight dict passed to each :func:compute_score call. Use a :data:WEIGHT_PRESETS entry or a custom dict. Only affects composite_score; all other signals are weight-independent.

None
half_life_days float

Recency decay half-life in days passed to each :func:compute_score call. Only affects recency-sensitive signals.

365.0

Returns:

Type Description
ProfessorComparison

class:ProfessorComparison with:

ProfessorComparison
  • ranking — list of (label, ProfessorScore) sorted best -> worst.
ProfessorComparison
  • scores{label: ProfessorScore} for direct lookup.
ProfessorComparison
  • sort_by — the resolved :class:~models.SortBy value used for ranking.
ProfessorComparison
  • best / worst — labels of the top and bottom professors.
ProfessorComparison
  • deltas{label: float} difference from the best value on the sort_by field (best = 0.0, others <= 0.0).
ProfessorComparison

Returns a comparison with empty ranking if professors is empty.

Raises:

Type Description
ValueError

If sort_by is not a valid :class:~models.SortBy value.


Individual signal computers. Each takes a list of Rating objects and returns a normalized metric.

compute_difficulty_histogram(ratings)

Count ratings in each integer difficulty bucket from 1 to 5.

Parameters:

Name Type Description Default
ratings list[Rating]

List of :class:~models.Rating objects.

required

Returns:

Type Description
dict[int, int]

Dict mapping each bucket {1, 2, 3, 4, 5} to its count.

compute_easiness_score(ratings)

Inverse of average difficulty, normalised to [0, 1].

difficulty = 1 (easiest) -> 1.0; difficulty = 5 -> 0.0. Ratings with None difficulty are excluded from both sides.

Parameters:

Name Type Description Default
ratings list[Rating]

List of :class:~models.Rating objects.

required

Returns:

Type Description
float

Easiness in [0, 1], or 0.0 if no difficulty data.

compute_recency_weighted_rating(ratings, half_life_days=365.0)

Weighted mean of per-rating overall quality, decayed by age.

More recent ratings contribute more to the result. Output stays on the 1–5 scale.

Parameters:

Name Type Description Default
ratings list[Rating]

List of :class:~models.Rating objects.

required
half_life_days float

Exponential decay half-life in days.

365.0

Returns:

Type Description
float

Recency-weighted mean in [1, 5], or 0.0 for empty input.

compute_reliability_score(num_ratings, target=25)

Bayesian confidence score based on sample size.

Uses a logistic curve centred at target ratings: ~0.5 at target, approaches 1 asymptotically, never reaches 0.

Parameters:

Name Type Description Default
num_ratings int

Total number of ratings for the professor.

required
target int

Rating count at which confidence reaches ~0.5 (default 25).

25

Returns:

Type Description
float

Confidence score in (0, 1).

compute_review_velocity(ratings, window_years=2.0)

Average number of reviews posted per year within a rolling window.

Parameters:

Name Type Description Default
ratings list[Rating]

List of :class:~models.Rating objects.

required
window_years float

How many years back to look (default 2.0).

2.0

Returns:

Type Description
float

Reviews per year as a float, or 0.0 for empty input.

compute_tag_frequencies(ratings)

Aggregate and rank all rating tags across a professor's ratings.

Tags stored as "--"-delimited strings are split automatically; list-typed tags are used directly.

Parameters:

Name Type Description Default
ratings list[Rating]

List of :class:~models.Rating objects.

required

Returns:

Type Description
list[tuple[str, int]]

List of (tag, count) tuples sorted descending by frequency.