-
Notifications
You must be signed in to change notification settings - Fork 0
Text similarity
How much do two pieces of text have in common, when where it appears does not matter? Every type on this page answers that. They cut each input into q-grams, count them as a bag, and divide the grams the two share by something — and the something is the only place they disagree.
Two conventions run through the whole namespace, and knowing them saves reading every entry.
- A q-gram is a run of
qvalconsecutive characters, andqvalis1by default, which makes the grams individual characters. Repeats count:"apple"holdsptwice, and a bag that holds it once shares only one of them. This istextdistance's reading, and the reason a caller who wants words rather than characters has to split and compare the pieces themselves. - Every member takes a
TextElementsaying what counts as one character. The default,TextElement.Utf16Unit, is .NET's own unit and agrees with Python for every character in the Basic Multilingual Plane; outside it — emoji, rare ideographs — one character is two UTF-16 units and the two disagree on purpose. PassTextElement.CodePointfor Python's answer. The reasoning is in decision 0002.
Comparing text position by position — how many edits turn one string into the other, whether
two names are spelled alike — is a different question, answered by
Lodestar.Text.Distances and not by anything here.
Every measure on this page divides |A∩B|, the grams the two bags share, by a denominator of its
own. That is the entire difference between them, and it is enough to predict which will disagree
with which.
| Type | Denominator | What that makes it do |
|---|---|---|
Jaccard |
|A∪B| |
Charges for every gram either side holds alone. The strictest of the five. |
SorensenDice |
(|A| + |B|) / 2 |
Counts shared grams twice, so it always reads higher than Jaccard. |
Overlap |
min(|A|, |B|) |
Ignores the size gap entirely: 1 whenever one bag is contained in the other. |
Cosine |
√(|A| · |B|) |
Between the other three — the geometric mean punishes a size gap, but gently. |
Tversky |
|A∩B| + α·|A\B| + β·|B\A| |
The general form the others are cases of, and the only asymmetric one. |
Two consequences are worth having before you choose.
Jaccard and SorensenDice rank identically. Dice = 2·Jaccard / (1 + Jaccard), which rises
with Jaccard over the whole of [0, 1], so sorting candidates by one produces the order the
other would. Choosing between them changes the number a threshold has to be set against, never
which match wins.
Tversky is the other four in disguise. α = β = 1 is Jaccard, α = β = 0.5 is
SorensenDice, and the asymmetric settings are what the other four cannot express: α = 1, β = 0
charges only for what the first input holds alone, which asks "is A contained in B" rather than
"do A and B agree".
flowchart TD
A["Comparing two bags of grams"] --> B{"Are the two roughly<br/>the same length?"}
B -->|yes| C["Any of them agree closely.<br/>Jaccard is the usual default"]
B -->|no| D{"Is the shorter one supposed to be<br/>a fragment of the longer?"}
D -->|yes, and a fragment<br/>should score full marks| E["Overlap"]
D -->|yes, but the extra material<br/>should still cost something| F["Cosine"]
D -->|no, the gap is real<br/>disagreement| G{"Do the two sides deserve<br/>the same penalty?"}
G -->|yes| H["Jaccard, or SorensenDice<br/>for a gentler number"]
G -->|no — one direction matters| I["Tversky, with α ≠ β"]
| Type | What it measures |
|---|---|
Cosine |
Shared grams over the geometric mean of the two bag sizes — the Ochiai coefficient. |
Jaccard |
Shared grams over the grams either side holds at all. |
Overlap |
Shared grams over the smaller bag, so containment scores 1. |
SorensenDice |
Shared grams counted twice, over the two bag sizes added. |
Tversky |
Shared grams against the two sides' surpluses, weighted separately. |
Two empty inputs share nothing and disagree about nothing, and all five answer 1 — the reference
libraries' choice, kept here so a ported comparison does not change at the boundary. One empty
input against a non-empty one answers 0 everywhere except Tversky,
whose weights can make the denominator vanish; its entry says when.
See also — distances for position-sensitive comparison, the
Python equivalence table, and
Lodestar.Fuzzy for scorers that do the token splitting for you.
- 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