记录链接与字符串比较

记录链接(Record linkage)是一个用于融合不同数据集合的强大技巧。当我们处理一些字符输入错误或者拼写不一致的问题时,记录链接技巧变得尤为实用。记录链接的记录操作底层逻辑就是对字符串内的元素的插入、替换以及删除。可以实现这种算法的库有nltk,fuzzywuzzy,textdistance等等。我们这里使用fuzzywuzzy库。

1
2
3
4
5
from fuzzywuzzy import fuzz
fuzz.WRatio('Reeding', 'Reading')
Out [1]:
86

上面例子比较了两个字符串‘Reeding’和‘Reading’的相似度,相似度是一个0-100的数字,数字越大代表相似度越高。fuzzywuzzy认为字符串‘Reeding’和‘Reading’的相似度为86。下面再来看两个比较一部分字符串的例子:

1
2
3
4
5
6
7
8
#部分字符串比较
fuzz.WRatio('Houston Rockets', 'Rockets')
Out [2]:
90
#不同顺序的部分字符串比较
fuzz.WRatio('Houston Rockets vs Los Angeles Lakers', 'Lakers vs Rockets')
Out [3]:
86

比较数组:

1
2
3
4
5
6
7
8
9
10
import pandas as pd
from fuzzywuzzy import process
string = "Houston Rockets vs Los Angeles Lakers"
choices = pd.Series(['Rockets vs Lakers', 'Lakers vs Rockets',
'Houson vs Los Angeles', 'Heat vs Bulls'])
#limit = 2 选取数组里的两个元素
process.extract(string, choices, limit = 2)
Out [4]:
[('Rockets vs Lakers', 86, 0), ('Lakers vs Rockets', 86, 1)]

在对fuzzywuzzy库有一个基本的了解之后,下面正式进入对实际数据的操作演示。本文使用的数据为restaurant数据,数据包含了餐厅名称name,地址addr,所在城市city,电话phone和食物类型type。

数据下载地址:点击下载

1
2
3
4
5
6
7
8
9
10
11
from fuzzywuzzy import fuzz
import pandas as pd
from fuzzywuzzy import process
restaurants = pd.read_csv('datasets/restaurants.csv')
unique_types = restaurants['type'].unique()
print(unique_types)
Out [5]:
['american' 'america' 'american ( new )' 'mericano' 'americano' 'asia'
'asian' 'cajun' 'coffeebar' 'italian' 'italia' 'italiano' 'mexican'
'southern' 'southwestern' 'steakhouses']

通过查看餐厅的唯一类型,我们可以看到有一些类别其实是重复的,比如’american’ ‘america’ ‘american ( new )’ ‘mericano’ ‘americano’,这些其实是同一个类型的菜品american美国菜。可能是由于数据的录入错误导致一种类型的数据出现了很多种表达方式。我们当然可以使用.replace()函数进行替换,但是每个不对等的字符串都需要单独批量替换就显得有点笨拙了,所以使用记录链接的方式去判断字符串的相似度来解决这个问题更加高效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#创建标准类型数据框里面分别包含三种标准类型(asian,american还有italian)
cat = {'type':['asian', 'american', 'italian']}
categories = pd.DataFrame(cat)
# 循环categories里的匹配的三种类型
for cuisine in categories['type']:
# 找出相似度
matches = process.extract(cuisine, restaurants['type'],
limit = len(restaurants['type']))
# 找出相似度 >= 80的数据
for possible_match in matches:
if possible_match[1] >= 80:
# 找出相似的类型并且进行替换
matching_cuisine = restaurants['type'] == possible_match[0]
restaurants.loc[matching_cuisine, 'type'] = cuisine
#最后打印唯一值来验证一下mapping是否成功
print(restaurants['type'].unique())
Out [6]:
['american' 'asian' 'cajun' 'coffeebar' 'italian' 'southern'
'southwestern' 'steakhouses']

