一尘不染

Python中的模糊字符串匹配

algorithm

我有2个超过一百万个名称的列表,它们的命名约定略有不同。目的是匹配具有95%置信度的逻辑的相似记录。

我知道有些库可以利用,例如Python中的FuzzyWuzzy模块。

但是,就处理而言,将一个列表中的每个字符串与另一个列表进行比较似乎会占用过多资源,在这种情况下,这似乎需要将100万乘以另一百万迭代次数。

还有其他更有效的方法来解决此问题吗?

更新:

因此,我创建了一个存储桶功能并应用了一个简单的规范化功能,即删除空格,符号并将值转换为小写字母等。

for n in list(dftest['YM'].unique()):
    n = str(n)
    frame = dftest['Name'][dftest['YM'] == n]
    print len(frame)
    print n
    for names in tqdm(frame):
            closest = process.extractOne(names,frame)

通过使用pythons
pandas,数据被加载到按年份分组的较小的存储桶中,然后使用FuzzyWuzzy模块process.extractOne获得最佳匹配。

结果仍然有些令人失望。在测试过程中,上面的代码在仅包含5000个名称的测试数据帧上使用,并且占用了几乎一个小时的时间。

测试数据被分割。

  • 名称
  • 出生年份的月份

我正在按桶比较它们的YM在同一桶中。

问题可能是因为我正在使用FuzzyWuzzy模块吗?感谢任何帮助。


阅读 688

收藏
2020-07-28

共1个答案

一尘不染

这里有几种优化级别可以将这个问题从O(n ^ 2)转变为较小的时间复杂度。

  • 预处理 :在第一遍中对您的列表进行排序,为每个字符串创建一个输出映射,它们对于映射的键可以是规范化的字符串。规范化可能包括:

    • 小写转换,
    • 没有空格,去除特殊字符,
    • 如果可能,将unicode转换为ascii等效项,请使用unicodedata.normalizeunidecode模块)

这将导致"Andrew H Smith""andrew h. smith""ándréw h. smith"产生相同的密钥"andrewhsmith",和你的万余名组将减少到一个较小的一套独特的/类似的分组名。

您可以使用此utlity方法来规范化您的字符串(尽管不包括unicode部分):

def process_str_for_similarity_cmp(input_str, normalized=False, ignore_list=[]):
    """ Processes string for similarity comparisons , cleans special characters and extra whitespaces
        if normalized is True and removes the substrings which are in ignore_list)
    Args:
        input_str (str) : input string to be processed
        normalized (bool) : if True , method removes special characters and extra whitespace from string,
                            and converts to lowercase
        ignore_list (list) : the substrings which need to be removed from the input string
    Returns:
       str : returns processed string
    """
    for ignore_str in ignore_list:
        input_str = re.sub(r'{0}'.format(ignore_str), "", input_str, flags=re.IGNORECASE)

    if normalized is True:
        input_str = input_str.strip().lower()
        #clean special chars and extra whitespace
        input_str = re.sub("\W", "", input_str).strip()

    return input_str
  • 现在,如果相似的字符串的规范化密钥相同,则它们将已经位于同一存储桶中。

  • 为了进行进一步的比较, 您将只需要比较键,而不是名称 。例如 andrewhsmithandrewhsmeeth,因为除了上面进行的标准化比较之外,名称的这种相似性还需要模糊字符串匹配。

  • Bucketing您是否真的需要将5个字符的密钥与9个字符的密钥进行比较,看看是否匹配95% ?你不可以。因此,您可以创建匹配字符串的存储桶。例如,将5个字符名称与4-6个字符名称匹配,将6个字符名称与5-7个字符匹配,等等。对于大多数实际匹配而言,字符键的n + 1,n-1个字符限制是一个不错的选择。

  • 开始比赛 :名字的大部分变化都会有相同的第一个字符的标准化格式(例如Andrew H Smithándréw h. smithAndrew H. Smeeth生成密钥andrewhsmithandrewhsmithandrewhsmeeth分别,他们通常不会在第一个字符不同,所以你可以开始键跑匹配。a其他键开头为a,并且位于长度段之内,这将大大减少您的匹配时间,因此无需匹配关键字andrewhsmith,因此bndrewhsmith,几乎不会出现带有首字母的名称变化。

然后,您可以使用此方法(或FuzzyWuzzy模块)的内容来查找字符串相似度百分比,可以排除jaro_winkler或difflib中的一个来优化速度和结果质量:

def find_string_similarity(first_str, second_str, normalized=False, ignore_list=[]):
    """ Calculates matching ratio between two strings
    Args:
        first_str (str) : First String
        second_str (str) : Second String
        normalized (bool) : if True ,method removes special characters and extra whitespace
                            from strings then calculates matching ratio
        ignore_list (list) : list has some characters which has to be substituted with "" in string
    Returns:
       Float Value : Returns a matching ratio between 1.0 ( most matching ) and 0.0 ( not matching )
                    using difflib's SequenceMatcher and and jellyfish's jaro_winkler algorithms with
                    equal weightage to each
    Examples:
        >>> find_string_similarity("hello world","Hello,World!",normalized=True)
        1.0
        >>> find_string_similarity("entrepreneurship","entreprenaurship")
        0.95625
        >>> find_string_similarity("Taj-Mahal","The Taj Mahal",normalized= True,ignore_list=["the","of"])
        1.0
    """
    first_str = process_str_for_similarity_cmp(first_str, normalized=normalized, ignore_list=ignore_list)
    second_str = process_str_for_similarity_cmp(second_str, normalized=normalized, ignore_list=ignore_list)
    match_ratio = (difflib.SequenceMatcher(None, first_str, second_str).ratio() + jellyfish.jaro_winkler(unicode(first_str), unicode(second_str)))/2.0
    return match_ratio
2020-07-28