字符串的相似性可以这样比较...

问题:数据库里长期积累的一些数据,由于经手的人太多,结果现在相同的客户出现了很多不同的名字,因此想做个相似度查询,把比较相似的名称查找出来,人工比对进行校对统一。

一、笨办法

想要比较两个字符串是否相似,这个应该有相应的算法,我先用了自己的办法,到时再到网上找找,有没有更好办法。
假设,有这样三个客户名:

s1 = "宁波市第一医院"
s2 = "宁波市一院"
s3 = "宁波李惠利医院"

我们先把他们变成集合,再比较两者的交集与并集,计算比值,原理是利用如果两个相似的名称的交集长度应该与并集长度相近,我们来看看:

# s1与s2相比
# 交集
set(s1)&set(s2)
# 输出: {'一', '宁', '市', '波', '院'}
# 并集
set(s1)|set(s2)
# 输出: {'一', '医', '宁', '市', '波', '第', '院'}
len(set(s1)&set(s2))/len(set(s1)|set(s2))
# 输出: 0.7142857142857143
# s1与s3相比
set(s1)&set(s3)
# 输出: {'医', '宁', '波', '院'}
set(s1)|set(s3)
# 输出: {'一', '利', '医', '宁', '市', '惠', '李', '波', '第', '院'}
len(set(s1)&set(s3))/len(set(s1)|set(s3))
# 输出: 0.4

利用这一特性,可以看出我们的策略是有效的。
不过这个算法还是存在一些问题,比如:

s1 = "宁波市一院"
s2 = "宁波市二院"
s3 = "宁波市三院"

这三个客户,人工识别是知道是不同的,可是用算法来看看:

len(set(s1)&set(s2))/len(set(s1)|set(s2))
# 输出:  0.6666666666666666
len(set(s1)&set(s3))/len(set(s1)|set(s3))
# 输出:  0.6666666666666666

他们输出值是一样的,都超过了0.5,这就需要我们在设置筛选条件时通过反复调试,找到最适合的阀值了,不然会出现,明明是不一样的,却筛选出来。

二、不要重复造轮子

有个伟大的程序员说过"不要重复造轮子",我们前面的分析其实就是重复造轮子,那现在我们来找找有没有别人造好的轮子。
网上找了下,原来python自带的库difflib就可以解决这个问题,python就是好用呀,再来试试:

import difflib
s1 = "宁波市第一医院"
s2 = "宁波市一院"
s3 = "宁波李惠利医院"
# 为了不用重复输入太长,我们先自定义一个函数

def string_similar(s1, s2):
    return difflib.SequenceMatcher(None, s1, s2).quick_ratio()

print(string_similar(s1,s1))
print(string_similar(s1,s2))
print(string_similar(s1,s3))
# 输出
# 1.0
# 0.8333333333333334
# 0.5714285714285714

可以很好的区分相似字符串,那么我们再来试试,前面第二个例子:

s1 = "宁波市一院"
s2 = "宁波市二院"
s3 = "宁波市三院"

print(string_similar(s1,s2))
print(string_similar(s1,s3))
# 输出
# 0.8
# 0.8

和我们自己做的算法类似,依然要调试阀值,避免筛选错误。这里虽然和我们自己造的轮子差不多,可是这个是python自带的库,通常运行会比我们自己造的轮子要快,所以还是推荐大家用这个库.

三、也试试第三方库

虽然前面已经用过我们自己造的轮子和python自带的库,都能解决我们的问题,但是我还是想看看有没有什么第三方库也能达到我们的目的。
经过一翻查找,我们找到了fuzzywuzzy,这是一个第三方库,需要我们自己安装,在查找的过程中我们还了解到一个新的知识点,我们前面的例子中都没有考虑一个问题,那就是字符串的位置关系。当然这个是在我们自己造的轮子里完全没有体现的,而系统自己带的库,我们等下会再测试一个例子,看看字符串位置会有影响吗?

pip install fuzzywuzzy

安装好,我们就可以开始使用了。

from fuzzywuzzy import fuzz

