脏数据匹配
一般数据建模步骤中,数据清洗耗时占比80%以上,因为现实中接触到的数据相当脏,无法直接简单的用pandas的merge函数解决。下面以QS大学排名的匹配为例,简单介绍脏数据匹配中会遇到的问题和主要步骤。
1 问题描述
给定一个QS大学排名数据集,主要字段为大学名和排名,再给定一个带大学名称的本地数据集,我们需要根据QS表中的名字与我们已有的数据集中的大学名字进行匹配,然后将对应的QS排名添加到本地数据集中。QS数据集和本地数据集形式如下图:
数据匹配的过程中,可能出现以下几个问题需要处理。
- 格式:比如是否加标点符号,名称顺序不同等
- 语言:不同国家的学校语言可能不同
- 别名:新旧名或多个名字、缩写等等
2 一般步骤
对于较为规整的数据,可以尝试直接用pandas的merge函数进行匹配,效率往往也较高。但merge函数只能解决规范化的问题,则建议使用json类型转化为列表和字典的组合形式,虽然降低了数据处理的速度,但提供了更灵活的匹配与修改操作。(ps 建议熟练掌握pandas的常用数据处理函数,了解其规范化的处理方式以及使用限制,才能很快判断是否能用标准库处理。)
原则上,匹配的过程遵循从精准匹配到模糊匹配的顺序。 因为已经匹配的数据将不参与后续的匹配,而模糊匹配可能会出现错误,且后续无法纠正该错误,所以应该在前面步骤实在无法匹配成功的情况下使用模糊匹配。
2.1 数据导入
# 将csv转为json再导入
qs = pd.read_csv('2024 QS World University Rankings 1.1 (For qs.com).csv')
data = pd.read_csv('data.csv')
qs.to_json('QS_rank.json', orient='records')
data.to_json('data.json', orient='records')
with open('QS_rank.json', 'r', encoding='utf-8') as f:
qs = json.load(f)
with open('data.json', 'r', encoding='utf-8') as f:
data = json.load(f)
2.2 匹配函数
left_on和right_on分别为左右合并键,func为判断是否匹配的函数,传入参数为要比较的两个字符串,返回是否匹配的bool值,以及两表对应匹配的字段,方便后续校对。
# 匹配函数
def merge(data, qs, left_on, right_on, func):
for i in range(len(data)):
ent = data[i]
if left_on not in ent.keys():
continue
# 判断左键值是否不为None
if not ent[left_on]:
continue
for j in range(len(qs)):
uni = qs[j]
result = func(ent[left_on], uni[right_on])
match, left_key, right_key = result[0], result[1], result[2]
if match and ('QS rank' not in ent.keys()):
ent['QS rank'] = uni['2024 RANK']
ent['left_key'] = left_key
ent['right_key'] = right_key
if uni[right_on] not in ent['Name List']:
ent['Name List'].append(uni[right_on]) # 创建name list字段方便日后遇到同样的名字可以直接进行匹配(这一步也可以不加)
data[i] = ent
break
# 判断匹配后还剩多少未匹配对象
print(pd.DataFrame(data)['QS rank'].isnull().sum())
2.3 匹配方式
按照从精准到不精准的匹配顺序,依次介绍传入匹配函数的func参数。(顺序可以根据具体情况判断,没有绝对的顺序)
2.3.1 完全相等
func_equal = lambda x, y : (x == y, x, y)
merge(data, qs, 'name', 'Institution Name', func_equal)
2.3.2 忽略大小写后相等
func_lower = lambda x, y : (x.lower() == y.lower(), x, y)
merge(data, qs, 'name', 'Institution Name', func_lower)
2.3.3 正则化匹配
将脏数据中没有实际意义的虚词删除可以进一步提高匹配数量,这一步可以用正则化来完成。根据数据集的具体情况,写对应的正则化pattern。需要注意的是,正则表达式来做替换时,需要注意替换的顺序,即先替换不容易满足的特例,再替换普遍容易满足的一般情况。
比如先用’ - ‘替换为’ ‘,再用’-‘替换为’ ‘,因为如果先替换后者则会使’ - '变为两个空格,而导致无法匹配上。
def pattern(string1, string2):
patterns_need_delete = ['the ', '\'', '’', '‘', ',', 'universi[A-Za-z]* ', 'Üniversitesi ']
patterns_need_replace = [' at ', ' in ', ' do ', ' de ', ' di ', ' of ', ' and ', ' & ', ' - ', '-', ' da ']
string1, string2 = string1.lower(), string2.lower()
for pa in patterns_need_replace:
string1, string2 = re.sub(pa, ' ', string1, re.I).strip(), re.sub(pa, ' ', string2, re.I).strip()
for pa in patterns_need_delete:
string1, string2 = re.sub(pa, '', string1, re.I).strip(), re.sub(pa, '', string2, re.I).strip()
# print(string1)
# print(string2)
set1, set2 = set(string1.split(' ')), set(string2.split(' '))
return (set1.issubset(set2) or set2.issubset(set1), string1, string2)
merge(data, qs, 'name', 'Institution Name', pattern)
2.3.4 编辑距离匹配
编辑距离函数计算两个字符串的相似度,100为完全匹配,0为完全不匹配,当编辑距离大于某个特定阈值则认为匹配。(这里取90)
# 编辑距离
def calculate_string_similarity(string1, string2, decimal_places=2):
if not string1 or not string2:
return (0, string1, string2)
if string1 == string2:
return (100, string1, string2)
length1 = len(string1)
length2 = len(string2)
max_length = max(length1, length2)
distance_matrix = [[0] * (length2 + 1) for _ in range(length1 + 1)]
# 初始化编辑距离矩阵
for i in range(length1 + 1):
distance_matrix[i][0] = i
for j in range(length2 + 1):
distance_matrix[0][j] = j
# 填充编辑距离矩阵
for i in range(1, length1 + 1):
char1 = string1[i - 1]
for j in range(1, length2 + 1):
char2 = string2[j - 1]
cost = 0 if char1 == char2 else 1
# 计算编辑距离
distance_matrix[i][j] = min(
distance_matrix[i - 1][j] + 1, # 删除操作
distance_matrix[i][j - 1] + 1, # 插入操作
distance_matrix[i - 1][j - 1] + cost # 替换操作
)
# 计算相似度得分
edit_distance = distance_matrix[length1][length2]
similarity_score = (1 - edit_distance / max_length) * 100
return (round(similarity_score, decimal_places) > 90, string1, string2)
merge(data, qs, 'name', 'Institution Name', calculate_string_similarity)
2.3.5 包含匹配
a包含b,或b包含a则认为匹配。
func_contain = lambda x, y : ((x in y) or (y in x), x, y)
merge(data, qs, func_contain)
这一步需要注意误匹配问题,比如天津大学可能会与天津财经大学匹配。这个问题无法完全避免,可以先用精准匹配把天津大学完全匹配掉,然后在做剩下的匹配,但也无法完全避免误匹配,如果对精准度要求较高,则只能后续统一人工校对一遍。
2.3.6 忽略顺序匹配
有的字段所包含的字符完全一致但顺序不同,这时可以先将字符串按空格切分为列表,将列表转化为集合,比较集合是否相等。
def without_order(string1, string2):
set1, set2 = set(string1.split(' ')), set(string2.split(' '))
return (set1.issubset(set2) or set2.issubset(set1), string1, string2)
merge(data, qs, without_order)
前面所提到的步骤都可以结合使用,比如忽略顺序且相互包含。
2.3.7 人工匹配
每一步匹配结束后,可以查看尚未匹配的数据的情况,从而调整或生成新的匹配方法,对于最终仍未能匹配的个性化情况,则只能使用人工匹配与校对。可以根据维基百科为每个未匹配字段添加别名,然后据此重新匹配。
上述匹配方法可以扩展到很多其他脏数据匹配问题,类似的问题还有公司名称的匹配等,可作参考。
如果你对Python感兴趣,想要学习python,这里给大家分享一份Python全套学习资料,都是我自己学习时整理的,希望可以帮到你,一起加油!
😝有需要的小伙伴,可以V扫描下方二维码免费领取🆓
1️⃣零基础入门
① 学习路线
对于从来没有接触过Python的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
② 路线对应学习视频
还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~
③练习题
每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
2️⃣国内外Python书籍、文档
① 文档和书籍资料
3️⃣Python工具包+项目源码合集
①Python工具包
学习Python常用的开发软件都在这里了!每个都有详细的安装教程,保证你可以安装成功哦!
②Python实战案例
光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。100+实战案例源码等你来拿!
③Python小游戏源码
如果觉得上面的实战案例有点枯燥,可以试试自己用Python编写小游戏,让你的学习过程中增添一点趣味!
4️⃣Python面试题
我们学会了Python之后,有了技能就可以出去找工作啦!下面这些面试题是都来自阿里、腾讯、字节等一线互联网大厂,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
上述所有资料 ⚡️ ,朋友们如果有需要的,可以扫描下方👇👇👇二维码免费领取🆓