<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
當然,基於排序的模糊匹配(類似於Excel的VLOOKUP函數的模糊匹配模式)也屬於模糊匹配的範疇,但那種過於簡單,不是本文討論的範疇。
本文主要討論的是以公司名稱或地址為主的字串的模糊匹配。
進行模糊匹配的基本思路就是,計算每個字串與目標字串的相似度,取相似度最高的字串作為與目標字串的模糊匹配結果。
對於計算字串之間的相似度,最常見的思路便是使用編輯距離演演算法。
下面我們有28條名稱需要從資料庫(390條資料)中找出最相似的名稱:
import pandas as pd excel = pd.ExcelFile("所有客戶.xlsx") data = excel.parse(0) find = excel.parse(1) display(data.head()) print(data.shape) display(find.head()) print(find.shape)
編輯距離演演算法,是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。允許的編輯操作包括將一個字元替換成另一個字元,插入一個字元,刪除一個字元。
一般來說,編輯距離越小,表示操作次數越少,兩個字串的相似度越大。
建立計算編輯距離的函數:
def minDistance(word1: str, word2: str): '編輯距離的計算函數' n = len(word1) m = len(word2) # 有一個字串為空串 if n * m == 0: return n + m # DP 陣列 D = [[0] * (m + 1) for _ in range(n + 1)] # 邊界狀態初始化 for i in range(n + 1): D[i][0] = i for j in range(m + 1): D[0][j] = j # 計算所有 DP 值 for i in range(1, n + 1): for j in range(1, m + 1): left = D[i - 1][j] + 1 down = D[i][j - 1] + 1 left_down = D[i - 1][j - 1] if word1[i - 1] != word2[j - 1]: left_down += 1 D[i][j] = min(left, down, left_down) return D[n][m]
關於上述程式碼的解析可參考力扣題解:https://leetcode-cn.com/problems/edit-distance/solution/bian-ji-ju-chi-by-leetcode-solution/
遍歷每個被查詢的名稱,計算它與資料庫所有客戶名稱的編輯距離,並取編輯距離最小的客戶名稱:
result = [] for name in find.name.values: a = data.user.apply(lambda user: minDistance(user, name)) user = data.user[a.argmin()] result.append(user) find["result"] = result find
測試後發現部分地址的效果不佳。
我們任取2個結果為信陽息縣淮河路店地址看看編輯距離最小的前10個地址和編輯距離:
a = data.user.apply(lambda user: minDistance(user, '河南美銳信陽息縣淮河路分店')) a = a.nsmallest(10).reset_index() a.columns = ["名稱", "編輯距離"] a.名稱 = data.user[a.名稱].values a
a = data.user.apply(lambda user: minDistance(user, '河南美銳信陽潢川四中分店')) a = a.nsmallest(10).reset_index() a.columns = ["名稱", "編輯距離"] a.名稱 = data.user[a.名稱].values a
可以看到,在前十個編輯距離最小的名稱中還是存在我們想要的結果。
通過上面的程式碼,我們已經基本瞭解了通過編輯距離演演算法進行批次模糊匹配的基本原理。不過自己編寫編輯距離演演算法的程式碼較為複雜,轉換為相似度進行分析也比較麻煩,如果已經有現成的輪子就不用自己寫了。
而fuzzywuzzy庫就是基於編輯距離演演算法開發的庫,而且將數值量化為相似度評分,會比我們寫的沒有針對性優化的演演算法效果要好很多,可以通過pip install FuzzyWuzzy來安裝。
對於fuzzywuzzy庫,主要包含fuzz模組和process模組,fuzz模組用於計算兩個字串之間的相似度,相當於對上面的程式碼的封裝和優化。而process模組則可以直接提取需要的結果。
from fuzzywuzzy import fuzz
簡單匹配(Ratio):
a = data.user.apply(lambda user: fuzz.ratio(user, '河南美銳信陽潢川四中分店')) a = a.nlargest(10).reset_index() a.columns = ["名稱", "相似度"] a.名稱 = data.user[a.名稱].values a
非完全匹配(Partial Ratio):
a = data.user.apply(lambda user: fuzz.partial_ratio(user, '河南美銳信陽潢川四中分店')) a = a.nlargest(10).reset_index() a.columns = ["名稱", "相似度"] a.名稱 = data.user[a.名稱].values a
顯然fuzzywuzzy庫的 ratio()函數比前面自己寫的編輯距離演演算法,準確度高了很多。
process模組則是進一步的封裝,可以直接獲取相似度最高的值和相似度:
from fuzzywuzzy import process
extract提取多條資料:
users = data.user.to_list() a = process.extract('河南美銳信陽潢川四中分店', users, limit=10) a = pd.DataFrame(a, columns=["名稱", "相似度"]) a
從結果看,process模組似乎同時綜合了fuzz模組簡單匹配(Ratio)和非完全匹配(Partial Ratio)的結果。
當我們只需要返回一條資料時,使用extractOne會更加方便:
users = data.user.to_list() find["result"] = find.name.apply(lambda x: process.extractOne(x, users)[0]) find
可以看到準確率相對前面自寫的編輯距離演演算法有了大幅度提升,但個別名稱匹配結果依然不佳。
檢視這兩個匹配不準確的地址:
process.extract('許灣鄉許灣村焦豔芳衛生室', users)
[('小寨溝村衛生室', 51),
('周口城鄉一體化焦豔芳一體化衛生室', 50),
('西華縣皮營鄉樓陳村衛生室', 42),
('葉縣鄧李鄉杜楊村第二衛生室', 40),
('湯陰縣瓦崗鄉龍虎村東衛生室', 40)]
process.extract('河南美銳信陽息縣淮河路分店', users)
[('信陽息縣淮河路店', 79),
('河南美銳大藥房連鎖有限公司息縣淮河路分店', 67),
('河南美銳大藥房連鎖有限公司息縣大河文錦分店', 53),
('河南美銳大藥房連鎖有限公司息縣千佛庵東路分店', 51),
('河南美銳大藥房連鎖有限公司息縣包信分店', 50)]
對於這樣的問題,個人並沒有一個很完美的解決方案,個人建議是將相似度最高的n個名稱都加入結果列表中,後期再人工篩選:
result = find.name.apply(lambda x: next(zip(*process.extract(x, users, limit=3)))).apply(pd.Series) result.rename(columns=lambda i: f"匹配{i+1}", inplace=True) result = pd.concat([find.drop(columns="result"), result], axis=1) result
雖然可能有個別正確結果這5個都不是,但整體來說為人工篩查節省了大量時間。
from fuzzywuzzy import process import pandas as pd excel = pd.ExcelFile("所有客戶.xlsx") data = excel.parse(0) find = excel.parse(1) users = data.user.to_list() result = find.name.apply(lambda x: next( zip(*process.extract(x, users, limit=3)))).apply(pd.Series) result.rename(columns=lambda i: f"匹配{i+1}", inplace=True) result = pd.concat([find, result], axis=1) result
Gensim支援包括TF-IDF,LSA,LDA,和word2vec在內的多種主題模型演演算法,支援流式訓練,並提供了諸如相似度計算,資訊檢索等一些常用任務的API介面。
基本概念:
安裝:pip install gensim
官網:https://radimrehurek.com/gensim/
什麼情況下需要使用NLP來進行批次模糊匹配呢?那就是資料庫資料過於龐大時,例如達到幾萬級別:
import pandas as pd data = pd.read_csv("所有客戶.csv", encoding="gbk") find = pd.read_csv("被查詢的客戶.csv", encoding="gbk") display(data.head()) print(data.shape) display(find.head()) print(find.shape)
此時如果依然用編輯距離或fuzzywuzzy暴力遍歷計算,預計1小時也無法計算出結果,但使用NLP神器Gensim僅需幾秒鐘,即可計算出結果。
首先,我們需要先對原始的文字進行分詞,得到每一篇名稱的特徵列表:
import jieba data_split_word = data.user.apply(jieba.lcut) data_split_word.head(10)
0 [珠海, 廣藥, 康鳴, 醫藥, 有限公司]
1 [深圳市, 寶安區, 中心醫院]
2 [中山, 火炬, 開發區, 伴康, 藥店]
3 [中山市, 同方, 醫藥, 有限公司]
4 [廣州市, 天河區, 元崗金, 健民, 醫藥, 店]
5 [廣州市, 天河區, 元崗居, 健堂, 藥房]
6 [廣州市, 天河區, 元崗潤佰, 藥店]
7 [廣州市, 天河區, 元崗, 協心, 藥房]
8 [廣州市, 天河區, 元崗, 心怡, 藥店]
9 [廣州市, 天河區, 元崗永亨堂, 藥店]
Name: user, dtype: object
接下來,建立語料特徵的索引字典,並將文字特徵的原始表達轉化成詞袋模型對應的稀疏向量的表達:
from gensim import corpora dictionary = corpora.Dictionary(data_split_word.values) data_corpus = data_split_word.apply(dictionary.doc2bow) data_corpus.head()
0 [(0, 1), (1, 1), (2, 1), (3, 1), (4, 1)]
1 [(5, 1), (6, 1), (7, 1)]
2 [(8, 1), (9, 1), (10, 1), (11, 1), (12, 1)]
3 [(0, 1), (3, 1), (13, 1), (14, 1)]
4 [(0, 1), (15, 1), (16, 1), (17, 1), (18, 1), (...
Name: user, dtype: object
這樣得到了每一個名稱對應的稀疏向量(這裡是bow向量),向量的每一個元素代表了一個詞在這個名稱中出現的次數。
至此我們就可以構建相似度矩陣:
from gensim import similarities index = similarities.SparseMatrixSimilarity(data_corpus.values, num_features=len(dictionary))
再對被查詢的名稱作相同的處理,即可進行相似度批次匹配:
find_corpus = find.name.apply(jieba.lcut).apply(dictionary.doc2bow) sim = index[find_corpus] find["result"] = data.user[sim.argmax(axis=1)].values find.head(30)
可以看到該模型計算速度非常快,準確率似乎整體上比fuzzywuzzy更高,但fuzzywuzzy對河南美銳大藥房連鎖有限公司308廠分店的匹配結果是正確的。
之前我們使用的Corpus都是詞頻向量的稀疏矩陣,現在將其轉換為TF-IDF模型後再構建相似度矩陣:
from gensim import models tfidf = models.TfidfModel(data_corpus.to_list()) index = similarities.SparseMatrixSimilarity( tfidf[data_corpus], num_features=len(dictionary))
被查詢的名稱也作相同的處理:
sim = index[tfidf[find_corpus]] find["result"] = data.user[sim.argmax(axis=1)].values find.head(30)
可以看到許灣鄉許灣村焦豔芳衛生室匹配正確了,但河南美銳信陽息縣淮河路分店又匹配錯誤了,這是因為在TF-IDF模型中,由於美銳在很多條資料中都出現被降權。
假如只對資料庫做TF-IDF轉換,被查詢的名稱只使用詞頻向量,匹配效果又如何呢?
from gensim import models tfidf = models.TfidfModel(data_corpus.to_list()) index = similarities.SparseMatrixSimilarity( tfidf[data_corpus], num_features=len(dictionary)) sim = index[find_corpus] find["result"] = data.user[sim.argmax(axis=1)].values find.head(30)
可以看到除了資料庫本來不包含正確名稱的愛聯寶之林大藥房外還剩下河南美銳大藥房連鎖有限公司308廠分店匹配不正確。這是因為不能識別出308的語意等於三零八。如果這類資料較多,我們可以先將被查詢的資料統一由小寫數位轉換為大寫數位(保持與資料庫一致)後,再分詞處理:
trantab = str.maketrans("0123456789", "零一二三四五六七八九") find_corpus = find.name.apply(lambda x: dictionary.doc2bow(jieba.lcut(x.translate(trantab)))) sim = index[find_corpus] find["result"] = data.user[sim.argmax(axis=1)].values find.head(30)
經過這樣處理後,308廠分店也被正確匹配上了,其他類似的問題都可以使用該思路進行轉換。
雖然經過上面的處理,匹配準確率幾乎達到100%,但不代表其他型別的資料也會有如此高的準確率,還需根據資料的情況具體去分析轉換。並沒有一個很完美的批次模糊匹配的處理辦法,對於這類問題,我們不能完全信任程式匹配的結果,都需要人工的二次檢查,除非能夠接受一定的錯誤率。
為了我們人工篩選的方便,我們可以將前N個相似度最高的資料都儲存到結果中,這裡我們以三個為例:
下面我們將相似度最高的3個值都新增到結果中:
result = [] for corpus in find_corpus.values: sim = pd.Series(index[corpus]) result.append(data.user[sim.nlargest(3).index].values) result = pd.DataFrame(result) result.rename(columns=lambda i: f"匹配{i+1}", inplace=True) result = pd.concat([find.drop(columns="result"), result], axis=1) result.head(30)
from gensim import corpora, similarities, models import jieba import pandas as pd data = pd.read_csv("所有客戶.csv", encoding="gbk") find = pd.read_csv("被查詢的客戶.csv", encoding="gbk") data_split_word = data.user.apply(jieba.lcut) dictionary = corpora.Dictionary(data_split_word.values) data_corpus = data_split_word.apply(dictionary.doc2bow) trantab = str.maketrans("0123456789", "零一二三四五六七八九") find_corpus = find.name.apply( lambda x: dictionary.doc2bow(jieba.lcut(x.translate(trantab)))) tfidf = models.TfidfModel(data_corpus.to_list()) index = similarities.SparseMatrixSimilarity( tfidf[data_corpus], num_features=len(dictionary)) result = [] for corpus in find_corpus.values: sim = pd.Series(index[corpus]) result.append(data.user[sim.nlargest(3).index].values) result = pd.DataFrame(result) result.rename(columns=lambda i: f"匹配{i+1}", inplace=True) result = pd.concat([find, result], axis=1) result.head(30)
本文首先分享了編輯距離的概念,以及如何使用編輯距離進行相似度模糊匹配。然後介紹了基於該演演算法的輪子fuzzwuzzy,封裝的較好,使用起來也很方便,但是當資料庫量級達到萬條以上時,效率極度下降,特別是資料量達到10萬級別以上時,跑一整天也出不了結果。於是通過Gensim計算分詞後對應的tf-idf向量來計算相似度,計算時間由幾小時降低到幾秒,而且準確率也有了較大提升,能應對大部分批次相似度模糊匹配問題。
到此這篇關於Python批次模糊匹配的3種方法的文章就介紹到這了,更多相關Python批次模糊匹配內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45