在日常开发工作中,经常会遇到这样的一个问题:要对数据中的某个字段进行匹配,但这个字段有可能会有微小的差异。比如同样是招聘岗位的数据,里面省份一栏有的写“广西”,有的写“广西壮族自治区”,甚至还有写“广西省”……为此不得不增加许多代码来处理这些情况。
今天跟大家分享FuzzyWuzzy
一个简单易用的模糊字符串匹配工具包。让你轻松解决烦恼的匹配问题!
在处理数据的过程中,难免会遇到下面类似的场景,自己手里头获得的是简化版的数据字段,但是要比对的或者要合并的却是完整版的数据(有时候也会反过来)
最常见的一个例子就是:在进行地理可视化中,自己收集的数据只保留的缩写,比如北京,广西,新疆,西藏等,但是待匹配的字段数据却是北京市,广西壮族自治区,新疆维吾尔自治区,西藏自治区等,如下。因此就需要有没有一种方式可以很快速便捷的直接进行对应字段的匹配并将结果单独生成一列,就可以用到FuzzyWuzzy
库。
FuzzyWuzzy
是一个简单易用的模糊字符串匹配工具包。它依据 Levenshtein Distance
算法,计算两个序列之间的差异。
Levenshtein Distance
算法,又叫 Edit Distance
算法,是指两个字符串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。
这里使用的是Anaconda
下的jupyter notebook
编程环境,因此在Anaconda
的命令行中输入一下指令进行第三方库安装。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple FuzzyWuzzy
该模块下主要介绍四个函数(方法),分别为:简单匹配(Ratio
)、非完全匹配(Partial Ratio
)、忽略顺序匹配(Token Sort Ratio
)和去重子集匹配(Token Set Ratio
)。
注意:如果直接导入这个模块的话,系统会提示warning,当然这不代表报错,程序依旧可以运行(使用的默认算法,执行速度较慢),可以按照系统的提示安装python-Levenshtein
库进行辅助,这有利于提高计算的速度。
pip install python-Levenshtein
简单的了解一下就行,这个不怎么精确,也不常用。
# 匹配
fuzz.ratio("河南省", "河南省")
# 输出结果
100
# 匹配
fuzz.ratio("河南", "河南省")
# 输出结果
80
尽量使用非完全匹配,精度较高。
# 匹配
fuzz.partial_ratio("河南省", "河南省")
# 输出结果
100
# 匹配
fuzz.partial_ratio("河南", "河南省")
# 输出结果
100
原理在于:以 空格 为分隔符,小写 化所有字母,无视空格外的其它标点符号。
1)使用简单匹配(ratio)的情况下
# 匹配
fuzz.ratio("西藏 自治区", "自治区 西藏")
# 输出结果
50
# 匹配
fuzz.ratio('I love YOU','YOU LOVE I')
# 输出结果
30
2)使用忽略顺序匹配(Token Sort Ratio)的情况下
# 匹配
fuzz.token_sort_ratio("西藏 自治区", "自治区 西藏")
# 输出结果
100
# 匹配
fuzz.token_sort_ratio('I love YOU','YOU LOVE I')
# 输出结果
100
相当于比对之前有一个集合去重的过程,注意最后两个,可理解为该方法是在token_sort_ratio方法的基础上添加了集合去重的功能,下面三个匹配的都是倒序。
# 1. 简单匹配
fuzz.ratio("西藏 西藏 自治区", "自治区 西藏")
# 输出结果
40
# 2. 忽略顺序匹配
fuzz.token_sort_ratio("西藏 西藏 自治区", "自治区 西藏")
# 输出结果
80
# 3. 去重子集匹配
fuzz.token_set_ratio("西藏 西藏 自治区", "自治区 西藏")
# 输出结果
100
fuzz这几个ratio()函数(方法)最后得到的结果都是数字,如果需要获得匹配度最高的字符串结果,还需要依旧自己的数据类型选择不同的函数,然后再进行结果提取,如果但看文本数据的匹配程度使用这种方式是可以量化的,但是对于我们要提取匹配的结果来说就不是很方便了,因此就有了process模块。
用于处理备选答案有限的情况,返回模糊匹配的字符串和相似度。
choices = ["河南省", "郑州市", "湖北省", "武汉市"]
process.extract("郑州", choices, limit=2)
# 输出结果
[('郑州市', 90), ('河南省', 0)]
"""
在选择列表中,字符串"郑州市"与目标字符串"郑州"最相似,相似程度为90。其次是字符串"河南省",相似程度为45。
"""
extract之后的数据类型是列表,即使limit=1,最后还是列表,注意和下面extractOne的区别。
如果要提取匹配度最大的结果,可以使用extractOne,注意这里返回的是 元组 类型, 还有就是匹配度最大的结果不一定是我们想要的数据,可以通过下面的示例和两个实战应用体会一下。
choices = ["河南省", "郑州市", "湖北省", "武汉市"]
process.extractOne("郑州", choices)
# 输出结果
('郑州市', 90)
process.extractOne("北京", choices)
# 输出结果
('湖北省', 45)
数据及待匹配的数据样式如下:两份数据,一个是公司全称,一个是公司简称。
直接将代码封装为函数,主要是为了方便日后的调用,这里参数设置的比较详细。:
from fuzzywuzzy import process
origin = [{'id': 1, 'name': '张娟', 'province': '香港特别行政区', 'address': '辽宁省瑞市崇文西安路w座 972299', 'company': '联软网络有限公司'},
{'id': 2, 'name': '常桂英', 'province': '澳门特别行政区', 'address': '辽宁省昆明市合川张路B座 931970', 'company': '创联世纪信息有限公司'}] # 此处省略了其他数据
target = [{'序号': 1, '姓名': '张娟', '身份': '香港', '地址': '辽宁省瑞市崇文西安路w座 972299', '公司': '联软网络'},
{'序号': 2, '姓名': '常桂英', '身份': '澳门', '地址': '辽宁省昆明市合川张路B座 931970', '公司': '创联世纪信息'}] # 此处省略了其他数据
# 模糊匹配
def fuzzy_merge(my_data, my_data_key, other_data, other_data_key, threshold=90, limit=2):
"""
:param my_data: 我自己的数据
:param my_data_key: 我自己数据里面 需要比对的key
:param other_data: 待匹配的数据
:param other_data_key: 待匹配的数据 需要比对的key
:param threshold: 字符串匹配的相似度阈值,匹配程度低于该阈值的结果会被过滤掉,默认为90。
:param limit: the amount of matches that will get returned, these are sorted high to low
:return: 返回的匹配数量,按高到低排序,默认为2。
"""
# 获取我自己数据 需要比对key的所有value
_my_data = [i[my_data_key] for i in my_data]
# 获取待匹配的数据 需要比对key的所有value
_other_data = [i[other_data_key] for i in other_data]
# 定义一个空列表,存储结果
result = []
i = 0
for x in _my_data:
result = process.extract(x, _other_data, limit=limit)
my_data[i]['matches'] = [i[0] for i in result if i[1] >= threshold][0] if len(
[i[0] for i in result if i[1] >= threshold]) > 0 else ''
i = i + 1
return my_data
res = fuzzy_merge(my_data=origin, my_data_key="company", other_data=target, other_data_key="公司")