Repository: pyg-team/pytorch_geometric
Pull request #10013 (2025-02-11 08:18:16)

Add `LinkPredHitRatio` metric


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d06481c2b..efc454a4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,11 +7,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Added
 
+- Added `HitRatio` metric for link prediction ([#10013](https://github.com/pyg-team/pytorch_geometric/pull/10013))
 - Added Data Splitting Tutorial ([#8366](https://github.com/pyg-team/pytorch_geometric/pull/8366))
 - Added `Diversity` metric for link prediction ([#10009](https://github.com/pyg-team/pytorch_geometric/pull/10009))
 - Added `Coverage` metric for link prediction ([#10006](https://github.com/pyg-team/pytorch_geometric/pull/10006))
 - Added Graph Transformer Tutorial ([#8144](https://github.com/pyg-team/pytorch_geometric/pull/8144))
-- Consolidate Cugraph examples into ogbn_train_cugraph.py and ogbn_train_cugraph_multigpu.py for ogbn-arxiv, ogbn-products and ogbn-papers100M ([#9953](https://github.com/pyg-team/pytorch_geometric/pull/9953))
+- Consolidate Cugraph examples into `ogbn_train_cugraph.py` and `ogbn_train_cugraph_multigpu.py` for `ogbn-arxiv`, `ogbn-products` and `ogbn-papers100M` ([#9953](https://github.com/pyg-team/pytorch_geometric/pull/9953))
 - Added `InstructMol` dataset ([#9975](https://github.com/pyg-team/pytorch_geometric/pull/9975))
 - Added support for weighted `LinkPredRecall` metric ([#9947](https://github.com/pyg-team/pytorch_geometric/pull/9947))
 - Added support for weighted `LinkPredNDCG` metric ([#9945](https://github.com/pyg-team/pytorch_geometric/pull/9945))
diff --git a/torch_geometric/metrics/__init__.py b/torch_geometric/metrics/__init__.py
index 4859acd06..3886c2306 100644
--- a/torch_geometric/metrics/__init__.py
+++ b/torch_geometric/metrics/__init__.py
@@ -9,6 +9,7 @@ from .link_pred import (
     LinkPredMAP,
     LinkPredNDCG,
     LinkPredMRR,
+    LinkPredHitRatio,
     LinkPredCoverage,
     LinkPredDiversity,
 )
@@ -22,6 +23,7 @@ link_pred_metrics = [
     'LinkPredMAP',
     'LinkPredNDCG',
     'LinkPredMRR',
+    'LinkPredHitRatio',
     'LinkPredCoverage',
     'LinkPredDiversity',
 ]
diff --git a/torch_geometric/metrics/link_pred.py b/torch_geometric/metrics/link_pred.py
index 5be78188f..39c6627fa 100644
--- a/torch_geometric/metrics/link_pred.py
+++ b/torch_geometric/metrics/link_pred.py
@@ -423,7 +423,12 @@ class LinkPredMetricCollection(torch.nn.ModuleDict):
 
 
 class LinkPredPrecision(LinkPredMetric):
-    r"""A link prediction metric to compute Precision @ :math:`k`.
+    r"""A link prediction metric to compute Precision @ :math:`k`, *i.e.* the
+    proportion of recommendations within the top-:math:`k` that are actually
+    relevant.
+
+    A higher precision indicates the model's ability to surface relevant items
+    early in the ranking.
 
     Args:
         k (int): The number of top-:math:`k` predictions to evaluate against.
@@ -437,7 +442,11 @@ class LinkPredPrecision(LinkPredMetric):
 
 
 class LinkPredRecall(LinkPredMetric):
-    r"""A link prediction metric to compute Recall @ :math:`k`.
+    r"""A link prediction metric to compute Recall @ :math:`k`, *i.e.* the
+    proportion of relevant items that appear within the top-:math:`k`.
+
+    A higher recall indicates the model's ability to retrieve a larger
+    proportion of relevant items.
 
     Args:
         k (int): The number of top-:math:`k` predictions to evaluate against.
@@ -472,7 +481,11 @@ class LinkPredF1(LinkPredMetric):
 
 class LinkPredMAP(LinkPredMetric):
     r"""A link prediction metric to compute MAP @ :math:`k` (Mean Average
-    Precision).
+    Precision), considering the order of relevant items within the
+    top-:math:`k`.
+
+    MAP @ :math:`k` can provide a more comprehensive view of ranking quality
+    than precision alone.
 
     Args:
         k (int): The number of top-:math:`k` predictions to evaluate against.
@@ -493,6 +506,10 @@ class LinkPredNDCG(LinkPredMetric):
     r"""A link prediction metric to compute the NDCG @ :math:`k` (Normalized
     Discounted Cumulative Gain).
 
+    In particular, can account for the position of relevant items by
+    considering relevance scores, giving higher weight to more relevant items
+    appearing at the top.
+
     Args:
         k (int): The number of top-:math:`k` predictions to evaluate against.
         weighted (bool, optional): If set to :obj:`True`, assumes sorted lists
@@ -549,7 +566,8 @@ class LinkPredNDCG(LinkPredMetric):
 
 class LinkPredMRR(LinkPredMetric):
     r"""A link prediction metric to compute the MRR @ :math:`k` (Mean
-    Reciprocal Rank).
+    Reciprocal Rank), *i.e.* the mean reciprocal rank of the first correct
+    prediction (or zero otherwise).
 
     Args:
         k (int): The number of top-:math:`k` predictions to evaluate against.
@@ -564,9 +582,28 @@ class LinkPredMRR(LinkPredMetric):
         return (pred_rel_mat / arange).max(dim=-1)[0]
 
 
+class LinkPredHitRatio(LinkPredMetric):
+    r"""A link prediction metric to compute the hit ratio @ :math:`k`, *i.e.*
+    the percentage of users for whom at least one relevant item is present
+    within the top-:math:`k` recommendations.
+
+    A high ratio signifies the model's effectiveness in satisfying a broad
+    range of user preferences.
+    """
+    higher_is_better: bool = True
+    weighted: bool = False
+
+    def _compute(self, data: LinkPredMetricData) -> Tensor:
+        pred_rel_mat = data.pred_rel_mat[:, :self.k]
+        return pred_rel_mat.max(dim=-1)[0].to(torch.get_default_dtype())
+
+
 class LinkPredCoverage(_LinkPredMetric):
     r"""A link prediction metric to compute the Coverage @ :math:`k` of
-    predictions.
+    predictions, *i.e.* the percentage of unique items recommended across all
+    users within the top-:math:`k`.
+
+    Higher coverage indicates a wider exploration of the item catalog.
 
     Args:
         k (int): The number of top-:math:`k` predictions to evaluate against.


Test patch:
diff --git a/test/metrics/test_link_pred_metric.py b/test/metrics/test_link_pred_metric.py
index b50bfb7e7..b0b25651e 100644
--- a/test/metrics/test_link_pred_metric.py
+++ b/test/metrics/test_link_pred_metric.py
@@ -7,6 +7,7 @@ from torch_geometric.metrics import (
     LinkPredCoverage,
     LinkPredDiversity,
     LinkPredF1,
+    LinkPredHitRatio,
     LinkPredMAP,
     LinkPredMetricCollection,
     LinkPredMRR,
@@ -180,6 +181,23 @@ def test_mrr():
     metric.reset()
 
 
+def test_hit_ratio():
+    pred_index_mat = torch.tensor([[1, 0], [1, 2], [0, 2], [0, 1]])
+    edge_label_index = torch.tensor([[0, 0, 2, 2, 3], [0, 1, 2, 1, 2]])
+
+    metric = LinkPredHitRatio(k=2)
+    assert str(metric) == 'LinkPredHitRatio(k=2)'
+    metric.update(pred_index_mat, edge_label_index)
+    result = metric.compute()
+
+    assert float(result) == pytest.approx(2 / 3)
+
+    # Test with `k > pred_index_mat.size(1)`:
+    metric.update(pred_index_mat[:, :1], edge_label_index)
+    metric.compute()
+    metric.reset()
+
+
 def test_coverage():
     pred_index_mat = torch.tensor([[1, 0], [1, 2], [0, 2], [0, 1]])
     edge_label_index = torch.empty(2, 0, dtype=torch.long)