PY-88

Calculate Retrieval Measures at Fixed Cutoffs

  • Medium–Hard
  • Retrieval Evaluation
  • Python

Task

Write retrieval_measures_at_cutoffs(ranked_ids, relevant_ids, cutoffs). ranked_ids is an ordered list of retrieved document IDs; duplicate retrieved IDs are allowed. relevant_ids is a list of unique IDs judged relevant for this query. cutoffs is a list of unique positive integers, and its order is the order of the returned records.

For each cutoff k, inspect the first min(k, len(ranked_ids)) retrieved positions. The number of hits is the number of distinct IDs in that prefix that occur in relevant_ids: a duplicate occupies another retrieved position but earns no second relevance hit. Missing relevant IDs remain in the recall denominator. Use these exact formulas:

precision = hits / retrieved_positions       (0.0 when the denominator is 0)
recall    = hits / len(relevant_ids)          (0.0 when there are no relevant IDs)
f1        = 2 * precision * recall / (precision + recall)
            (0.0 when precision + recall is 0)

Return a list of dictionaries in the supplied cutoff order. Each dictionary has exactly "cutoff", "retrieved", "hits", "precision", "recall", and "f1"; retrieved is the number of prefix positions, including duplicates. All three measures are Python floats. Raise ValueError("relevant IDs must be unique") for a repeated relevant ID, ValueError("cutoffs must be positive and unique") for a non-positive or repeated cutoff, and keep every input unchanged.

Example

For ranked IDs ['d1','d2','d1','d3'], relevant IDs ['d1','d3'], and cutoffs [1,3,5], the records have (retrieved, hits) of (1,1), (3,1), and (4,2). The duplicate d1 at position three increases the precision denominator but not the hit count. The cutoff 5 simply uses all four available positions.

If relevant_ids is empty, every recall and F1 value is 0.0; if a cutoff prefix is empty, its precision is 0.0 as well.

Your implementation

No retrieval library or ranking theory is required. Do not print or ask for input.