通过对比Out [5]和Out [6]的结果,可以看到在Out [5]中杂乱不堪的类别已经被我们根据字符串相似性的条件整理成了统一个类别。’american’ ‘america’ ‘american ( new )’ ‘mericano’ ‘americano’被统一整理成了‘american’,’asia’和’asian’被统一成了’asian’,’italian’ ‘italia’ ‘italiano’被统一成了’italian’。

记录链接(record linkage)和连接(join)的作用基本类似。不同的是,记录链接不需要数据完全匹配也可以对不同的dataframe进行合并。记录链接的基本操作思路是:

  1. 拿到两个数据框,例如Data A和Data B
  2. 生成配对
  3. 比较配对
  4. 为配生成相似度分数
  5. 连接数据

例如我们拿到一个新的数据框restaurants_new,这个数据框中我们发现了有一些餐厅的名称存在一些数据错误。这时,我们就需要记录链接来合并两个数据框:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
restaurants_new = pd.read_csv('datasets/restaurants_new.csv')
#创建indexer和对象去寻找可能的成对数据
indexer = recordlinkage.Index()
#避免创建太多的“对”,这里把以'type'创建的“对”给去掉
indexer.block('type')
#生成“对”
pairs = indexer.index(restaurants, restaurants_new)
print(pairs)
Out [7]:
MultiIndex([( 0, 0),
( 0, 1),
( 0, 7),
( 0, 12),
( 0, 13),
( 0, 20),
( 0, 27),
( 0, 28),
( 0, 39),
( 0, 40),
...
(312, 47),
(312, 57),
(312, 73),
(312, 75),
(312, 76),
(331, 18),
(332, 18),
(333, 18),
(334, 18),
(335, 18)],
length=3784)

在生成我们的成对数据之后,接下来需要做的是比较每列的数据,然后为比较打上相似分数,最后合并数据框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#创建比较对象
comp_cl = recordlinkage.Compare()
#为变量'city'和变量'type'找到精准匹配
comp_cl.exact('city', 'city', label='city')
comp_cl.exact('type', 'type', label = 'type')
#为餐厅名称'name'匹配0.8匹配度的数据
comp_cl.string('name', 'name', label='name', threshold = 0.8)
#计算潜在的匹配数据
potential_matches = comp_cl.compute(pairs, restaurants, restaurants_new)
print(potential_matches)
Out [8]:
city type name
0 0 0 1 0.0
1 0 1 0.0
7 0 1 0.0
12 0 1 0.0
13 0 1 0.0
... ... ...
331 18 0 1 0.0
332 18 0 1 0.0
333 18 0 1 0.0
334 18 0 1 0.0
335 18 0 1 0.0
[3784 rows x 3 columns]

变量里等于1代表是潜在的匹配,0代表不匹配。根据我们的实际情况,我们需要‘city’,’type’和’name’三者都为1的时候,我们才能确定这两个数据框中的某两条餐厅数据为同一家餐厅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#选出‘city’,’type’和’name’三者都为1的数据
matches = potential_matches[potential_matches.sum(axis=1) >= 3]
#获取三者完全匹配数据的index
matching_indices = matches.index.get_level_values(1)
#获取restaurants_new中没有出现在restaurants中的餐厅数据
non_dup = restaurants_new[~restaurants_new.index.isin(matching_indices)]
#将两个df中唯一的餐厅数据合并
full_restaurants = restaurants.append(non_dup)
print(full_restaurants)
Out [9]:
Unnamed: 0 name ... phone type
0 0 arnie morton's of chicago ... 3102461501 american
1 1 art's delicatessen ... 8187621221 american
2 2 campanile ... 2139381447 american
3 3 fenix ... 2138486677 american
4 4 grill on the alley ... 3102760615 american
.. ... ... ... ... ...
76 76 don ... 3102091422 italian
77 77 feast ... 3104750400 chinese
78 78 mulberry ... 8189068881 pizza
80 80 jiraffe ... 3109176671 californian
81 81 martha's ... 3103767786 american
[396 rows x 6 columns]

借此,我们就完成了对两个df的记录链接的所有操作。

0%