Contents
Deep Breakdown
What it tells me (Situation Awareness): Jaccard similarity (also called Intersection over Union, IoU) measures the overlap between two sets: the size of their intersection divided by the size of their union. Range: . means the sets are identical; means they share nothing. In DA/DS: Jaccard is the standard similarity metric for sets and binary data. Object detection (IoU of predicted vs. ground-truth bounding boxes), recommendation systems (user purchase set overlap), NLP (n-gram or word set similarity), duplicate detection (MinHash/LSH approximation of Jaccard), and A/B test audience overlap analysis are all core use cases.
Flowchart
Real-world use
Where it is used
Object detection evaluation (IoU of bounding boxes vs. threshold like 0.5 or 0.75), semantic segmentation (per-class IoU), user-item basket overlap in recommendation, duplicate/near-duplicate document detection via MinHash, keyword set similarity in NLP, and audience overlap in advertising (shared users between two campaign exposures).
Problem it solves
Set-based similarity needs a scale-invariant measure that accounts for both shared elements and the "missed" elements on either side. A simple intersection count is not normalized: two large sets with a 50-element intersection look very similar, even if one has 1000 elements (missing 950). Jaccard normalizes by the union.
How it solves the problem
. Dividing the intersection by the union normalizes for set size: . For binary vectors : (count of positions where both are 1), (count of positions where at least one is 1).
Example in a business
A DS evaluates an object detection model. For each predicted bounding box and ground-truth box, they compute . A prediction is considered correct at the COCO benchmark if IoU . The mean IoU (mIoU) across all classes is the primary evaluation metric for the model release decision.
The formula, piece by piece
- : two sets (or binary vectors viewed as indicator functions of sets)
- : number of elements in both and (intersection size)
- : number of elements in or or both (union size)
- : 0 = no overlap, 1 = identical sets
- Equivalent for binary vectors:
- Jaccard distance: — IS a proper metric (satisfies triangle inequality)
- For continuous (non-binary) data, use generalized Jaccard:
The formula in words
"Count the elements shared by both sets. Count all elements in either set. Divide: how big is the overlap relative to everything involved? 1 = perfect overlap, 0 = no shared elements." Tiny example: , . . . .
Critical Concepts
- Jaccard distance is a proper metric: satisfies all four metric axioms including triangle inequality (unlike cosine distance). This makes it safe for metric-based algorithms.
- MinHash approximation: for large-scale set similarity (e.g., comparing millions of documents), computing exact Jaccard is expensive. MinHash provides an unbiased estimator: .
- IoU threshold in object detection: COCO mAP is computed at IoU thresholds . A higher threshold means stricter localization requirements.
- Asymmetry of set sizes: if (e.g., a tiny predicted region vs. large ground truth), IoU is low even if the prediction is fully inside the ground truth. This is a known limitation for small object detection.
- Zero-zero problem: if both and for feature , this contributes nothing to either numerator or denominator. Jaccard ignores shared absences — this is desirable for sparse data (zero means "no occurrence", not "similar").
- **vs.
Dice coefficient:** Dice weights the intersection more heavily than Jaccard. Dice .
Step-by-step calculation (tiny example)
Step 1: Define the sets and compute intersection User A purchased: . User B purchased: . . .
Step 2: Compute union . . (Alternatively: .)
Step 3: Compute Jaccard similarity and distance . .
Code examples
Beginner example (Python)
def jaccard_similarity(set_a: set, set_b: set) -> float:
"""Jaccard similarity between two sets."""
intersection = len(set_a & set_b)
union = len(set_a | set_b)
return intersection / union if union > 0 else 0.0
def jaccard_binary(a, b) -> float:
"""Jaccard similarity for binary (0/1) vectors."""
import numpy as np
a, b = np.asarray(a, dtype=bool), np.asarray(b, dtype=bool)
intersection = int(np.sum(a & b))
union = int(np.sum(a | b))
return intersection / union if union > 0 else 0.0 # Set example
A = {1, 3, 5, 7, 9}
B = {3, 5, 6, 9, 10}
print(f"J(A, B) = {jaccard_similarity(A, B):.4f}")
print(f"d_J = {1 - jaccard_similarity(A, B):.4f}") # Binary vector example
import numpy as np
a = np.array([1, 1, 0, 1, 0, 1, 0, 0, 1, 0])
b = np.array([0, 1, 0, 1, 1, 0, 0, 1, 1, 0])
print(f"\nBinary J = {jaccard_binary(a, b):.4f}") # Verify with scipy
from scipy.spatial.distance import jaccard
print(f"scipy jaccard distance: {jaccard(a, b):.4f} (= 1 - J)")Beginner example (R)
# -- Jaccard similarity ---
jaccard_sim <- function(a, b) { # Works for numeric (binary) vectors
intersection <- sum(a & b)
union <- sum(a | b)
if (union == 0)
return(0) intersection / union
}
A <- c(1, 3, 5, 7, 9)
B <- c(3, 5, 6, 9, 10)
intersection <- length(intersect(A, B))
union_size <- length(union(A, B))
j_ab <- intersection / union_size
cat(sprintf("|A ∩ B| = %d, |A ∪ B| = %d\n", intersection, union_size))
cat(sprintf("J(A, B) = %.4f\n", j_ab))
cat(sprintf("d_J = %.4f\n", 1 - j_ab)) # Binary vector
a <- c(1, 1, 0, 1, 0, 1, 0, 0, 1, 0)
b <- c(0, 1, 0, 1, 1, 0, 0, 1, 1, 0)
cat(sprintf("\nBinary J = %.4f\n", jaccard_sim(a, b)))Professional example (Python)
import numpy as np
import pandas as pd
np.random.seed(42) # -- Business scenario: IoU evaluation for object detection bounding boxes ---
def bbox_iou(box1: np.ndarray, box2: np.ndarray) -> float:
""" Compute IoU (Jaccard) between two bounding boxes. box format: [x_min, y_min, x_max, y_max] """
x_inter1 = max(box1[0], box2[0])
y_inter1 = max(box1[1], box2[1])
x_inter2 = min(box1[2], box2[2])
y_inter2 = min(box1[3], box2[3])
inter_area = max(0, x_inter2 - x_inter1) * max(0, y_inter2 - y_inter1)
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union_area = area1 + area2 - inter_area
return inter_area / union_area if union_area > 0 else 0.0 # Simulated ground-truth and predicted bounding boxes
N_BOXES = 12
gt_boxes = np.column_stack([ np.random.randint(0, 400, N_BOXES), np.random.randint(0, 300, N_BOXES),
]).astype(float)
gt_boxes = np.column_stack([gt_boxes, gt_boxes + np.random.randint(50, 150, (N_BOXES, 2))]) # Predictions: add small random offsets to ground truth
noise = np.random.randint(-20, 20, gt_boxes.shape).astype(float)
pred_boxes = np.clip(gt_boxes + noise, 0, None)
IOU_THRESHOLD_50 = 0.50
IOU_THRESHOLD_75 = 0.75
results = []
for i in range(N_BOXES):
iou = bbox_iou(gt_boxes[i], pred_boxes[i])
results.append({ "box_id": i + 1, "iou": round(iou, 4), "tp_at_50": iou >= IOU_THRESHOLD_50, "tp_at_75": iou >= IOU_THRESHOLD_75, })
df = pd.DataFrame(results)
print("Object detection IoU evaluation:")
print(df.to_string(index=False))
print(f"\nmAP@50 (fraction correct): {df['tp_at_50'].mean():.4f}")
print(f"mAP@75 (fraction correct): {df['tp_at_75'].mean():.4f}")Source: domain knowledge