Source code for corerec.evaluation.metrics
"""
Evaluation Metrics
Comprehensive metrics for recommendation system evaluation.
Author: Vishesh Yadav (mail: [email protected])
"""
import numpy as np
from typing import List, Set, Dict, Any
from collections import defaultdict
[docs]
class RankingMetrics:
"""
Ranking metrics for recommendation evaluation.
Implements standard metrics like NDCG, MAP, MRR, Precision, Recall.
Author: Vishesh Yadav (mail: [email protected])
"""
[docs]
@staticmethod
def ndcg_at_k(predictions: List, ground_truth: List, k: int = 10) -> float:
"""
Normalized Discounted Cumulative Gain @ K.
Args:
predictions: Predicted ranked list of items
ground_truth: Ground truth relevant items
k: Cut-off position
Returns:
NDCG @ K score
Author: Vishesh Yadav (mail: [email protected])
"""
predictions = predictions[:k]
ground_truth_set = set(ground_truth)
# Calculate DCG
dcg = sum(
[
(1 if pred in ground_truth_set else 0) / np.log2(i + 2)
for i, pred in enumerate(predictions)
]
)
# Calculate IDCG
ideal_length = min(k, len(ground_truth))
idcg = sum([1 / np.log2(i + 2) for i in range(ideal_length)])
return dcg / idcg if idcg > 0 else 0.0
[docs]
@staticmethod
def map_at_k(predictions: List, ground_truth: List, k: int = 10) -> float:
"""
Mean Average Precision @ K.
Author: Vishesh Yadav (mail: [email protected])
"""
predictions = predictions[:k]
ground_truth_set = set(ground_truth)
score = 0.0
num_hits = 0.0
for i, pred in enumerate(predictions):
if pred in ground_truth_set:
num_hits += 1.0
score += num_hits / (i + 1.0)
return score / min(len(ground_truth), k) if ground_truth else 0.0
[docs]
@staticmethod
def mrr_at_k(predictions: List, ground_truth: List, k: int = 10) -> float:
"""
Mean Reciprocal Rank @ K.
Author: Vishesh Yadav (mail: [email protected])
"""
predictions = predictions[:k]
ground_truth_set = set(ground_truth)
for i, pred in enumerate(predictions):
if pred in ground_truth_set:
return 1.0 / (i + 1.0)
return 0.0
[docs]
@staticmethod
def precision_at_k(predictions: List, ground_truth: List, k: int = 10) -> float:
"""
Precision @ K.
Author: Vishesh Yadav (mail: [email protected])
"""
predictions = predictions[:k]
ground_truth_set = set(ground_truth)
hits = sum(1 for pred in predictions if pred in ground_truth_set)
return hits / k
[docs]
@staticmethod
def recall_at_k(predictions: List, ground_truth: List, k: int = 10) -> float:
"""
Recall @ K.
Author: Vishesh Yadav (mail: [email protected])
"""
predictions = predictions[:k]
ground_truth_set = set(ground_truth)
hits = sum(1 for pred in predictions if pred in ground_truth_set)
return hits / len(ground_truth) if ground_truth else 0.0
[docs]
@staticmethod
def hit_rate_at_k(predictions: List, ground_truth: List, k: int = 10) -> float:
"""
Hit Rate @ K (binary: 1 if any hit, 0 otherwise).
Author: Vishesh Yadav (mail: [email protected])
"""
predictions = predictions[:k]
ground_truth_set = set(ground_truth)
return 1.0 if any(pred in ground_truth_set for pred in predictions) else 0.0
[docs]
class ClassificationMetrics:
"""
Classification metrics for binary/multi-class prediction.
Author: Vishesh Yadav (mail: [email protected])
"""
[docs]
@staticmethod
def accuracy(predictions: np.ndarray, ground_truth: np.ndarray) -> float:
"""Calculate accuracy."""
return (predictions == ground_truth).mean()
[docs]
@staticmethod
def precision(predictions: np.ndarray, ground_truth: np.ndarray) -> float:
"""Calculate precision."""
true_positives = ((predictions == 1) & (ground_truth == 1)).sum()
predicted_positives = (predictions == 1).sum()
return true_positives / predicted_positives if predicted_positives > 0 else 0.0
[docs]
@staticmethod
def recall(predictions: np.ndarray, ground_truth: np.ndarray) -> float:
"""Calculate recall."""
true_positives = ((predictions == 1) & (ground_truth == 1)).sum()
actual_positives = (ground_truth == 1).sum()
return true_positives / actual_positives if actual_positives > 0 else 0.0
[docs]
@staticmethod
def f1_score(predictions: np.ndarray, ground_truth: np.ndarray) -> float:
"""Calculate F1 score."""
p = ClassificationMetrics.precision(predictions, ground_truth)
r = ClassificationMetrics.recall(predictions, ground_truth)
return 2 * p * r / (p + r) if (p + r) > 0 else 0.0
[docs]
class DiversityMetrics:
"""
Diversity and novelty metrics for recommendations.
Author: Vishesh Yadav (mail: [email protected])
"""
[docs]
@staticmethod
def intra_list_diversity(recommendations: List[List]) -> float:
"""
Intra-list diversity (average pairwise distance within lists).
Author: Vishesh Yadav (mail: [email protected])
"""
if not recommendations:
return 0.0
diversities = []
for rec_list in recommendations:
unique_items = len(set(rec_list))
total_items = len(rec_list)
diversities.append(unique_items / total_items if total_items > 0 else 0.0)
return np.mean(diversities)
[docs]
@staticmethod
def coverage(all_recommendations: List[List], total_items: int) -> float:
"""
Catalog coverage (percentage of items ever recommended).
Author: Vishesh Yadav (mail: [email protected])
"""
recommended_items = set()
for rec_list in all_recommendations:
recommended_items.update(rec_list)
return len(recommended_items) / total_items if total_items > 0 else 0.0
[docs]
@staticmethod
def gini_coefficient(item_counts: Dict[Any, int]) -> float:
"""
Gini coefficient (measures recommendation concentration).
0 = perfect equality, 1 = perfect inequality
Author: Vishesh Yadav (mail: [email protected])
"""
if not item_counts:
return 0.0
counts = sorted(item_counts.values())
n = len(counts)
cumsum = np.cumsum(counts)
return (2 * np.sum((np.arange(1, n + 1) * counts))) / (n * cumsum[-1]) - (n + 1) / n