← All Posts
Machine Learning · Search & Retrieval · Evaluation

Precision, Recall, MAP, and MRR: One Ranking, Four Questions

Before you start: relevance judgments and test collections. Here every item is judged and eligible; we can focus on what the metrics count.

A search engine returns B, A, C, E, D, F. The useful items in this six-document catalog are A, C, D and F. The first result is wrong, three useful results are in the first five slots, and one sits just beyond them.

We will keep that example throughout the chapter. Precision asks how much of the displayed list is useful. Recall asks how much useful material we recovered. Average precision rewards finding useful items early; reciprocal rank looks only for the first one.

One overlap, two denominators

Choose a cutoff $K$: the number of ranked slots being evaluated. At $K=5$, we inspect B, A, C, E and D. Three are relevant. That same count answers two different questions:

Precision: inspect the five slots

B · noA · hitC · hitE · noD · hit

3 useful / 5 slots = 0.60

Recall: inspect the four useful items

A · foundC · foundD · foundF · missed

3 found / 4 relevant = 0.75

The shared count is A, C and D. Precision divides by the displayed slots; recall divides by everything known to be relevant. “Missed” means outside the evaluated top five, even though F appears at rank six.

For the equation, let $r_i$ equal 1 when the item at rank $i$ is relevant, and 0 otherwise. Let $R$ be the total number of relevant items in the eligible catalog’s judgments. Here $R=4$. Adding the first $K$ indicators counts the hits:

$$P@K=\frac{\sum_{i=1}^{K}r_i}{K},\qquad \operatorname{Recall}@K=\frac{\sum_{i=1}^{K}r_i}{R}.$$

Moving A to rank one keeps the top-five membership unchanged, so both scores stay the same. They cannot tell which useful item appears first. Increasing $K$ can recover more items and increase recall; precision can rise or fall depending on the newly included items.

Choose $K$ to match the stage being evaluated: five visible results and a hundred candidates sent to a reranker are different questions. Compare systems at the same cutoff before attributing a score difference to better retrieval.

Average precision gives credit at each relevant hit

Imagine scanning the ranking from the top. Every time you find a relevant item, record the precision of the list you have seen so far. This prefix precision rewards finding the hit with fewer irrelevant items ahead of it.

In our example, A is the first hit at rank 2: its prefix precision is 1/2. C is the second hit at rank 3: 2/3. D is the third hit at rank 5: 3/5. F is outside the cutoff and gets zero credit. Average these four contributions, one for each relevant item: (1/2 + 2/3 + 3/5 + 0) / 4 = 53/120 ≈ 0.4417.

This is average precision at K, or AP@K. In the compact formula, $P@i$ is the prefix precision at rank $i$. Multiplying by $r_i$ keeps that term only when rank $i$ contains a relevant item:

$$\operatorname{AP}@K=\frac{1}{R}\sum_{i=1}^{K}r_i(P@i).$$

We divide by all $R$ relevant items, including those not found by the cutoff. That makes missing F matter. At $K=6$, F adds a prefix precision of 4/6; AP becomes approximately 0.6083. Irrelevant ranks contribute no direct term, but placing them before useful items lowers those items’ prefix precisions.

Why two evaluators can report different AP@K

The formula above uses $R$ as the denominator, as in trec_eval’s map_cut. Another convention divides by $\min(R,K)$, as in Recommenders’ map_at_k implementation.

At $K=2$ in our ranking, the numerator is 1/2. Dividing by four gives 0.125; dividing by two gives 0.25. The second convention normalizes against how many relevant items could fit in the cutoff. Under the first convention, even a perfect top-two list cannot recover all four relevant items, and its AP@2 is at most 0.5. Record the denominator with the score.

Reciprocal rank stops at the first useful result

For a task where one acceptable answer is enough, ask how far down the user must go before encountering any relevant item. Call that first relevant rank $j$. Reciprocal rank assigns 1 for rank one, 1/2 for rank two, 1/3 for rank three, and so on. If no relevant item appears by $K$, the score is zero:

$$\operatorname{RR}@K=\begin{cases}1/j,&\text{first relevant hit is at rank }j\le K,\\0,&\text{no relevant hit by }K.\end{cases}$$

