-
Notifications
You must be signed in to change notification settings - Fork 0
Text 0.4.0 phonetics
Lodestar.Text 0.4.0. This page is frozen at that release. Read the current documentation for what
mainsays now. A link to a decision or a migration page followsmain, and leaves the archive.
Smith and Smyth are the same name. So are Robert and Rupert, to an ear if not to a
string comparison. A phonetic encoder reduces a word to a code that stands for how it sounds, so
two spellings of one name become one key and meet in an index.
Lodestar.Text.Phonetics holds three of them, one static class each, all with the same single
method: a word in, a code out.
flowchart TD
A["What is the job?"] --> B{"Matching an index or a<br/>record system that already exists?"}
B -->|yes| C["Soundex — use whatever built it"]
B -->|no| D{"Names, or ordinary words?"}
D -->|"names"| E["Nysiis"]
D -->|"words, and English spelling<br/>is what is misleading you"| F["Metaphone"]
The three are pinned on the same 402-word corpus, which makes the difference between them countable rather than a matter of reputation:
| encoder | code | distinct codes over 402 words | words sharing a code |
|---|---|---|---|
Soundex |
one letter and three digits, always 4 characters | 347 | 101 |
Nysiis |
letters, 1 to 11 characters | 395 | 13 |
Metaphone sits between them and is measured on its own corpus of 123
real words — 117 distinct codes, 1 to 6 characters — for the reason
decision 0007 gives.
That column is the whole trade-off. Soundex merges aggressively, so it finds spellings you did not think of and also returns names that have nothing to do with the query. NYSIIS barely merges at all, so what it returns is nearly always right and the one you wanted may not be in it.
| word | Soundex | Metaphone | NYSIIS |
|---|---|---|---|
Robert |
R163 |
RBRT |
RABAD |
Rupert |
R163 |
RPRT |
RAPAD |
Knight |
K523 |
NT |
NAGT |
Wright |
W623 |
RT |
WRAGT |
Thomas |
T520 |
0MS |
TAN |
Robert and Rupert are the textbook Soundex collision: the b/p distinction is exactly what
its digit table throws away, and the two other encoders keep it.
Knight and Wright are the opposite case. Metaphone models English spelling: it knows the
k in Knight and the w in Wright are silent, and drops them, so the code starts on the
sound the word starts with. Soundex and NYSIIS both key on the written first letter and file the
two words under K and W. If your data is English words rather than surnames, that difference
is usually the one that matters.
Thomas shows Metaphone's alphabet: 0 is "th", and X — as in Christina → XRSTN — is
"sh". A code is not readable, and is not meant to be.
- A code is a key, not a pronunciation. Compare codes to each other; never show one, and never try to read a word back out of one.
- They are English heuristics. None is Unicode-aware, and none has anything reliable to say about a name that is not English in origin — which includes many of the names a real dataset holds.
- Non-letters are ignored, and the empty string encodes to the empty string.
-
A
nullword is refused, the same rule the stemmers next door apply — decision 0042.Soundex.Encodeshows it. - Each is a static class with no state, so all three are safe to call from any number of threads at once.
| Type | What it is |
|---|---|
Metaphone |
English spelling modelled as sound; silent letters dropped. |
Nysiis |
The finest of the three, built for names. |
Soundex |
The 1918 classic: one letter, three digits, and it merges a lot. |
-
Python → C# equivalence — the
jellyfishcall each of these replaces. -
Fuzz.Ratio— the other way to decide two names match, on spelling rather than sound. -
decisions/0007— why Metaphone is pinned on real words only.
- 0001-target-framework
- 0002-unicode-comparison-unit
- 0003-provenance-and-licensing
- 0004-levenshtein-myers-backlog
- 0005-hamming-jellyfish-divergence
- 0006-ratcliff-autojunk
- 0007-metaphone-scope
- 0008-italian-enza-nltk-divergence
- 0009-sample-consumes-a-local-feed
- 0010-stop-word-list-provenance
- 0011-persistence-format
- 0012-per-package-versioning
- 0013-sentencepiece-parity-scope
- 0014-precompiled-normalizer
- 0015-sonar-rules-in-the-build
- 0016-metrics-package-placement
- 0017-bpe-parity-scope
- 0018-multiclass-roc-auc-parallelism-is-opt-in
- 0019-the-net-analysers-run-in-the-build-too
- 0020-normalize-is-a-projection-not-a-parameter
- 0021-multioutput-is-a-method-not-an-enum
- 0022-added-token-matching-flags
- 0023-byte-level-decode-substitutes
- 0024-weighted-median-averages-within-scikit-learns-epsilon
- 0025-quickselect-replaces-a-full-sort-for-the-median
- 0026-r2-and-explainedvariance-split-their-undefined-cases-differently
- 0027-r2-and-explainedvariance-vectorize-only-a-single-output
- 0028-log1p-is-kahans-identity-not-math-log-1-plus-x
- 0029-balanced-accuracy-adjusted-is-left-to-ieee-754-at-the-edge
- 0030-cohen-kappa-keeps-scikit-learns-expected-matrix-orientation
- 0031-nosamplecorrect-mirrors-numpys-float64-upcast
- 0032-fbeta-substitutes-tp-predicted-and-support-algebraically
- 0033-compensated-sum-is-neumaiers-variant
- 0034-dropout-is-refused-for-want-of-a-user
- 0035-a-null-pre-split-is-removed-with-invert-not-isolated
- 0036-a-member-may-ship-without-an-oracle-if-it-says-so
- 0037-the-guards-run-before-the-commit
- 0038-the-gate-confronts-an-exception-tag-with-the-page-that-documents-it
- 0039-mutual-information-returns-zero-on-an-empty-input
- 0040-a-curve-is-a-sealed-class-per-curve
- 0041-one-sample-file-per-public-class
- 0042-phonetic-encoders-refuse-a-null-word
- 0043-the-equality-table-is-sized-to-the-pattern
- 0044-compression-belongs-to-the-caller
- 0045-a-console-call-carries-its-reason-on-the-line
- 0046-check-adr-immutable-runs-in-ci-only
- 0047-one-gate-per-kernel-not-one-per-alphabet
- 0048-the-gate-depends-on-the-kernel-and-the-alphabet
- 0049-two-gates-per-kernel-tested-where-the-width-is-known
- 0050-the-sentencepiece-bpe-lineage-stays-a-bpe-model
- benchmark_latest
- decisions
- equivalence
- matplotlib
- migration
- nightly_run
- numpy
- pandas
- performance
- pytorch
- seaborn
- sklearn
- statsmodels