Here is a way to do this differently. You will have to test it to see if it is actually faster (since I don't have an example of 17k different almost matching ways to write countries).
First note that you can extract the underlying data used by pycountry
. On my machine the file containing the data on all existing countries can be found at /usr/local/lib/python3.6/dist-packages/pycountry/databases/iso3166-1.json
. Depending on your OS this will be in a different place, but the actual filename should be the same.
Next, there exists a tool that can directly merge dataframes on certain columns, using fuzzy search, called fuzzy_pandas
. With it you can do something like this:
import json
import pandas as pd
import fuzzy_pandas as fpd
with open("/tmp/iso3166-1.json") as f:
countries = pd.DataFrame.from_dict(json.load(f)["3166-1"])
countries = countries.drop(columns=["alpha_2", "numeric"])
countries = countries.fillna(method="ffill", axis=1)
data = pd.DataFrame({"country": ["Samos", "Germanz"]})
fpd.fuzzy_merge(data, countries, left_on=["country"], right_on=["name"], method="levenshtein")
# country alpha_3 name official_name common_name
# 0 Samos WSM Samoa Independent State of Samoa Independent State of Samoa
# 1 Germanz DEU Germany Federal Republic of Germany Federal Republic of Germany
You might have to try which column to use for right_on
. You might have to use all of them in separate calls to pfd.fuzzy_merge
and then decide what to do with the ones that give different results. In the implementation of pycountry
the name and official name are used and given a certain weighting factor:
# Prio 3: partial matches on country names
for candidate in countries:
# Higher priority for a match on the common name
for v in [candidate._fields.get('name'),
candidate._fields.get('official_name')]:
if v is None:
continue
v = remove_accents(v.lower())
if query in v:
# This prefers countries with a match early in their name
# and also balances against countries with a number of
# partial matches and their name containing 'new' in the
# middle
add_result(candidate, max([5, 30-(2*v.find(query))]))
break