python: fuzzywuzzy学习笔记

本文内容基本来源于这篇文章,这里存一下我的笔记:

https://chairnerd.seatgeek.com/fuzzywuzzy-fuzzy-string-matching-in-python/

本人非专业生,芝士水平有限,如有错漏烦请指正。

全局匹配和子字符串匹配

先看对ice和icecream两个字符串的处理:

from fuzzywuzzy import fuzz

str1 = "ice"
str2 = "icecream"
    
print (fuzz.ratio(str1, str2))
# 55
print (fuzz.partial_ratio(str1, str2))
# 100

从形式上看,icecream比ice在末尾多了几个字符,或者说ice是icecream的子字符串,但其实它们是两个东西。ratio()判断不相似,而partial_ratio()做得不够好,它认为两者是完全等同的。

str1 = "ice-cream"
str2 = "icecream"

print (fuzz.ratio(str1, str2))
# 94
print (fuzz.partial_ratio(str1, str2))
# 88

这个例子的结果相反,对于相同的两样东西“ice-cream”和"icecream",partial_ratio()给出了比ratio()更低的匹配值。

前面两个例子中,str1、str2长度是不等的。实际上当两个字符串长度相等时,ratio()和partial_ratio()给出的得分是一样的:

str1 = "ice-cream"
str2 = "ice,cream"

print (fuzz.ratio(str1, str2))
# 89
print (fuzz.partial_ratio(str1, str2))
# 89

总结一下,对于两个长度不同的字符串(比如ice和icecream),partial_ratio()返回得分最高的子字符串(substring)的匹配值。对比ice和icecream,他会把icecream拆成ice, cec, cre, …等等长度和ice相等的子字符串构成的序列,再一一进行ratio()比较。

在icecream中得分最高的子字符串是ice,因为ice和ice完全一样,所以最终结果是ice和ice的匹配值,即100。

简单理解,如果字符串B包含了完整的字符串A,比如在icecream中包含了ice,partial_ratio()得出的匹配值就是100,无论在icecream and cheesecake and beefburger前或后面加多少吃的,结果还是100。

打乱顺序的匹配

fuzz.token_sort_ratio()用来匹配两个意思相同、但顺序不同的字符串:

str1 = "Tom and Jerry"
str2 = "Jerry and Tom"

print (fuzz.ratio(str1, str2))
# 38
print (fuzz.token_sort_ratio(str1, str2))
# 100

ratio()对顺序敏感,而token_sort_ratio()不受单词顺序影响。

token_sort_ratio()以空格为分隔符,小写化所有字母,无视空格外的其它标点符号,把字符串转化为“tom”, “and”, “jerry"三个token(tokenize),按字母顺序组合成一个新的字符串“and jerry tom”,再进行普通的ratio()比较。所以“Tom and Jerry”和“Jerry and Tom”,在token_sort_ratio()眼里,都是"and jerry tom”,最终匹配得分为100.

另外还有partial_token_sort_ratio()方法,这里Tom和Tommy的partial_ratio()一样,所以整体匹配值也为100:

str1 = "Tom and Jerry"
str2 = "Jerry and Tommy"

print (fuzz.ratio(str1, str2))
# 43
print (fuzz.partial_token_sort_ratio(str1, str2))
# 100

由于这种排序,还会发生如下情况:

str1 = "Tom and Jerry"
str2 = "Jerry and Tom and Ana"

print (fuzz.ratio(str1, str2))
# 47
print (fuzz.partial_token_sort_ratio(str1, str2))
# 100

这是因为排序后,"and Ana"位于字符串的最前面,按照partial_ratio()的处理被忽略掉了(排在末尾也是一样的)。

含有重复元素的匹配

假设有人在数据录入出现了重复,类似"Tom Tom and Jerry"这样的错误,我们可用fuzz.token_set_ratio()解决。

str1 = "Tom and Jerry"
str2 = "Tom Tom and Jerry"

print (fuzz.ratio(str1, str2))
# 87
print (fuzz.token_set_ratio(str1, str2))
# 100

除此以外,token_set_ratio()还有更多用途。我们之前讲的token_sort_ratio(),是先token化,再排序,最后匹配。而token_set_ratio()在排序的同时还会把字符串分为共有部分(intersection)和多余部分,如下:

# 这一步除了token化、分离、排序外,还会删除字符串中重复的token
inter = [sorted_intersection]
sorted_str1 = [sorted_intersection] + [sorted_rest_of_str1]
sorted_str2 = [sorted_intersection] + [sorted_rest_of_str2]

ratio1 = fuzz.ratio(inter, str1)
ratio2 = fuzz.ratio(inter,str2)
ratio3 = fuzz.ratio(str1, str2)

再在ratio1, ratio2, ratio3三个值之中简单取最大值。

来看实例,假设我们现在有两个人同时输入Donald Trump,但其中一个人输入了全名"Donald J. Trump",而另一个人不小心多按了一次ctrl+v,输入了"Donald TrumpDonald Trump". 这个例子中并没有重复的单词,那么token_set_ratio()处理效果如何呢?

str1 = "Donald J. Trump"
str2 = "Donald TrumpDonald Trump"

# token化,删除重复值,排序
inter = "donald trump" 
sorted_str1 = "donald trump j"  # j后面的标点被忽视了
sortrd_str2 = "donald trump trumpdonald"

# 两两匹配,取最大值
print (fuzz.ratio(inter, sorted_str1))
# 92
print (fuzz.ratio(inter, sorted_str2))
# 67
print (fuzz.ratio(sorted_str1, sorted_str2))
# 68

print (fuzz.token_set_ratio(str1, str2))
# 92

可以看到,token_set_ratio()在这几个条件下匹配分会较高:
(1)共有部分在其中一个字符串所占比例很大,那么ratio1或ratio2得分会高;
(2)两个字符串多余的部分十分接近,那么ratio3得分会高。

总结

总的来说,fuzz这几个ratio()函数比较笨,需要人工判断字符串自身的情况,再选择相应的函数匹配。如果选错了,会得到和预想差别很大的结果。前面例子中就有很多匹配结果不理想的。

另外fuzzywuzzy还有process模块,用于处理备选答案有限的情况。比如某个问题的答案只有[yes, no, maybe, N/A]几种可能,而答案可能出现"ya", "none"等,可以使用process.extract()方法,去匹配备选项中最接近的那一个。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值