Names are written down by people, and people spell the same name in different ways. Meyer, Meier, Mayer and Maier are one German surname. Catherine and Katherine are one first name. Stephen Phillips and Steven Philips may well be one person. Any join, lookup or duplicate check that relies on exact equality of names will miss these.
Phonetic algorithms address this by turning a word into a code that stands for how it sounds. Words that sound alike get the same code, so records can be compared by code instead of by spelling. The idea goes back to Soundex, patented in 1918 and used for the United States Census. Lawrence Philips improved on it with Metaphone in 1990, which follows the rules of English spelling far more closely. In 2000 he published Double Metaphone, which also handles names of German, Slavic, Italian, Spanish, French, Greek and Chinese origin, and which gives a second code where a name has two common pronunciations.
This package provides both algorithms in R. Its main difference from other implementations is the length of the codes. Most implementations cut every code to four characters. Here codes keep up to 32 characters by default, which is the complete code for practically every real name, and the length can be set by the user. The sections below explain why that matters and show how to use the codes in common data tasks.
The package has three functions.
| Function | Returns |
|---|---|
metaphone(x) |
A character vector with one Metaphone code per element of
x. |
double_metaphone(x) |
A data frame with a primary and a
secondary Double Metaphone code per element of
x. |
sounds_like(x, y) |
A logical vector, TRUE where x and
y share a code. |
They share the following arguments.
| Argument | Default | Meaning |
|---|---|---|
max_length |
32 |
The largest number of characters kept in each code. Use
Inf for no limit, or 4 for the traditional
short codes. See Choosing the code
length. |
by_word |
FALSE |
If TRUE, each word of a name is encoded separately and
the codes are joined by spaces. See Names with several words. |
method |
"double" |
sounds_like() only. "double" compares
Double Metaphone codes, "metaphone" compares Metaphone
codes. |
names <- c("Meyer", "Meier", "Mayer", "Catherine", "Katherine",
"Smith", "Schmidt")
metaphone(names)
#> [1] "MYR" "MR" "MYR" "K0RN" "K0RN" "SM0" "SKMTT"
double_metaphone(names)
#> primary secondary
#> 1 MR MR
#> 2 MR MR
#> 3 MR MR
#> 4 K0RN KTRN
#> 5 K0RN KTRN
#> 6 SM0 XMT
#> 7 XMT SMTAll functions accept character vectors of any length and return one
result per element. Missing values give NA. Each distinct
value is encoded only once, so columns with many repeats are fast.
The argument max_length controls how many characters of
each code are kept. A code has roughly one character for each consonant
sound, so it grows with the length of the name.
long_name <- "Christopher Alexander Montgomery Fitzgerald"
double_metaphone(long_name)
#> primary secondary
#> 1 KRSTFRLKSNTRMNTKMRFTSKRLT KRSTFRLKSNTRMNTKMRFTSJRLT
double_metaphone(long_name, max_length = 4)
#> primary secondary
#> 1 KRST KRST
double_metaphone(long_name, max_length = 10)
#> primary secondary
#> 1 KRSTFRLKSN KRSTFRLKSN
double_metaphone(long_name, max_length = Inf)
#> primary secondary
#> 1 KRSTFRLKSNTRMNTKMRFTSKRLT KRSTFRLKSNTRMNTKMRFTSJRLTThere are three common choices.
us_surnames data, the longest code has 8 characters, and
even the four-part name above needs only 25. The limit only guards
against unusually long strings, such as whole addresses passed in by
mistake.Inf. No limit at all. Use it when you
want the complete code whatever the input.4. The traditional length used by
Philips’ original programs and by most other software. Use it to
reproduce or compare with results from other tools. As the next section
shows, it is too short for most matching tasks.With by_word = TRUE the limit applies to each word
separately.
double_metaphone(long_name, by_word = TRUE, max_length = 4)
#> primary secondary
#> 1 KRST ALKS MNTK FTSK KRST ALKS MNTK FTSJCodes made with different lengths cannot be compared with each other,
so use the same max_length for all data that you want to
match.
A code keeps the consonant sounds of a word and drops most vowels.
Some rules are easy to recognise. A “ph” sounds like F, initial “kn”,
“gn” and “wr” lose their silent first letter, and “th” becomes the digit
0 because it looks like the Greek letter theta.
rules <- c("Philip", "Knight", "Wright", "Gnome", "Thomas", "Laugh",
"Dumb", "Edge", "Xavier")
data.frame(word = rules, metaphone = metaphone(rules),
double_metaphone(rules))
#> word metaphone primary secondary
#> 1 Philip FLP FLP FLP
#> 2 Knight NT NT NT
#> 3 Wright RT RT RT
#> 4 Gnome NM NM NM
#> 5 Thomas 0MS TMS TMS
#> 6 Laugh L LF LF
#> 7 Dumb TM TM TM
#> 8 Edge EJ AJ AJ
#> 9 Xavier SFR SF SFRDouble Metaphone has many more rules than Metaphone, mostly for names from other languages. Where a name is pronounced differently in its language of origin and in English, the secondary code gives the second reading.
foreign <- c("Schmidt", "Michael", "Jose", "Tagliaro", "Czerny",
"Wasserman", "Zhao", "Breaux")
data.frame(word = foreign, double_metaphone(foreign))
#> word primary secondary
#> 1 Schmidt XMT SMT
#> 2 Michael MKL MXL
#> 3 Jose HS HS
#> 4 Tagliaro TKLR TLR
#> 5 Czerny SRN XRN
#> 6 Wasserman ASRMN FSRMN
#> 7 Zhao J J
#> 8 Breaux PR PRSo “Schmidt” has the primary code XMT, as it is said in
German, and the secondary code SMT, as an English speaker
might say it. “Smith” has the codes SM0 and
XMT. The two names share XMT, so they are
treated as possible matches. Philips recommended exactly this
comparison. Two names match if any code of one equals any code of the
other, and sounds_like() applies it.
sounds_like("Smith", c("Schmidt", "Smyth", "Smythe", "Jones"))
#> [1] TRUE TRUE TRUE FALSE
sounds_like("Wasserman", "Vasserman")
#> [1] TRUEThe original Metaphone gives only one code, so it is stricter.
This section compares the default codes with codes cut to four
characters, using max_length = 4. Philips’ original
programs, and most later implementations such as
PGRdup::DoubleMetaphone(), return only the first four
characters of each code. Four characters were enough for building search
indexes of English surnames in the 1990s. They are not enough for many
tasks today, because different names that merely start alike end up with
the same code.
The package includes us_surnames, the 5000 most common
surnames in the 2010 United States Census.
head(us_surnames)
#> surname rank count
#> 1 Smith 1 2442977
#> 2 Johnson 2 1932812
#> 3 Williams 3 1625252
#> 4 Brown 4 1437026
#> 5 Jones 5 1425470
#> 6 Garcia 6 1166120
codes_full <- double_metaphone(us_surnames$surname)$primary
codes_four <- double_metaphone(us_surnames$surname, max_length = 4)$primary
length(unique(codes_full))
#> [1] 2344
length(unique(codes_four))
#> [1] 1957Even for single surnames, which are short, cutting the codes to four characters removes almost 400 distinct codes, merging surnames that sound different. Here is one four-character code and the surnames behind it.
group <- codes_four == "ALTR"
data.frame(surname = us_surnames$surname[group],
four = codes_four[group],
full = codes_full[group])
#> surname four full
#> 1 Walters ALTR ALTRS
#> 2 Walter ALTR ALTR
#> 3 Wilder ALTR ALTR
#> 4 Elder ALTR ALTR
#> 5 Eldridge ALTR ALTRJ
#> 6 Aldridge ALTR ALTRJ
#> 7 Aldrich ALTR ALTRX
#> 8 Waldron ALTR ALTRN
#> 9 Waldrop ALTR ALTRP
#> 10 Alderman ALTR ALTRMN
#> 11 Wooldridge ALTR ALTRJWith four characters, Walter, Waldron, Aldrich and Alderman look alike. With full codes, only Walter, Wilder and Elder remain together.
The effect is much stronger for full names, which are what most record linkage tasks deal with. The example below pairs common first names from several countries with the census surnames.
first_names <- c("James", "Mary", "Robert", "Patricia", "John",
"Jennifer", "Michael", "Linda", "David", "Elizabeth",
"Christopher", "Sarah", "Maria", "Jose", "Juan",
"Mohammed", "Ahmed", "Wei", "Anna", "Sophie", "Lukas",
"Pierre", "Giovanni", "Olga", "Katarzyna", "Priya")
set.seed(2026)
people <- unique(paste(sample(first_names, 5000, replace = TRUE),
sample(us_surnames$surname, 5000, replace = TRUE)))
length(people)
#> [1] 4914
length(unique(double_metaphone(people)$primary))
#> [1] 4243
length(unique(double_metaphone(people, max_length = 4)$primary))
#> [1] 875With four-character codes, the code of a full name is little more than the code of the first name. Almost 5000 different people share fewer than 900 codes, so on average about five or six unrelated people would be flagged as one. Full codes keep most of them apart.
cr <- c("Christopher Anderson", "Christina Andrews", "Christopher Andersen")
data.frame(name = cr,
four = double_metaphone(cr, max_length = 4)$primary,
full = double_metaphone(cr)$primary)
#> name four full
#> 1 Christopher Anderson KRST KRSTFRNTRSN
#> 2 Christina Andrews KRST KRSTNNTRS
#> 3 Christopher Andersen KRST KRSTFRNTRSNA typical task is to find records in one table that may describe the same person. In this small contact list, several people were entered twice with different spellings.
contacts <- data.frame(
id = 1:12,
first = c("Catherine", "Katherine", "Stephen", "Steven", "Mohammed",
"Muhammad", "Jeffrey", "Geoffrey", "Anne", "Ann", "Sean",
"Shaun"),
last = c("Smith", "Smyth", "Phillips", "Philips", "Ali", "Aly",
"Wasserman", "Vasserman", "Thompson", "Thomson", "O'Neill",
"O'Neil"),
city = c("Leeds", "Leeds", "Boston", "Boston", "Toronto", "Toronto",
"Chicago", "Chicago", "Sydney", "Sydney", "Dublin", "Dublin")
)Comparing every record with every other record is fine for a few hundred rows, but for large tables it is too slow. The usual approach, called blocking, first groups records that share a cheap key and only compares records within a group. A natural first try is to use the primary code of the surname as the key, together with the city.
contacts$key <- paste(double_metaphone(contacts$last)$primary,
contacts$city)
pairs <- merge(contacts, contacts, by = "key", suffixes = c("_a", "_b"))
pairs <- pairs[pairs$id_a < pairs$id_b, ]
pairs$first_match <- sounds_like(pairs$first_a, pairs$first_b)
pairs[, c("first_a", "last_a", "first_b", "last_b", "first_match")]
#> first_a last_a first_b last_b first_match
#> 2 Mohammed Ali Muhammad Aly TRUE
#> 6 Sean O'Neill Shaun O'Neil FALSE
#> 11 Stephen Phillips Steven Philips TRUE
#> 16 Catherine Smith Katherine Smyth TRUEFour pairs share a block, and in three of them the first names also
sound alike. The Wasserman pair is missing, because “Wasserman” and
“Vasserman” have different primary codes, ASRMN and
FSRMN. They share only a secondary code. A key built from
one code cannot see that, while sounds_like() compares all
four combinations of primary and secondary codes. So a better plan is to
block on something simpler, here the city, and to compare names with
sounds_like() inside each block.
pairs <- merge(contacts, contacts, by = "city", suffixes = c("_a", "_b"))
pairs <- pairs[pairs$id_a < pairs$id_b, ]
same_person <- sounds_like(pairs$last_a, pairs$last_b) &
sounds_like(pairs$first_a, pairs$first_b)
pairs[same_person, c("id_a", "first_a", "last_a", "id_b", "first_b", "last_b")]
#> id_a first_a last_a id_b first_b last_b
#> 2 3 Stephen Phillips 4 Steven Philips
#> 6 7 Jeffrey Wasserman 8 Geoffrey Vasserman
#> 14 1 Catherine Smith 2 Katherine Smyth
#> 22 5 Mohammed Ali 6 Muhammad AlyNow four of the six planted duplicates are found. Jeffrey and
Geoffrey match through the shared code JFR. The other two
pairs show the limits of the method. “Thompson” and “Thomson” get
different codes, TMPSN and TMSN, because the P
in Thompson is coded although it is barely pronounced. “Sean” and
“Shaun” differ because the Irish “Se” is read by the English rule as S.
Phonetic codes reduce the problem of spelling variation, but they do not
remove it. The section on good practice below suggests ways to catch
more.
The same technique links records across two sources, for example a membership list and a donations file kept by different staff.
members <- data.frame(
member_id = c("M1", "M2", "M3", "M4", "M5"),
name = c("Jon Schmidt", "Maria Gonzalez", "Peter Kowalski",
"Claire Dubois", "Hans Meyer")
)
donations <- data.frame(
receipt = c("R1", "R2", "R3", "R4", "R5", "R6"),
donor = c("John Smith", "Mariah Gonzales", "Piotr Kowalsky",
"Clare Du Bois", "Hans Maier", "Helen Mayer"),
amount = c(50, 120, 75, 200, 30, 90)
)Split the names into first and last names, then block on the surname code.
split_name <- function(x) {
parts <- strsplit(x, " ", fixed = TRUE)
data.frame(first = vapply(parts, `[`, "", 1),
last = vapply(parts, function(p) paste(p[-1], collapse = " "), ""))
}
members <- cbind(members, split_name(members$name))
donations <- cbind(donations, split_name(donations$donor))
members$block <- double_metaphone(members$last)$primary
donations$block <- double_metaphone(donations$last)$primary
candidates <- merge(members, donations, by = "block")
candidates[, c("name", "donor", "block")]
#> name donor block
#> 1 Peter Kowalski Piotr Kowalsky KLSK
#> 2 Maria Gonzalez Mariah Gonzales KNSLS
#> 3 Hans Meyer Hans Maier MR
#> 4 Hans Meyer Helen Mayer MR
#> 5 Claire Dubois Clare Du Bois TPJoining on the primary code alone misses names whose primary codes
differ but whose secondary codes match, such as “Schmidt” and “Smith”.
As in the first example, it is more thorough to compare names with
sounds_like(), which checks all four combinations of codes.
With small tables every member can be compared with every donor. With
large ones, block on something broader first, such as the first letter
of the surname or a postcode.
all_pairs <- merge(members, donations, by = NULL)
all_pairs <- all_pairs[sounds_like(all_pairs$last.x, all_pairs$last.y) &
sounds_like(all_pairs$first.x, all_pairs$first.y), ]
all_pairs[, c("member_id", "name", "receipt", "donor", "amount")]
#> member_id name receipt donor amount
#> 1 M1 Jon Schmidt R1 John Smith 50
#> 7 M2 Maria Gonzalez R2 Mariah Gonzales 120
#> 13 M3 Peter Kowalski R3 Piotr Kowalsky 75
#> 19 M4 Claire Dubois R4 Clare Du Bois 200
#> 25 M5 Hans Meyer R5 Hans Maier 30Here merge(..., by = NULL) forms all combinations, which
is only sensible for small tables. To rank the candidate pairs, a string
distance such as the generalised Levenshtein distance from base R’s
adist() is a useful second opinion.
all_pairs$distance <- diag(adist(all_pairs$name, all_pairs$donor))
all_pairs[order(all_pairs$distance),
c("name", "donor", "distance")]
#> name donor distance
#> 7 Maria Gonzalez Mariah Gonzales 2
#> 25 Hans Meyer Hans Maier 2
#> 19 Claire Dubois Clare Du Bois 3
#> 13 Peter Kowalski Piotr Kowalsky 4
#> 1 Jon Schmidt John Smith 5Small distances suggest near-identical spellings. Larger ones, such as “Jon Schmidt” and “John Smith”, are worth a closer look by a person before the records are merged.
Genealogists, call centres and archivists often need to look up a
name that was heard rather than read. sounds_like() with a
single name on one side searches a whole register at once.
hits <- us_surnames[sounds_like("Meyer", us_surnames$surname), ]
head(hits[order(-hits$count), ], 10)
#> surname rank count
#> 18 Moore 18 724374
#> 136 Murray 136 184910
#> 183 Meyer 183 150895
#> 583 Mora 583 57464
#> 594 Morrow 594 56840
#> 757 Murillo 757 45558
#> 880 Mayer 880 39555
#> 935 Moyer 935 37050
#> 1391 Meier 1391 25486
#> 2106 Mohr 2106 17180The search finds the expected spellings, Meyer, Mayer, Meier, Maier and Moyer, but also Moore, Murray and others. Double Metaphone drops vowels, so all names with an M followed by an R sound fall together. Full-length codes keep longer names apart, but for short names a phonetic search gives a candidate list to review, not a final answer. Adding a string distance helps to put the closest spellings first.
By default the whole text is encoded as one string, exactly as the original algorithm does. Spaces are kept while encoding, and a few rules depend on them, for example the Spanish “San” or the Dutch and German “Van” and “Von”. The resulting code runs the words together.
With by_word = TRUE each word is encoded separately and
the codes are joined by spaces. This keeps the structure of the name and
lets you compare the parts on their own.
double_metaphone("Maria Gonzalez Lopez")
#> primary secondary
#> 1 MRKNSLSLPS MRKNSLSLPS
double_metaphone("Maria Gonzalez Lopez", by_word = TRUE)
#> primary secondary
#> 1 MR KNSLS LPS MR KNSLS LPSWhole-string codes are useful when the spacing of a name varies, as in “Du Bois” and “Dubois”. Word-by-word codes are useful when the order of the parts varies, as in many registers that record the family name first. Sorting the word codes makes the comparison independent of order.
double_metaphone() returns a data frame, so its result
can be bound to an existing table with cbind().
staff <- data.frame(name = c("Stephen Phillips", "Steven Philips"))
cbind(staff, double_metaphone(staff$name))
#> name primary secondary
#> 1 Stephen Phillips STFNFLPS STFNFLPS
#> 2 Steven Philips STFNFLPS STFNFLPSIn ‘dplyr’, a data frame returned inside mutate()
without a name is unpacked into separate columns.
Phonetic codes work best as one step in a matching process, not as the whole of it. The following points come up in almost every real project.
MR are shared by many names. Confirm candidate matches with
other fields, such as a date of birth, address or identification
number.max_length = 4, from by_word = TRUE and from
the default settings are not comparable with each other.Lawrence Philips designed both algorithms. The Double Metaphone rules
in this package are a translation of the C implementation by Maurice
Aubrey in the Perl module Text::DoubleMetaphone,
which includes fixes by Kevin Atkinson. The Metaphone rules and the
reference test data come from Apache Commons
Codec. The us_surnames data come from the United
States Census Bureau.
Philips, L. (1990). Hanging on the metaphone. Computer Language, 7(12), 38-43.
Philips, L. (2000). The double metaphone search algorithm. C/C++ Users Journal, 18(6), 38-43. Archived copy.