s1 = "宁波市第一医院"
s2 = "宁波市一院"
s3 = "宁波李惠利医院"
print(fuzz.ratio(s1,s1))
print(fuzz.ratio(s1,s2))
print(fuzz.ratio(s1,s3))
# 输出
# 100
# 83
# 57

很好,可以正常区分,再测试一下位置关系:

s4 = "李惠利宁波医院"
print(fuzz.ratio(s3,s4))
# 输出: 71

可以看出,位置是有影响的,我们自己造的轮子根据我们的算法原理是对位置不敏感的,依然测试下:

len(set(s3)&set(s4))/len(set(s3)|set(s4))
# 输出: 1.0

如我们所想的那样,没有考虑到位置关系的影响。
fuzzywuzzy还有一个方法,可以只考虑包含关系,比如:

s1 = "宁波李惠利医院"
s2 = "宁波李惠利医院东部院区"
# 先看看我们自己的算法
len(set(s1)&set(s2))/len(set(s1)|set(s2))
# 再看看fuzzywuzzy的方法
print(fuzz.partial_ratio(s1,s2))
print(fuzz.ratio(s1,s2))
# 输出
# 0.7
# 100
# 78

可以看出,我们自己的算法没有考虑到这种情况,如果按阀值在0.8以上,这个相似性会忽略,而采用fuzzywuzzy这种方法就会识别出来。

四、实战

分析了这么多库后,我们会采用哪种方法,自己造的轮子不用说,已经出局,那么python自带的还是第三方库呢,其实经过查阅文档,官方库完全能解决我们的问题,而且速度还有保证,不过官方库一般的问题就是代码可能比较底层,也就是不太人性化,调用起来有点别扭。这时我们可以用第三方库,使用起来比较平滑。
我们的文件内容,名称是网上爬来的:


# coding:utf-8
import pandas as pd
import fire

# 原来的方法
def check_similar(s1, s2):
    """比较两个字符串的相似度
    :param s1: string 字符串1
    :param s2: string 字符串2
    :return: float 返回相似度
    """
    if s1 is None:
        return
    if s2 is None:
        return
    s1 = set(list(s1))
    s2 = set(list(s2))
    return len(s1 & s2) / len(s1 | s2)


# 采用fuzz
def check_similar_with_fuzz(s1, s2):
    """比较两个字符串的相似度
    :param s1: string 字符串1
    :param s2: string 字符串2
    :return: float 返回相似度
    """
    return fuzz.partial_ratio(s1, s2)

def find_similar(df, threshold=0.8, id_col_num=0, name_col_num=3):
    """找出文档中的符合相似度的字符串
    :param df: pd.DataFrame 包含字符串的文档
    :param threshold: float 相似度
    :param id_col_num: int 参考列号
    :param name_col_num: int 需要对比字符串的列号
    :return list: 返回包含符合相似度的结果列表
    """
    ...


def main(target_path, save_path, id_col_num, name_col_num):
    """主函数,运行命令行程序
    :param target_path:str 目标路径
    :param save_path: str 保存路径
    :param id_col_num: int 参考列号
    :param name_col_num: int 需要对比字符串的列号
    """
    df = pd.read_excel(target_path)
    result = find_similar(
        df, threshold=80, id_col_num=id_col_num, name_col_num=name_col_num)
    out = pd.DataFrame(result)
    out.to_csv(save_path, index=False)


if __name__ == '__main__':
    fire.Fire(main)

运行测试

# 文件里给每个字符串做了一个编号,方便查找原文件里的位置
python similar.py "目标路径" "存储路径" 参考列号 比较列号

输出结果查看下:



可以看到输出,确实把相似的都排出来了,不过话说这些公司取名可真没有创意呀,这样的只有两个字不一样的,怎么算呢?

五、提高

经过实战,我们发现能够成功的把相似的客户名放到一起供人工核对了,可是如果内容多的话还有公司名字只有极少数字不一样,依然会是比较麻烦。这时我们有两个方法,不断的调试阀值,再调高点。另外就是人工识别吧。
其实应该还可以设计那些词汇不需要比较,比如这里的有限公司之类的,还有各种地名都可以去除再比较。

后记

既然有这么多公司名,下次分析下,这些公司比较喜欢用什么字做公司名。最才是美容机构的心头好呢?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342