Question: I have a loop which is comparing street addresses. It then uses fuzzy matching to tokenise the addresses and compare the addresses. I have tried this both with fuzzywuzzy and rapidfuzz. It subsequently returns how close the match is. The aim is to try and take all my street addresses 30k or so and match a variation of the street address to a structured street address in my dataset. The end result would be a reference table with two columns:  Column A is the reference column address Column B is an address where the match is good enough to be associated with column A Column A can have many associated addresses.  I am not a huge python user but do know that for loops are the last resort for most problems (third answer). With that in mind, I have used for loops. However my loops will take approx 235 hours which is sub-optimal to say the least. I have created a reproducible example below. Can anyone see where i can make any tweaks? I have added a progress bar to give you an idea of the speed. You can increase the number of addresses by changing the line for _ in range(20): import pandas as pd from tqdm import tqdm from faker import Faker from rapidfuzz import process, fuzz # GENERATE FAKE ADDRESSES FOR THE REPRODUCIBLE EXAMPLE ----------------------------------------------- fake = Faker() fake_addresses = pd.DataFrame() for _ in range(20): # Generate fake address d = {'add':fake.address()} df = pd.DataFrame(data = [d]) # Append it to the addresses dataframe fake_addresses = pd.concat([fake_addresses, df]) fake_addresses = fake_addresses.reset_index(drop=True) # COMPARE ADDRESSES --------------------------------------------------------------------------------- # Here we are making a "dictionary" of the addresses where we use left side as a reference address # We use the right side as all the different variations of the address. The addresses have to be # 0% similar. Normally this is 95% similarity reference = fake_addresses['add'].drop_duplicates() ref_addresses = pd.DataFrame() # This takes a long time. I have added tqdm to show how long when the number of addresses is increased dramatically for address in tqdm(reference): for raw_address in reference: result = fuzz.token_sort_ratio(address, raw_address) d = {'reference_address': address, 'matched_address': raw_address, 'matched_result': result} df = pd.DataFrame(data = [d]) if len(df.index) > 0: filt = df['matched_result'] >= 0 df = df.loc[filt] ref_addresses = pd.concat([ref_addresses, df], ignore_index=True) else: ref_addresses = ref_addresses 

Answer: I would start by pre-calculating the sorted tokens once for each address so that you don't end up doing it n*n-1 times. This allows you to bypass the processor and avoid calling the sort method of the fuzzer. After that, I would take pandas out of the picture at least while doing these tests. This test runs through 1k faked address in about 2 seconds and 10k in about 4 1/2 minutes. At the moment if addr1 and addr2 are similar we record that both ways.: import faker import rapidfuzz import itertools import tqdm ## ----------------------- ## Generate some "fake" addresses ## apply the deault process once to each address ## sort the tokens once for each address ## ----------------------- faked_address_count = 1_000 make_fake = faker.Faker() fake_addresses = [make_fake.address() for _ in range(faked_address_count)] fake_addresses = { address: " ".join(sorted( x.strip() for x in rapidfuzz.utils.default_process(address).split(" ") if x.strip() )) for address in fake_addresses } ## ----------------------- ## ----------------------- ## Find similar addresses ## ----------------------- threshhold = 82 results = {} pairs = itertools.combinations(fake_addresses.items(), 2) for (addr1, processed_addr1), (addr2, processed_addr2) in tqdm.tqdm(pairs): similarity = rapidfuzz.fuzz.token_ratio( processed_addr1, processed_addr2, processor=None ) if similarity > threshhold: results.setdefault(addr1, []).append([addr2, similarity]) results.setdefault(addr2, []).append([addr1, similarity]) # also record 2 similar to 1? ## ----------------------- ## ----------------------- ## Print the final results ## ----------------------- import json print(json.dumps(results, indent=4)) ## -----------------------  generating a result like: { "PSC 0586, Box 8976\nAPO AE 52923": [ ["PSC 0586, Box 6535\nAPO AE 76148", 83.33333333333334] ], "PSC 0586, Box 6535\nAPO AE 76148": [ ["PSC 0586, Box 8976\nAPO AE 52923", 83.33333333333334] ], "USNS Brown\nFPO AE 20210": [ ["USNV Brown\nFPO AE 70242", 82.6086956521739] ], "USNV Brown\nFPO AE 70242": [ ["USNS Brown\nFPO AE 20210", 82.6086956521739] ] }  To create a version that more directly aligns with what I think your inputs and outputs might be, you can take a look at. This will compare 1k standard addresses to 10k test addresses in about 40 seconds.: import faker import rapidfuzz import itertools import tqdm ## ----------------------- ## Generate sets of standard addresses and test addresses ## ----------------------- make_fake = faker.Faker() standard_addresses = [make_fake.address() for _ in range(1_000)] test_addresses = [make_fake.address() for _ in range(2_000)] ## ----------------------- ## ----------------------- ## pre-process the addresses ## ----------------------- def address_normalizer(address): return " ".join(sorted( x.strip() for x in rapidfuzz.utils.default_process(address).split(" ") if x.strip() )) standard_addresses = {address: address_normalizer(address) for address in standard_addresses} test_addresses = {address: address_normalizer(address) for address in test_addresses} ## ----------------------- ## ----------------------- ## Create a list to hold our results ## ----------------------- results = [] ## ----------------------- ## ----------------------- ## Find similar addresses ## ----------------------- threshhold = 82 pairs = itertools.product(standard_addresses.items(), test_addresses.items()) for standard_address_kvp, test_address_kvp in tqdm.tqdm(pairs): similarity = rapidfuzz.fuzz.token_ratio( standard_address_kvp[1], test_address_kvp[1], processor=None ) if similarity > threshhold: results.append([standard_address_kvp[0], test_address_kvp[0]]) ## ----------------------- for pair in results: print(pair)  That will generate a result like: ['USS Collins\nFPO AE 04706', 'USNS Collins\nFPO AE 40687'] ['USS Miller\nFPO AP 15314', 'USS Miller\nFPO AP 91203'] ['Unit 4807 Box 9762\nDPO AP 67543', 'Unit 6542 Box 9721\nDPO AP 48806'] ['Unit 4807 Box 9762\nDPO AP 67543', 'Unit 9692 Box 6420\nDPO AP 46850']