Our first hit is A at rank two, so RR@5 is 0.5. Put A first and RR becomes 1. It stays 1 even if every later result is poor. AP can distinguish those later changes because it also counts the remaining relevant items.

Move a result and see which scores react

Start with Promote A: precision and recall stay fixed, while AP and RR improve. Then choose Delay later hits: A stays first, but C moves down. RR stays perfect while AP falls. The controls change only the ranking and cutoff; the relevance labels stay fixed.

    At K=5: precision 0.6000, recall 0.7500, AP 0.4417, reciprocal rank 0.5000.
    Each relevant row shows its contribution to AP. Prefix precision is divided by the four relevant items in the catalog. Dashed rows are outside the cutoff. Use the arrow buttons for your own ordering; preset buttons reset K to five.

    MAP and MRR average over queries

    AP and RR describe one query. Mean average precision (MAP) averages the AP values of the included queries; mean reciprocal rank (MRR) averages their RR values. Calculate each query’s score before taking the mean.

    Add a second query with one relevant item, found at rank one. It has AP=1 and RR=1. Give the two queries equal weight:

    Two fully judged queries, evaluated at K=5
    QueryFound / relevantAP@5RR@5
    Our ranking3 / 453/120 ≈ 0.44170.5
    One relevant item, found first1 / 111

    Mean AP

    (0.4417… + 1) / 2
    MAP@5 ≈ 0.7208

    Mean reciprocal rank

    (0.5 + 1) / 2
    MRR@5 = 0.75

    Each query receives half the weight. A query with four relevant documents does not receive four times the weight in these means.

    The distinction also explains macro versus micro recall. Macro recall averages the two query recalls: (0.75 + 1)/2 = 0.875. Micro recall pools the counts first: (3 + 1)/(4 + 1) = 0.80. The latter gives more influence to queries with more relevant items. Neither number is a mistake; they summarize different units. For traffic weights and diagnostic query sets, use the previous chapter’s query-mixture example.

    Keep the evaluation contract explicit

    For a short run, this series still divides precision by the requested $K$: returning two useful items for five slots gives P@5=0.4. Empty slots receive no credit. If your product intentionally evaluates only the returned list, name that different denominator.

    Require unique document IDs. Repeating A cannot recover another relevant document; our helper rejects duplicates. Also declare the unjudged-item policy. This chapter’s complete labels let us avoid treating missing assessments as known negatives.

    If $R=0$, recall and AP involve division by zero. Our helper returns None, excludes those values from their means, and reports the counts. RR is zero when there is no hit, but that does not evaluate whether abstaining was correct. Report no-answer queries separately and follow a benchmark’s stated convention when comparing with it.

    Python: reproduce the ranking, both AP conventions and query averages

    Save retrieval_metrics.py beside this snippet. The helper uses only Python’s standard library; python retrieval_metrics.py --self-test runs its numerical and edge-case checks.

    from retrieval_metrics import ranking_metrics, evaluate
    
    qrels = {"A": 3, "B": 0, "C": 2, "D": 1, "E": 0, "F": 1}
    run = ["B", "A", "C", "E", "D", "F"]
    scores = ranking_metrics(run, qrels, 5)
    print({key: round(scores[key], 4)
           for key in ("precision", "recall", "ap", "rr")})
    # {'precision': 0.6, 'recall': 0.75, 'ap': 0.4417, 'rr': 0.5}
    
    print(ranking_metrics(run, qrels, 2)["ap"])  # 0.125
    print(ranking_metrics(run, qrels, 2, ap_denominator="min")["ap"])  # 0.25
    
    report = evaluate({"q1": run, "q2": ["X"]},
                      {"q1": qrels, "q2": {"X": 1}}, 5)
    print(report["macro"]["ap"])  # mean=0.720833..., included=2, excluded=0
    print(report["macro"]["rr"])  # mean=0.75, included=2, excluded=0
    

    These metrics treat all positive grades alike. If an excellent answer should earn more credit than a marginally useful one, continue to graded gain and nDCG.

    References and further reading