Skip to content

Add robust similarity metrics and normalization to compare.text - #1144

Open
mborodii-prog wants to merge 2 commits into
mainfrom
feature/1134-enhancement-add-robust-similarity-metrics-and-normalization-to-comparetext
Open

Add robust similarity metrics and normalization to compare.text#1144
mborodii-prog wants to merge 2 commits into
mainfrom
feature/1134-enhancement-add-robust-similarity-metrics-and-normalization-to-comparetext

Conversation

@mborodii-prog

Copy link
Copy Markdown
Contributor

Add robust similarity metrics and normalization to compare.text

Closes #1134

Summary

Adds method: similarity to compare.text — a symmetric, 0.01.0 bounded similarity score for comparing product descriptions that may be reordered, differently punctuated, or contain typos — while leaving difference/intersection/overlap output shapes unchanged for backward compatibility.

What's in this PR

  • New method: similarity with three metrics (wrangles/compare.py):
    • token_sort (default) — ignores token order, keeps duplicates, penalizes missing/extra content
    • token_set — ignores token order and duplicates; a shorter description fully contained in a longer one can score 1.0
    • damerau_levenshtein — character edit distance where an adjacent transposition counts as one edit
    • Built on RapidFuzz (fuzz.token_sort_ratio, fuzz.token_set_ratio, distance.DamerauLevenshtein.normalized_similarity) — new dependency: rapidfuzz>=3.0,<4.0
  • Standard normalization pipeline (normalize_similarity_text): NFKC Unicode normalization → Unicode-aware case folding → punctuation/separators converted to spaces (not concatenated) → whitespace collapsed. Deliberately does not equate AB-12/AB12, doesn't touch units/numbers, doesn't do synonym/acronym expansion.
  • Null handling: missing/blank input returns null, never stringified into "nan"/"None" — checked before any str() conversion, unlike the legacy .astype(str) path.
  • case_sensitive deprecated: still accepted (won't break old recipes), value is ignored, logs a deprecation warning only when explicitly supplied. true/false/omitted now produce identical (always case-insensitive) results.
  • Bug fixes required to make the above correct, not scope creep:
    • Fixed contrast()/overlap() casing: previously, case-insensitive matching also lowercased the output in difference/overlap. Now matching is case-insensitive but original casing is preserved in the output (needed once case-insensitive became the permanent default).
    • Fixed the quoted decimal_places bug (int(decimal_places)'s result was discarded, causing a later TypeError).
  • Legacy difference/intersection/overlap (including include_ratio) are unchanged in behavior/output shape — all pre-existing tests pass unmodified.

Tests

35 pre-existing tests pass unchanged + 23 new tests in tests/recipes/wrangles/test_compare.py: symmetry, score bounds, exact-match, reordered/duplicate/subset tokens, conflicting attributes, each Damerau-Levenshtein edit type individually (insertion/deletion/substitution/transposition), anagram guard, punctuation (AB-12 vs AB12), Unicode normalization, nulls (both library-level None and recipe-level non-stringified), the two regression bugs, and the case_sensitive deprecation warning/equivalence.

@mborodii-prog

Copy link
Copy Markdown
Contributor Author

@ebhills @thomasstvr PR was trested in QA to test it you can use image: dev-1.20.0rc59 . test file
compare_text_data.xlsx
recipe:
compare_text_recipe.wrgl.yml



def test_recipe_by_production_version():
def test_recipe_by_production_version(mocker):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mborodii-prog why are these tests being mocked? It looks like they should be able to be real tests.

It does look like the model id's do not exist, but they shouldn't be mocked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests are mocked because after latest cleaning models list some of the models are deleted

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thomasstvr I can create new model and update model_ids if it's better

empty_b=empty_b,
all_empty=all_empty,
case_sensitive=case_sensitive,
case_sensitive=False,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing case_sensitive?

Removing it and reversing the default to false are both breaking changes.

@mborodii-prog mborodii-prog Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thomasstvr This is in requirements of issue, but we still support old syntax. case_sensitive remains accepted but is ignored, produces a deprecation warning when supplied, and is removed from new examples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ENHANCEMENT] Add robust similarity metrics and normalization to compare.text

2 participants