Context
This is a case of approximate string matching or fuzzy matching. There is good materials and libraries about that.
There are different libraries and ways to cover this. I will restrict to relatively simple libraries
Some cool libraries:
from fuzzywuzzy import process
import pandas as pd
import string
First Part
Let's place the data to play around. I try to reproduce the above example, hope it is fine.
# Set up dataframe
d = {'originals': [["Water","PEG-60 Hydrogenated Castor Oil"],
["PEG-60 Hydrnated Castor Oil"],
["wter"," PEG-60 Hydrnated Castor Oil"],
['Vitamin E']],
'correct': [["Water","PEG-60 Hydrogenated Castor Oil"],
["PEG-60 Hydrogenated Castor Oil"],
['Water', 'PEG-60 Hydrogenated Castor Oil'],
['Tocopherol (Vitamin E)']]}
df = pd.DataFrame(data=d)
print(df)
originals correct
0 [Water, PEG-60 Hydrogenated Castor Oil] [Water, PEG-60 Hydrogenated Castor Oil]
1 [PEG-60 Hydrnated Castor Oil] [PEG-60 Hydrogenated Castor Oil]
2 [wter, PEG-60 Hydrnated Castor Oil] [Water, PEG-60 Hydrogenated Castor Oil]
3 [Vitamin E] [Tocopherol (Vitamin E)]
From the above we have the statement of the problem: we have some original wording and want to change it.
Which are the correct options for us:
strOptions = ['Water', "Tocopherol (Vitamin E)",
"Vitamin D", "PEG-60 Hydrogenated Castor Oil"]
This functions are going to help us. I try to document them well.
def function_proximity(str2Match,strOptions):
"""
This function help to get the first guess by similiarity.
paramters
---------
str2Match: string. The string to match.
strOptions: list of strings. Those are the possibilities to match.
"""
highest = process.extractOne(str2Match,strOptions)
return highest[0]
def check_strings(x, strOptions):
"""
Takes a list of string and give you a list of string best matched.
:param x: list of string to link / matched
:param strOptions:
:return: list of string matched
"""
list_results = []
for i in x:
i=str(i)
list_results.append(function_proximity(i,strOptions))
return list_results
Let's apply to the dataframe:
df['solutions_1'] = df['originals'].apply(lambda x: check_strings(x, strOptions))
Let's check the results, by comparing the columns.
print(df['solutions_1'] == df['correct'])
0 True
1 True
2 True
3 True
dtype: bool
As you can see the solution work in the four cases.
Second Part
Problem example solution:
You have Water Vtamin D should become Water, Vitamin D.
Let's create a list of valid words.
list_words = []
for i in strOptions:
print(i.split(' '))
list_words = list_words + i.split(' ')
# Lower case and remove punctionation
list_valid_words = []
for i in list_words:
i = i.lower()
list_valid_words.append(i.translate(str.maketrans('', '', string.punctuation)))
print(list_valid_words)
['water', 'tocopherol', 'vitamin', 'e', 'vitamin', 'd', 'peg60', 'hydrogenated', 'castor', 'oil']
That if the list of words that are valid.
def remove_puntuation_split(x):
"""
This function remove puntuation and split the string into tokens.
:param x: string
:return: list of proper tokens
"""
x = x.lower()
# Remove all puntuation
x = x.translate(str.maketrans('', '', string.punctuation))
return x.split(' ')
tokens = remove_puntuation_split(x)
# Clean tokens
clean_tokens = [function_proximity(x,list_valid_words) for x in tokens]
# Matched tokens with proper selection
tokens_clasified = [function_proximity(x,strOptions) for x in tokens]
# Removed repeated
tokens_clasified = list(set(tokens_clasified))
print(tokens_clasified)
['Vitamin D', 'Water']
That is as required initially.
However those could fail a bit, particularly when Vitamin E and D are combined.
References