105

我有两个要基于列合并的 DataFrame。但是,由于拼写不同、空格数量不同、变音符号的缺失/存在,只要它们彼此相似,我希望能够合并。

任何相似性算法都可以(soundex、Levenshtein、difflib's)。

假设一个 DataFrame 具有以下数据:

df1 = DataFrame([[1],[2],[3],[4],[5]], index=['one','two','three','four','five'], columns=['number'])

       number
one         1
two         2
three       3
four        4
five        5

df2 = DataFrame([['a'],['b'],['c'],['d'],['e']], index=['one','too','three','fours','five'], columns=['letter'])

      letter
one        a
too        b
three      c
fours      d
five       e

然后我想得到结果 DataFrame

       number letter
one         1      a
two         2      b
three       3      c
four        4      d
five        5      e
4

13 回答 13

100

与@locojay 建议类似,您可以将difflib'sget_close_matches应用于df2' 索引,然后应用 a join

In [23]: import difflib 

In [24]: difflib.get_close_matches
Out[24]: <function difflib.get_close_matches>

In [25]: df2.index = df2.index.map(lambda x: difflib.get_close_matches(x, df1.index)[0])

In [26]: df2
Out[26]: 
      letter
one        a
two        b
three      c
four       d
five       e

In [31]: df1.join(df2)
Out[31]: 
       number letter
one         1      a
two         2      b
three       3      c
four        4      d
five        5      e

.

如果这些是列,那么您可以同样适用于该列merge

df1 = DataFrame([[1,'one'],[2,'two'],[3,'three'],[4,'four'],[5,'five']], columns=['number', 'name'])
df2 = DataFrame([['a','one'],['b','too'],['c','three'],['d','fours'],['e','five']], columns=['letter', 'name'])

df2['name'] = df2['name'].apply(lambda x: difflib.get_close_matches(x, df1['name'])[0])
df1.merge(df2)
于 2012-12-03T10:06:04.680 回答
56

使用fuzzywuzzy

由于该软件包没有示例,因此fuzzywuzzy我编写了一个函数,它将根据您可以作为用户设置的阈值返回所有匹配项:


示例数据框

df1 = pd.DataFrame({'Key':['Apple', 'Banana', 'Orange', 'Strawberry']})
df2 = pd.DataFrame({'Key':['Aple', 'Mango', 'Orag', 'Straw', 'Bannanna', 'Berry']})

# df1
          Key
0       Apple
1      Banana
2      Orange
3  Strawberry

# df2
        Key
0      Aple
1     Mango
2      Orag
3     Straw
4  Bannanna
5     Berry

模糊匹配函数

def fuzzy_merge(df_1, df_2, key1, key2, threshold=90, limit=2):
    """
    :param df_1: the left table to join
    :param df_2: the right table to join
    :param key1: key column of the left table
    :param key2: key column of the right table
    :param threshold: how close the matches should be to return a match, based on Levenshtein distance
    :param limit: the amount of matches that will get returned, these are sorted high to low
    :return: dataframe with boths keys and matches
    """
    s = df_2[key2].tolist()
    
    m = df_1[key1].apply(lambda x: process.extract(x, s, limit=limit))    
    df_1['matches'] = m
    
    m2 = df_1['matches'].apply(lambda x: ', '.join([i[0] for i in x if i[1] >= threshold]))
    df_1['matches'] = m2
    
    return df_1

在数据帧上使用我们的函数: #1

from fuzzywuzzy import fuzz
from fuzzywuzzy import process

fuzzy_merge(df1, df2, 'Key', 'Key', threshold=80)

          Key       matches
0       Apple          Aple
1      Banana      Bannanna
2      Orange          Orag
3  Strawberry  Straw, Berry

在数据帧上使用我们的函数: #2

df1 = pd.DataFrame({'Col1':['Microsoft', 'Google', 'Amazon', 'IBM']})
df2 = pd.DataFrame({'Col2':['Mcrsoft', 'gogle', 'Amason', 'BIM']})

fuzzy_merge(df1, df2, 'Col1', 'Col2', 80)

        Col1  matches
0  Microsoft  Mcrsoft
1     Google    gogle
2     Amazon   Amason
3        IBM         

安装:

pip install fuzzywuzzy

蟒蛇

conda install -c conda-forge fuzzywuzzy
于 2019-05-26T16:42:53.933 回答
21

我写了一个 Python 包来解决这个问题:

pip install fuzzymatcher

您可以在此处找到 repo并此处找到文档。

基本用法:

给定两个要模糊连接的数据框df_leftdf_right,您可以编写以下内容:

from fuzzymatcher import link_table, fuzzy_left_join

# Columns to match on from df_left
left_on = ["fname", "mname", "lname",  "dob"]

# Columns to match on from df_right
right_on = ["name", "middlename", "surname", "date"]

# The link table potentially contains several matches for each record
fuzzymatcher.link_table(df_left, df_right, left_on, right_on)

或者,如果您只想链接最接近的匹配项:

fuzzymatcher.fuzzy_left_join(df_left, df_right, left_on, right_on)
于 2017-12-02T09:15:43.890 回答
13

我会使用 Jaro-Winkler,因为它是目前可用的最高效和最准确的近似字符串匹配算法之一 [ Cohen, et al. ],[温克勒]。

这就是我使用jellyfish包中的 Jaro-Winkler 的方法:

def get_closest_match(x, list_strings):

  best_match = None
  highest_jw = 0

  for current_string in list_strings:
    current_score = jellyfish.jaro_winkler(x, current_string)

    if(current_score > highest_jw):
      highest_jw = current_score
      best_match = current_string

  return best_match

df1 = pandas.DataFrame([[1],[2],[3],[4],[5]], index=['one','two','three','four','five'], columns=['number'])
df2 = pandas.DataFrame([['a'],['b'],['c'],['d'],['e']], index=['one','too','three','fours','five'], columns=['letter'])

df2.index = df2.index.map(lambda x: get_closest_match(x, df1.index))

df1.join(df2)

输出:

    number  letter
one     1   a
two     2   b
three   3   c
four    4   d
five    5   e
于 2016-05-29T01:54:05.510 回答
7

http://pandas.pydata.org/pandas-docs/dev/merging.html没有钩子函数可以即时执行此操作。不过会很好...

我将只做一个单独的步骤并使用 difflib getclosest_matches 在两个数据帧之一中创建一个新列,并在模糊匹配列上进行合并/连接

于 2012-11-30T19:56:02.180 回答
6

对于一般方法:fuzzy_merge

对于更一般的场景,我们想要合并两个数据帧中包含稍微不同的字符串的列,以下函数使用difflib.get_close_matcheswithmerge以模仿 pandas 的功能,merge但具有模糊匹配:

import difflib 

def fuzzy_merge(df1, df2, left_on, right_on, how='inner', cutoff=0.6):
    df_other= df2.copy()
    df_other[left_on] = [get_closest_match(x, df1[left_on], cutoff) 
                         for x in df_other[right_on]]
    return df1.merge(df_other, on=left_on, how=how)

def get_closest_match(x, other, cutoff):
    matches = difflib.get_close_matches(x, other, cutoff=cutoff)
    return matches[0] if matches else None

以下是一些带有两个示例数据框的用例:

print(df1)

     key   number
0    one       1
1    two       2
2  three       3
3   four       4
4   five       5

print(df2)

                 key_close  letter
0                    three      c
1                      one      a
2                      too      b
3                    fours      d
4  a very different string      e

通过上面的例子,我们会得到:

fuzzy_merge(df1, df2, left_on='key', right_on='key_close')

     key  number key_close letter
0    one       1       one      a
1    two       2       too      b
2  three       3     three      c
3   four       4     fours      d

我们可以使用以下方法进行左连接:

fuzzy_merge(df1, df2, left_on='key', right_on='key_close', how='left')

     key  number key_close letter
0    one       1       one      a
1    two       2       too      b
2  three       3     three      c
3   four       4     fours      d
4   five       5       NaN    NaN

对于右连接,我们将左侧数据框中的所有不匹配键用于None

fuzzy_merge(df1, df2, left_on='key', right_on='key_close', how='right')

     key  number                key_close letter
0    one     1.0                      one      a
1    two     2.0                      too      b
2  three     3.0                    three      c
3   four     4.0                    fours      d
4   None     NaN  a very different string      e

另请注意,如果在截止范围内没有匹配项,则将返回一个空列表。在共享示例中,如果我们将最后一个索引更改为:difflib.get_close_matches df2

print(df2)

                          letter
one                          a
too                          b
three                        c
fours                        d
a very different string      e

我们会得到一个index out of range错误:

df2.index.map(lambda x: difflib.get_close_matches(x, df1.index)[0])

IndexError:列表索引超出范围

为了解决这个问题,上面的函数get_closest_match将通过索引返回的列表来返回最接近的匹配,difflib.get_close_matches 只有当它实际上包含任何匹配时。

于 2020-03-28T23:56:11.547 回答
4

我使用了 Fuzzymatcher 包,这对我来说效果很好。访问此链接以获取更多详细信息。

使用以下命令安装

pip install fuzzymatcher

下面是示例代码(上面已经由 RobinL 提交)

from fuzzymatcher import link_table, fuzzy_left_join

# Columns to match on from df_left
left_on = ["fname", "mname", "lname",  "dob"]

# Columns to match on from df_right
right_on = ["name", "middlename", "surname", "date"]

# The link table potentially contains several matches for each record
fuzzymatcher.link_table(df_left, df_right, left_on, right_on)

您可能会遇到的错误

  1. ZeroDivisionError: float 除以零--->参考这个 链接解决它
  2. OperationalError: No Such Module:fts4 --> 从这里下载 sqlite3.dll并替换 python 或 anaconda DLLs 文件夹中的 DLL 文件。

优点:

  1. 工作更快。 就我而言,我将一个包含 3000 行的数据帧与另一个包含 170,000 条记录的数据帧进行了比较。这也使用 SQLite3 跨文本搜索。比很多人都快
  2. 可以检查多个列和 2 个数据框就我而言,我正在根据地址和公司名称寻找最接近的匹配项。有时,公司名称可能相同,但地址也是检查的好东西。
  3. 为您提供同一记录的所有最接近匹配项的得分。你选择什么是截止分数。

缺点:

  1. 原包安装有bug
  2. 还安装了所需的 C++ 和 Visual Studio
  3. 不适用于 64 位 anaconda/Python
于 2019-07-12T20:18:11.497 回答
3

有一个名为的包可以fuzzy_pandas使用levenshteinjaro和方法。这里有一些很好的例子metaphonebilenco

import pandas as pd
import fuzzy_pandas as fpd

df1 = pd.DataFrame({'Key':['Apple', 'Banana', 'Orange', 'Strawberry']})
df2 = pd.DataFrame({'Key':['Aple', 'Mango', 'Orag', 'Straw', 'Bannanna', 'Berry']})

results = fpd.fuzzy_merge(df1, df2,
            left_on='Key',
            right_on='Key',
            method='levenshtein',
            threshold=0.6)

results.head()

  Key    Key
0 Apple  Aple
1 Banana Bannanna
2 Orange Orag
于 2020-03-11T10:43:18.020 回答
2

提醒一下,这基本上是有效的,除非找不到匹配项,或者任一列中都有 NaN。get_close_matches我发现应用以下功能更容易,而不是直接应用。NaN 替换的选择很大程度上取决于您的数据集。

def fuzzy_match(a, b):
    left = '1' if pd.isnull(a) else a
    right = b.fillna('2')
    out = difflib.get_close_matches(left, right)
    return out[0] if out else np.NaN
于 2014-08-07T18:33:26.430 回答
2

您可以为此使用d6tjoin

import d6tjoin.top1
d6tjoin.top1.MergeTop1(df1.reset_index(),df2.reset_index(),
       fuzzy_left_on=['index'],fuzzy_right_on=['index']).merge()['merged']

index number index_right letter 0 one 1 one a 1 two 2 too b 2 three 3 three c 3 four 4 fours d 4 five 5 five e

它具有多种附加功能,例如:

  • 检查加入质量,加入前和加入后
  • 自定义相似度函数,例如编辑距离 vs 汉明距离
  • 指定最大距离
  • 多核计算

详情见

于 2018-08-15T13:00:17.390 回答
1

在匹配in的现有行为和关键字时,我fuzzywuzz以非常简单的方式使用。mergepandas

只需指定您接受threshold的匹配项(介于0和之间100):

from fuzzywuzzy import process

def fuzzy_merge(df, df2, on=None, left_on=None, right_on=None, how='inner', threshold=80):
    
    def fuzzy_apply(x, df, column, threshold=threshold):
        if type(x)!=str:
            return None
        
        match, score, *_ = process.extract(x, df[column], limit=1)[0]
            
        if score >= threshold:
            return match

        else:
            return None
    
    if on is not None:
        left_on = on
        right_on = on

    # create temp column as the best fuzzy match (or None!)
    df2['tmp'] = df2[right_on].apply(
        fuzzy_apply, 
        df=df, 
        column=left_on, 
        threshold=threshold
    )

    merged_df = df.merge(df2, how=how, left_on=left_on, right_on='tmp')
    
    del merged_df['tmp']
    
    return merged_df

使用示例数据进行尝试:

df1 = pd.DataFrame({'Key':['Apple', 'Banana', 'Orange', 'Strawberry']})

df2 = pd.DataFrame({'Key':['Aple', 'Mango', 'Orag', 'Straw', 'Bannanna', 'Berry']})

fuzzy_merge(df, df2, on='Key', threshold=80)
于 2021-03-27T15:48:56.537 回答
0

对于更复杂的用例来匹配具有多列的行,您可以使用recordlinkagepackage.json 。recordlinkage提供所有工具来模糊匹配pandas数据帧之间的行,这有助于在合并时对数据进行重复数据删除。我在这里写了一篇关于包的详细文章

于 2020-11-19T06:23:56.653 回答
0

如果连接轴是数字,这也可以用于匹配具有指定容差的索引:

def fuzzy_left_join(df1, df2, tol=None):
    index1 = df1.index.values
    index2 = df2.index.values

    diff = np.abs(index1.reshape((-1, 1)) - index2)
    mask_j = np.argmin(diff, axis=1)  # min. of each column
    mask_i = np.arange(mask_j.shape[0])

    df1_ = df1.iloc[mask_i]
    df2_ = df2.iloc[mask_j]

    if tol is not None:
        mask = np.abs(df2_.index.values - df1_.index.values) <= tol
        df1_ = df1_.loc[mask]
        df2_ = df2_.loc[mask]

    df2_.index = df1_.index

    out = pd.concat([df1_, df2_], axis=1)
    return out
于 2021-02-27T18:11:10.290 回答