PolyFuzz Documentation

repository·master·Indexed 21 days ago

https://github.com/maartengr/polyfuzz

A unified framework for fuzzy string matching, string grouping, and evaluation. PolyFuzz integrates multiple techniques into a single API, including edit distance, TF-IDF, and word embeddings from FastText, GloVe, Transformers, SBERT, Flair, Gensim, Spacy, and USE. It supports comparing multiple models simultaneously, visualizing precision-recall curves, and implementing custom matchers via the BaseMatcher class.

Tokens
9.4K
Snippets
38
Records
39
Agent score
68%

What's inside PolyFuzz

  1. Manage model state with fit and transform

    master

    When building custom models that involve expensive computations (like generating embeddings), you can use the fit and transform pattern to avoid redundant calculations.

    • fit(from_list): Use this to pre-calculate and store state (e.g., embeddings) for the from_list.
    • transform(to_list): Use this to match a new list against the state already stored during the fit step.

    To support this, your custom match method should handle a re_train parameter. When re_train=False (typically during a transform call), the model should leverage previously stored attributes (like self.embeddings_to) instead of re-calculating them.

    # Example pattern for stateful models
    class SentenceEmbeddings(BaseMatcher):
        def __init__(self, model_id):
            super().__init__(model_id)
            self.embeddings_to = None
    
        def match(self, from_list, to_list, re_train=True) -> pd.DataFrame:
            # 1. Always calculate embeddings for the 'from' side
            embeddings_from = self.embedding_model.encode(from_list)
    
            # 2. Use stored embeddings if re_train is False
            if not re_train:
                embeddings_to = self.embeddings_to
            else:
                embeddings_to = self.embedding_model.encode(to_list)
    
            # 3. Store for future transform calls
            self.embeddings_to = embeddings_to
            
            # ... perform matching ...
    
    # Usage pattern
    model = PolyFuzz(custom_matcher).fit(from_list)
    results = model.transform(to_list)
  2. How self-matching works in PolyFuzz

    master

    When you pass a single list to the .match() method instead of two separate lists, PolyFuzz performs a self-match. In this mode, the model compares the strings in the list against every other string in that same list. To prevent trivial results, PolyFuzz automatically ignores any comparison a string has with itself; otherwise, every string would simply map to itself with a perfect score.

    # Example of self-matching a single list
    from polyfuzz import PolyFuzz
    from polyfuzz.datasets import load_company_names
    
    data = load_company_names()
    model = PolyFuzz("TF-IDF").match(data)
  3. Quick Start: Match two lists of strings

    master

    To perform a simple fuzzy match between two lists using the TF-IDF method, instantiate PolyFuzz with the method name and call .match(from_list, to_list). You can access the results using .get_matches().

    Notes:

    • To compare distances within a single list, use model.match(from_list).
    • Common quick-access methods include "TF-IDF", "EditDistance", and "Embeddings" (which uses FastText).
    from polyfuzz import PolyFuzz
    
    from_list = ["apple", "apples", "appl", "recal", "house", "similarity"]
    to_list = ["apple", "apples", "mouse"]
    
    model = PolyFuzz("TF-IDF")
    model.match(from_list, to_list)
    
    # Access results
    matches = model.get_matches()
    print(matches)
  4. Perform quick string matching with PolyFuzz

    master

    To perform a quick comparison between two lists of strings, instantiate PolyFuzz with a matching method and call .match().

    Supported quick-access methods include:

    • "TF-IDF" (Character n-grams)
    • "EditDistance" (Levenshtein distance)
    • "Embeddings" (FastText English)

    After matching, use .get_matches() to retrieve a DataFrame containing the From string, the To string, and the Similarity score.

    from polyfuzz import PolyFuzz
    
    from_list = ["apple", "apples", "appl", "recal", "house", "similarity"]
    to_list = ["apple", "apples", "mouse"]
    
    # Instantiate with method and match lists
    model = PolyFuzz("TF-IDF").match(from_list, to_list)
    
    # Access results
    matches = model.get_matches()
    print(matches)
  5. Use multiple models simultaneously

    master

    You can pass a list of different matchers to PolyFuzz to run multiple matching strategies at once.

    When using multiple models:

    1. The .match() method returns a dictionary of DataFrames, where each key is the model_id of the matcher.
    2. To access results for a specific model, call get_matches(model_id).
    3. You can visualize the comparison using visualize_precision_recall(kde=True).
    from polyfuzz import PolyFuzz
    from polyfuzz.models import EditDistance, TFIDF, Embeddings
    from flair.embeddings import TransformerWordEmbeddings
    
    from_list = ["apple", "apples", "appl", "recal", "house", "similarity"]
    to_list = ["apple", "apples", "mouse"]
    
    bert = TransformerWordEmbeddings('bert-base-multilingual-cased')
    bert_matcher = Embeddings(bert, min_similarity=0, model_id="BERT")
    tfidf_matcher = TFIDF(min_similarity=0)
    edit_matcher = EditDistance()
    
    matchers = [bert_matcher, tfidf_matcher, edit_matcher]
    models = PolyFuzz(matchers).match(from_list, to_list)
    
    # Access specific model results
    bert_results = models.get_matches("BERT")
    
    # Visualize comparison
    models.visualize_precision_recall(kde=True)
  6. Implement a custom model using BaseMatcher

    master

    To use a custom similarity or distance measure not provided by PolyFuzz, create a class that inherits from polyfuzz.models.BaseMatcher.

    Your class must implement a match method with the following signature: match(self, from_list, to_list, **kwargs) -> pd.DataFrame

    Steps for implementation:

    1. Calculate distances: Compute the similarity/distance between every pair in from_list and to_list.
    2. Get best matches: For each item in from_list, find the item in to_list with the highest similarity score.
    3. Prepare dataframe: Return a pandas.DataFrame with columns From, To, and Similarity.
    import numpy as np
    import pandas as pd
    from rapidfuzz import fuzz
    from polyfuzz.models import BaseMatcher
    
    class MyModel(BaseMatcher):
        def match(self, from_list, to_list, **kwargs):
            # Calculate distances
            matches = [[fuzz.ratio(from_string, to_string) / 100 
                       for to_string in to_list] for from_string in from_list]
            
            # Get best matches
            mappings = [to_list[index] for index in np.argmax(matches, axis=1)]
            scores = np.max(matches, axis=1)
            
            # Prepare dataframe
            matches = pd.DataFrame({'From': from_list,
                                    'To': mappings, 
                                    'Similarity': scores})
            return matches
    
    # Usage
    from_list = ["apple", "apples", "appl"]
    to_list = ["apple", "apples", "mouse"]
    
    custom_matcher = MyModel()
    model = PolyFuzz(custom_matcher).match(from_list, to_list)
  7. Implement a custom matcher by subclassing BaseMatcher

    master

    To use a custom similarity or distance measure, create a class that inherits from polyfuzz.models.BaseMatcher and implements the match method.

    The match method must:

    1. Accept two lists of strings (from_list, to_list).
    2. Return a pandas.DataFrame with exactly three columns: From, To, and Similarity.
    import numpy as np
    import pandas as pd
    from polyfuzz.models import BaseMatcher
    
    class MyModel(BaseMatcher):
        def match(self, from_list, to_list):
            # 1. Calculate distances/similarities
            # 2. Get best matches and scores
            # 3. Prepare dataframe
            matches = pd.DataFrame({
                'From': from_list,
                'To': mappings,
                'Similarity': scores
            })
            return matches
    
    # Usage
    custom_matcher = MyModel()
    model = PolyFuzz(custom_matcher).match(from_list, to_list)
  8. Use a custom grouper with PolyFuzz

    master

    By default, PolyFuzz uses a TF-IDF implementation with single linkage to group mapped strings. However, you can replace this with any supported model or a custom model by passing it to the .group() method of a PolyFuzz instance after performing a match.

    To use a different model (such as EditDistance) for grouping, instantiate the desired model and pass it to model.group().

    from polyfuzz import PolyFuzz
    from polyfuzz.models import EditDistance
    
    from_list = ["apple", "apples", "appl", "recal", "house", "similarity"]
    to_list = ["apple", "apples", "mouse"]
    
    # 1. Perform the initial matching
    model = PolyFuzz("TF-IDF").match(from_list, to_list)
    
    # 2. Define a custom grouper (e.g., EditDistance)
    base_edit_grouper = EditDistance(n_jobs=1)
    
    # 3. Apply the custom grouper to the matches
    model.group(base_edit_grouper)
  9. Use fit and transform for production workflows

    master

    For production environments where you need to match incoming (unseen) strings against a known set of correct strings, use the fit and transform pattern. This is more efficient than .match() because the representations (like TF-IDF) are calculated once during fit and reused during transform.

    1. fit(train_words): Calculates and stores the representations for the reference list.
    2. transform(unseen_words): Maps the new words to the reference list using the stored representations.
    from polyfuzz import PolyFuzz
    
    train_words = ["apple", "apples", "appl", "recal", "house", "similarity"]
    unseen_words = ["apple", "apples", "mouse"]
    
    # Fit on the reference list
    model = PolyFuzz("TF-IDF")
    model.fit(train_words)
    
    # Transform incoming words
    results = model.transform(unseen_words)
  10. Install PolyFuzz and backend dependencies

    master

    Install the core library via pip:

    pip install polyfuzz

    Depending on your requirements for language models and backends, you can install additional dependencies using the following extras:

    • SBERT: pip install polyfuzz[sbert]
    • Flair: pip install polyfuzz[flair]
    • Gensim: pip install polyfuzz[gensim]
    • SpaCy: pip install polyfuzz[spacy]
    • USE (Universal Sentence Encoder): pip install polyfuzz[use]
    • Fast Cosine Similarity: To speed up comparisons and decrease memory usage when using embedding models, install sparse_dot_topn via pip install polyfuzz[fast].
    pip install polyfuzz
    # Optional extras:
    pip install polyfuzz[sbert]
    pip install polyfuzz[flair]
    pip install polyfuzz[gensim]
    pip install polyfuzz[spacy]
    pip install polyfuzz[use]
    pip install polyfuzz[fast]
  11. Compare multiple models simultaneously

    master

    You can pass a list of model objects to PolyFuzz to compare their performance. When multiple models are used, get_matches() returns a dictionary of DataFrames. You can retrieve a specific model's results using its model_id.

    from polyfuzz import PolyFuzz
    from polyfuzz.models import EditDistance, TFIDF, Embeddings
    from flair.embeddings import TransformerWordEmbeddings
    
    # Setup different models
    embeddings = TransformerWordEmbeddings('bert-base-multilingual-cased')
    bert = Embeddings(embeddings, min_similarity=0, model_id="BERT")
    tfidf = TFIDF(min_similarity=0)
    edit = EditDistance()
    
    # Instantiate PolyFuzz with a list of models
    string_models = [bert, tfidf, edit]
    model = PolyFuzz(string_models)
    model.match(from_list, to_list)
    
    # Access results for a specific model by its ID
    bart_results = model.get_matches("BERT")
    
    # Visualize comparison
    model.visualize_precision_recall(kde=True)
  12. Install PolyFuzz

    master

    Install the core package via pip:

    pip install polyfuzz

    Depending on your required backends (transformers, embeddings, etc.), you may need to install additional dependencies:

    • SBERT: pip install polyfuzz[sbert]
    • Flair: pip install polyfuzz[flair]
    • Gensim: pip install polyfuzz[gensim]
    • Spacy: pip install polyfuzz[spacy]
    • USE: pip install polyfuzz[use]

    To speed up cosine similarity comparisons and reduce memory usage when using embedding models, install the fast extra:

    pip install polyfuzz[fast]

    Troubleshooting sparse_dot_topn: If you encounter installation issues with the fast extra, try installing it via conda first:

    conda install -c conda-forge sparse_dot_topn