RapidFuzz: Fuzzy String Matching in Python (2026) — typos, reordered words, and "Acme Corp." vs "ACME Corporation" break exact joins. RapidFuzz scores how similar two strings are, in C++ speed, with an MIT licence.
Exact string tools from our Python string operations guide handle clean text. Real-world names are messy, and dropping exact duplicates as in dropping duplicate rows in pandas misses near-duplicates. This guide fills that gap.
TL;DR
fuzz.ratio— plain edit-based similarity, 0 to 100fuzz.WRatio— smart weighted score; best default for names and titlesprocess.extractOne— best match from a list of choicesprocess.cdist— all-pairs score matrix, multi-core, great for deduperapidfuzz.distance.Levenshtein— raw edit distances and edit operations
Install
python3 -m venv .venv
source .venv/bin/activate
pip install rapidfuzz==3.14.6 numpy
1. ratio vs partial_ratio vs token_sort_ratio vs WRatio
from rapidfuzz import fuzz
a = "New York Mets vs Atlanta Braves"
b = "Atlanta Braves vs New York Mets"
print("ratio: ", round(fuzz.ratio(a, b), 1))
print("partial_ratio: ", round(fuzz.partial_ratio("Mets", a), 1))
print("token_sort_ratio:", round(fuzz.token_sort_ratio(a, b), 1))
print("WRatio: ", round(fuzz.WRatio(a, b), 1))
Output (RapidFuzz 3.14.6, Python 3.13):
ratio: 45.2
partial_ratio: 100.0
token_sort_ratio: 100.0
WRatio: 95.0
Same words in a different order score only 45.2 with ratio, but 100 with token_sort_ratio. WRatio combines several strategies and returns 95.0 — a sensible default when you do not know how the strings differ.
2. Best match with extractOne and extract
from rapidfuzz import process, fuzz, utils
cities = ["Karachi", "Lahore", "Islamabad", "Rawalpindi", "Faisalabad"]
print(process.extractOne("islamabd", cities, scorer=fuzz.WRatio,
processor=utils.default_process))
print(process.extract("lahor", cities, limit=2,
processor=utils.default_process))
Output (RapidFuzz 3.14.6, Python 3.13):
('Islamabad', 94.11764705882352, 2)
[('Lahore', 90.9090909090909, 1), ('Islamabad', 40.0, 2)]
Each result is (choice, score, index). utils.default_process lowercases and strips punctuation first. Pass score_cutoff=80 to get None instead of a weak match.
3. Deduplicate company names with cdist
import numpy as np
from rapidfuzz import process, fuzz, utils
names = ["Acme Corp", "ACME Corporation", "Globex Inc",
"Globex, Inc.", "Initech", "Acme Corp."]
m = process.cdist(names, names, scorer=fuzz.token_set_ratio,
processor=utils.default_process, workers=-1)
seen, groups = set(), []
for i in range(len(names)):
if i in seen:
continue
idx = [j for j in np.where(m[i] >= 85)[0] if j not in seen]
seen.update(idx)
groups.append([names[j] for j in idx])
for g in groups:
print(g)
Output (RapidFuzz 3.14.6, Python 3.13):
['Acme Corp', 'Acme Corp.']
['ACME Corporation']
['Globex Inc', 'Globex, Inc.']
['Initech']
cdist returns a NumPy matrix and workers=-1 uses every core. Note that "ACME Corporation" stays separate at a threshold of 85 — tune the threshold on real data. For large tables, load with Polars or pandas (see fast CSV processing with Polars and pandas) and run cdist on blocked groups (for example by first letter) instead of all rows at once.
4. Levenshtein distance and edit operations
from rapidfuzz.distance import Levenshtein
print(Levenshtein.distance("kitten", "sitting"))
print(round(Levenshtein.normalized_similarity("kitten", "sitting"), 3))
print(Levenshtein.editops("kitten", "sitting"))
Output (RapidFuzz 3.14.6, Python 3.13):
3
0.571
Editops([Editop(tag='replace', src_pos=0, dest_pos=0), Editop(tag='replace', src_pos=4, dest_pos=4), Editop(tag='insert', src_pos=6, dest_pos=6)], src_len=6, dest_len=7)
"kitten" to "sitting" takes three edits: two replacements and one insertion. normalized_similarity scales that to 0–1.
5. Real speed vs difflib
import difflib, random, string, time
from rapidfuzz import process, fuzz
random.seed(42)
words = ["".join(random.choices(string.ascii_lowercase, k=10)) for _ in range(20000)]
query = "pythonista"
t = time.perf_counter()
d = difflib.get_close_matches(query, words, n=1, cutoff=0.0)
t_difflib = time.perf_counter() - t
t = time.perf_counter()
r = process.extractOne(query, words, scorer=fuzz.ratio)
t_rf = time.perf_counter() - t
print(f"difflib: {t_difflib*1000:.1f} ms -> {d[0]}")
print(f"rapidfuzz: {t_rf*1000:.1f} ms -> {r[0]} ({r[1]:.1f})")
print(f"speedup: {t_difflib/t_rf:.0f}x")
Output (RapidFuzz 3.14.6, Python 3.13):
difflib: 188.8 ms -> pyongtqxya
rapidfuzz: 0.9 ms -> pyongtqxya (60.0)
speedup: 201x
On 20,000 random words, RapidFuzz found the same best match about 200x faster than the standard-library difflib. Timings vary by machine; the gap does not.
Production tips
- Always set a
processorso case and punctuation do not skew scores - Use
score_cutoffto skip work and avoid bad matches - Block large datasets before
cdist; an all-pairs matrix grows with n squared - Pin the version (
rapidfuzz==3.14.6) so scores stay reproducible
Wrap-up
RapidFuzz gives you fast, drop-in fuzzy matching: WRatio for scoring, extractOne for lookups, cdist for dedupe, and Levenshtein for raw distances — far faster than difflib.