Duplicate data is the same real-world entity represented more than once. In a hospital that is rarely an exact copy: it is one patient registered four times across an emergency visit, an outpatient clinic, a transfer from another facility and a corrected spelling of their name. The modelling consequence is specific and severe. If those four records split across training and test, the model is evaluated on a person it has already memorised, and the reported score describes recall of individuals rather than generalisation to new ones.
Deduplication is an identity problem before it is a data-cleaning problem. Until you can say which records are the same person, you cannot split the dataset honestly.
What problem does this solve?
A hospital group runs a readmission-risk model across four facilities. The master patient index holds 612,000 records. Clinical audit believes those represent roughly 540,000 distinct people, so around 12% of records are duplicates of someone already present.
The duplicates arise the way they always do. Emirates ID is captured at some registration desks and skipped at others when a patient arrives unconscious. Arabic names transliterate several ways, so Mohammed, Mohamed and Muhammad all appear. Dates of birth default to the first of January when only a year is known. Two facilities merged their systems in 2023 and matched on name and date of birth alone. A patient who married and changed surname exists twice by design.
The model reported an AUC of 0.89 in validation and 0.71 in the first quarter after deployment. Nobody had checked whether the same person appeared on both sides of the split. Around 12% of the test set was people the model had already seen, and those were the easiest predictions in it.
How the solution works
Separate the three duplicate types, because they need different treatment. An exact duplicate is a byte-identical row, usually an ingestion fault, and can be removed by hashing. A near duplicate is the same entity with field-level variation and needs similarity matching. A repeated encounter is legitimately several rows for one person and must not be removed at all.
Resolve entities before splitting. Blocking narrows the comparison space, similarity scoring compares candidates within a block, and a clustering step assigns a stable enterprise identifier to each resolved person.
Split by that identifier, not by row. Group-aware splitting guarantees every record belonging to one patient falls entirely on one side, which is what makes the reported score honest.
Keep the merge reversible. A wrongly merged pair of patients is a clinical safety event, not a data-quality one, so every merge decision needs an audit trail and an unmerge path.
- 1Hash for exact duplicates Normalise then hash the row. Identical hashes are ingestion artefacts and are safe to collapse.
- 2Block to reduce comparisons Compare only records sharing a coarse key such as birth year plus a name phonetic code. Comparing 612,000 records pairwise is 187 billion comparisons; blocking makes the problem tractable.
- 3Score similarity per field Use edit distance on names, exact match on national identifier, and date proximity on birth date, then combine into one score.
- 4Cluster into entities Group records above threshold into clusters and assign a stable enterprise identifier per cluster.
- 5Split by entity Use the enterprise identifier as the grouping key so no patient appears in both training and test.
Reference architecture
Four layers. The identity layer is the one that decides whether the evaluation layer can be trusted.
| Layer | What it contains |
|---|---|
| Capture layer | Registration workflow, which identifier fields are mandatory, and what happens when a patient cannot present one. |
| Identity layer | Blocking, similarity scoring, clustering and the enterprise identifier that results. |
| Governance layer | Merge and unmerge workflow, audit trail, and who is authorised to confirm a match. |
| Evaluation layer | Group-aware splitting on the enterprise identifier so no patient spans training and test. |
Deployment options: Patient identifiers are among the most sensitive fields a hospital holds. Matching runs inside the facility's approved environment, and comparison logs contain identifying data in their own right.
Key capabilities
Duplication rate estimation
A measured duplicate rate per facility and per registration route, rather than an assumption.
availableBlocking and similarity design
A candidate-generation strategy that makes matching tractable without discarding true matches.
availableEntity resolution pipeline
A stable enterprise identifier per resolved person, with confidence recorded per cluster.
custom developmentGroup-aware evaluation
Splitting keyed on the resolved identifier so reported scores reflect new patients, not remembered ones.
custom developmentIntegrations
Entity resolution touches the registration workflow and the master patient index far more than it touches the model, because the durable fix is upstream.
| System | Integration point & data exchanged | Direction |
|---|---|---|
| Master patient index | Resolved enterprise identifiers written back with confidence scores and a reversible merge history. → Label Noise: The Accuracy Ceiling Nobody Put There Deliberately | bi-directional |
| Registration workflow | Identifier capture made mandatory where clinically possible, which prevents duplicates rather than repairing them. → Missing Values: Why the Gap Itself Carries Information | bi-directional |
| Model training pipeline | Enterprise identifier carried as the grouping key through every split and every retrain. → Data Leakage: The Model That Only Works Before Deployment | bi-directional |
Industry use cases
Hospital readmission modelling
The target is defined per patient, so a duplicated patient corrupts both the label and the split.
Insurance claims analytics
One member across several policies produces the same leakage pattern as one patient across several records.
Government service registries
Citizens recorded under transliteration variants inflate population counts and distort eligibility models.
Customer master data
Household and business duplicates break both churn modelling and any per-entity revenue measure.
UAE & GCC considerations
Arabic name handling is the dominant technical source of near duplicates in the region. One name may be transliterated several ways in the Latin field while the Arabic field is consistent, or vice versa, so matching should compare the Arabic script directly rather than relying on a Latin transliteration. Compound names with particles such as Al, Abd and bin split differently between systems, and definite-article prefixes are inconsistently attached. Where Emirates ID is captured it is close to decisive and should be allowed to veto a name match outright. Where it is absent - unconscious admissions, visitors, neonates - the match must fall back on weaker evidence, which is exactly where merge errors concentrate and where human confirmation belongs.
Implementation approach
- 1Measure before building Estimate the duplicate rate on a manually reviewed sample so the pipeline has a target to hit.
- 2Fix capture where possible Mandatory identifier capture at registration prevents more duplicates than any matching algorithm resolves.
- 3Design blocking deliberately Choose blocking keys that survive Arabic transliteration variation, and measure how many true matches each key would miss.
- 4Set thresholds by consequence In a clinical setting a false merge is worse than a missed one, so the threshold should be conservative and borderline pairs should go to a human.
- 5Split by entity everywhere Apply the enterprise identifier as the grouping key in every split, including cross-validation folds.
Security & deployment
Entity resolution requires comparing identifying fields in clear text, so the matching process handles the most sensitive data the organisation holds and must run inside the approved clinical environment. Comparison logs, candidate-pair files and review queues all contain patient identifiers and inherit the same classification as the records themselves. A false merge exposes one patient's clinical history to another and is a patient-safety incident: every merge needs an audit trail, an authorising user and a tested unmerge path before automation is enabled. Nothing in this article requires or exposes real patient data - all examples are illustrative.
A worked example
Four records that entity resolution should recognise as one person, and one that it must not.
- Record 1. MOHAMMED AL SAYED, DOB 1971-03-14, national ID present, emergency admission.
- Record 2. Mohamed Alsayed, DOB 1971-03-14, national ID absent, outpatient clinic. Name distance is small and the date matches exactly.
- Record 3. M. AL-SAYED, DOB 1971-01-01, national ID present and identical to record 1. The date is a default placeholder; the identifier is decisive.
- Record 4. Mohammed Al Sayed, DOB 1971-03-14, transferred from another facility, different medical record number. Same person, different source system.
- Record 5 - the trap. Mohammed Al Sayed, DOB 1971-03-14, different national ID, different address. A common name and a shared birth date. This is a different person and merging it would be a patient-safety incident.
Records 1 to 4 resolve to one enterprise identifier and carry three legitimate encounters between them. Record 5 stays separate because a present-and-different national identifier outweighs a strong name and date match. That asymmetry - identifiers can veto, names cannot confirm alone - is the core rule of clinical entity resolution.
In code
The pipeline is hash, block, score, cluster, then split by the resolved identifier. The last step is the one that actually protects the evaluation.
import hashlib
import pandas as pd
from itertools import combinations
from difflib import SequenceMatcher
from sklearn.model_selection import GroupShuffleSplit
# 1. Exact duplicates: normalise, then hash. Ingestion artefacts only.
def row_hash(r):
key = "|".join(str(r[c]).strip().upper() for c in ["name", "dob", "national_id"])
return hashlib.sha256(key.encode()).hexdigest()
df["row_hash"] = df.apply(row_hash, axis=1)
exact = df["row_hash"].duplicated().sum()
print("exact duplicates:", int(exact))
df = df.drop_duplicates(subset="row_hash").copy()
# 2. Blocking: never compare every pair. n(n-1)/2 is not tractable at scale.
def phonetic(name):
s = "".join(ch for ch in str(name).upper() if ch.isalpha())
return s[:4]
df["block"] = df["dob"].str[:4] + "_" + df["name"].map(phonetic)
print("blocks:", df["block"].nunique(), "| largest:", df["block"].value_counts().max())
# 3. Similarity scoring inside each block.
def score(a, b):
name = SequenceMatcher(None, str(a["name"]).upper(), str(b["name"]).upper()).ratio()
dob = 1.0 if a["dob"] == b["dob"] else 0.0
ida, idb = a["national_id"], b["national_id"]
if pd.notna(ida) and pd.notna(idb):
# A present-and-different identifier vetoes the match outright.
return 1.0 if ida == idb else 0.0
return 0.6 * name + 0.4 * dob
pairs = []
for _, grp in df.groupby("block"):
for i, j in combinations(grp.index, 2):
sc = score(df.loc[i], df.loc[j])
if sc >= 0.90:
pairs.append((i, j, sc))
print("candidate matches:", len(pairs))
# 4. Cluster matched pairs into entities (union-find).
parent = {i: i for i in df.index}
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]; x = parent[x]
return x
for i, j, _ in pairs:
ri, rj = find(i), find(j)
if ri != rj: parent[ri] = rj
df["enterprise_id"] = [find(i) for i in df.index]
print("records:", len(df), "-> distinct patients:", df["enterprise_id"].nunique())
# 5. THE STEP THAT MATTERS: split by patient, never by row.
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=8)
train_idx, test_idx = next(gss.split(df, groups=df["enterprise_id"]))
overlap = set(df.iloc[train_idx]["enterprise_id"]) & set(df.iloc[test_idx]["enterprise_id"])
print("patients in both splits:", len(overlap)) # must be 0A count of exact duplicates, the blocking statistics, the number of candidate matches, the reduction from records to distinct patients, and a final overlap count that must be zero. If that last number is not zero the evaluation is invalid regardless of every other result.
Diagnostic checks
- Count distinct enterprise identifiers appearing in both training and test. Anything above zero invalidates the evaluation.
- Compare validation and production performance. A large drop with no distribution change is a classic duplicate-leakage signature.
- Sample the highest-similarity non-merged pairs and the lowest-similarity merged pairs; both reveal a badly set threshold.
- Check duplicate rate per facility and per registration route. A single desk or a single import usually accounts for most of it.
- Look for placeholder birth dates such as the first of January. They cluster duplicates and defeat date-based blocking.
- Verify every merge is reversible and logged before any automated merging is enabled.
When to use it
- Records are created at multiple points with no single enforced identifier.
- Systems have been merged, migrated or consolidated, which is when most duplicate populations are created.
- Names arrive through transliteration, which guarantees field-level variation for the same person.
- The modelling target is defined per entity rather than per row.
When not to use it
- A single authoritative identifier is enforced at every capture point and validated, where duplicates are rare enough to handle individually.
- Multiple rows per entity are the intended structure - repeated encounters are not duplicates and removing them destroys the history.
- The dataset is small enough for manual review, which is more accurate than any automated matcher.
- The consequence of a false merge is unacceptable and no human confirmation step is available.
Limitations & prerequisites
- Blocking trades recall for tractability; any key will miss some true matches and the trade should be measured rather than assumed.
- String similarity is weak on short names and on transliteration variants that differ early in the string.
- Thresholds encode a value judgement about false merges versus missed merges and cannot be set from the data alone.
- Resolution is never final: new records arrive continuously and identifiers change with marriage, correction and reissue.
Duplicate types and their treatment
Only the first is safe to remove automatically. The third must not be removed at all.
| Type | How it presents | Correct treatment |
|---|---|---|
| Exact duplicate | Byte-identical row | Hash and collapse |
| Near duplicate | Same person, field variation | Similarity match, then merge |
| Repeated encounter | Same person, different visit | Keep; aggregate per entity |
| Cross-system duplicate | Different record numbers | Resolve to one enterprise identifier |
| False match | Different people, similar fields | Identifier veto; human review |
Removing repeated encounters as duplicates is the most common error in the list, and it silently deletes the longitudinal history most clinical models depend on.
Key takeaways
- Most hospital duplicates are near duplicates, not exact copies, so hashing alone finds very few of them.
- A duplicated patient across the split turns an evaluation into a memory test.
- Repeated encounters are legitimate records and must not be deduplicated away.
- A present-and-different national identifier should veto an otherwise strong name match.
- Split by resolved entity identifier, and confirm the overlap between splits is exactly zero.
FAQ
If one patient appears in both training and test, the model is scored on someone it already memorised. The reported figure measures recall of individuals rather than generalisation, and it collapses in production.
It handles exact duplicates only, which are usually the smallest category. Near duplicates and cross-system duplicates survive any hash-based deduplication because no two rows are identical.
Blocking restricts comparisons to records sharing a coarse key. Comparing 612,000 records pairwise is about 187 billion comparisons; blocking reduces that by orders of magnitude while keeping most true matches.
From consequence, not from an F-score. In a clinical setting a false merge is far worse than a missed one, so set the threshold conservatively and route borderline pairs to human review.
No. Several encounters for one patient are legitimate records. Resolve them to a shared entity identifier and aggregate as the task requires, but never delete them.
One name transliterates several ways, particles such as Al and bin are attached inconsistently, and the definite article may or may not be present. Matching on the Arabic script where available is more reliable than matching transliterations.
Only above a conservative threshold, with an audit trail and a working unmerge path. Borderline pairs belong with a human, because a wrong merge is a clinical safety event.
Duplicate leakage is a form of group leakage: the same entity spans the split. The fix is the same - group-aware splitting on a resolved entity identifier.
Unsure how many patients are in your patient index?
Send the record schema, the identifier fields collected at registration and the volume of merges your team performs manually. We will estimate the duplication rate and assess whether models trained on it can be evaluated safely.
Request a patient data integrity assessmentSources & evidence
- scikit-learn: GroupShuffleSplit — Official reference for group-aware train and test splitting.
- Fellegi & Sunter, A Theory for Record Linkage — The probabilistic record-linkage framework underlying modern entity resolution.
- Christen, Data Matching — Reference text covering blocking, comparison functions and classification for record linkage.
- scikit-learn: cross-validation with groups — Official guidance on grouped cross-validation strategies.
Vendor and product names are trademarks of their respective owners; references are for technical context and do not imply partnership, certification or endorsement unless stated on the vendor's official pages.