长度不相同的2系列/列之间的模糊查找

时间:2018-08-29 14:37:22

标签: python-3.x pandas difflib

我正在尝试在df1和df2之间的2个系列/列之间进行模糊查找,其中df1是字典文件(将用作基础文件),而df2是目标文件(将在其上查找)

import pandas as pd
df1 = pd.DataFrame(data ={'Brand_var':['Altmeister Bitter','Altos Las Hormigas Argentinian Wine','Amadeus Contri Sparkling Wine','Amadeus Cream Liqueur','Amadeus Sparkling Sparkling Wine']})
df2 = pd.DataFrame(data = {'Product':['1960 Altmeister 330ML CAN METAL','Hormi 12 Yr Bottle','test']})

我在SO中寻找一些解决方案,不幸的是似乎没有找到解决方案。

使用过:

df3 = df2['ProductLongDesc'].apply(lambda x: difflib.get_close_matches(x, df1['Brand_var'])[0])

还:

df3 = df2['Product'].apply(lambda x: difflib.get_close_matches(x, df1['Brand_var']))

第一个给我一个索引错误,第二个给我一个索引。

我想要的输出是使用模糊查找在df1项和df2项之间打印映射,并为各自的匹配项同时打印Brand_var和Product。

所需的输出:

Brand_var                            Product
Altmeister Bitter                    1960 Altmeister 330ML CAN METAL
Altos Las Hormigas Argentinian Wine  Hormi 12 Yr Bottle

对于不匹配的项目,例如df2中的测试,可以忽略。

注意:匹配的字符串名称也可能不相同,因为其中可能缺少1或2个字母。 :(

预先感谢您抽出宝贵的时间解决此问题。 :)

1 个答案:

答案 0 :(得分:2)

如果安装fuzzywuzzy,仍然会遇到一个问题,即如何选择适当的启发式方法来选择正确的产品并削减那些选择不正确的产品(以下说明)

安装fuzzywuzzy

pip install fuzzywuzzy

fuzzywuzzy有几种用于比率计算的方法(examples on github)。您面临的问题是:如何选择最佳?我对您的数据进行了尝试,但是它们全部失败了。 代码:

import pandas as pd
import numpy as np
from fuzzywuzzy import fuzz

# df1 = ...
# df2 = ...

def get_top_by_ratio(x, df2):
    product_values = df2.Product.values
    # compare two strings by characters
    ratio = np.array([fuzz.partial_ratio(x, val) for val in product_values])
    argmax = np.argmax(ratio)
    rating = ratio[argmax]
    linked_product = product_values[argmax]
    return rating, linked_product

将此功能应用于您的数据:

partial_ratio = (df1.Brand_var.apply(lambda x: get_top_by_ratio(x, df2))
                    .apply(pd.Series)  # convert returned Series of tuples into pd.DataFrame
                    .rename(columns={0: 'ratio', 1: 'Product'}))  # just rename columns
print(partial_ratio)
Out:
0     65  1960 Altmeister 330ML CAN METAL  # Altmeister Bitter 
1     50                             test  # Altos Las Hormigas Argentinian Wine
2     33                             test
3     50                             test
4     50                             test

那不好。其他fuzz.ratiofuzz.token_sort_ratio等比率方法也失败了。

因此,我想扩展试探法来比较单词,不仅字符可能有所帮助。定义一个函数,该函数将根据您的数据创建词汇表,对所有句子进行编码,并使用更复杂的启发式查找词:

def create_vocab(df1, df2):
     # Leave 0 index free for unknow words
    all_words = set((df1.Brand_var.str.cat(sep=' ') + df2.Product.str.cat(sep=' ')).split())
    vocab = dict([(i + 1, w) for i, w in enumerate(all_words)])
    return vocab


def encode(string, vocab):
    """This function encodes a sting with vocabulary"""
    return [vocab[w] if w in vocab else 0 for w in string.split()]

定义新的启发式:

def get_top_with_heuristic(x, df2, vocab):
    product_values = df2.Product.values
    # compare two strings by characters
    ratio_per_char = np.array([fuzz.partial_ratio(x, val) for val in product_values])
    # compare two string by words
    ratio_per_word = np.array([fuzz.partial_ratio(x, encode(val, vocab)) for val in product_values])
    ratio = ratio_per_char + ratio_per_word
    argmax = np.argmax(ratio)
    rating = ratio[argmax]
    linked_product = product_values[argmax]
    return rating, linked_product

创建词汇表,对数据应用复杂的启发式:

vocab = create_vocab(df1, df2)
heuristic_rating = (df1.Brand_var.apply(lambda x: get_top_with_heuristic(x, df2, vocab))
                    .apply(pd.Series)
                    .rename(columns={0: 'ratio', 1: 'Product'}))
print(heuristic_rating)
Out: 
   ratio                          Product
0     73  1960 Altmeister 330ML CAN METAL  # Altmeister Bitter 
1     61               Hormi 12 Yr Bottle  # Altos Las Hormigas Argentinian Wine
2     45               Hormi 12 Yr Bottle
3     50                             test
4     50                             test

这似乎是正确的!将此数据帧连接到df1,更改索引:

result_heuristic = pd.concat((df1, heuristic_rating), axis=1).set_index('Brand_var')
print(result_heuristic)
Out:

                                     ratio                          Product
Brand_var                                                                  
Altmeister Bitter                       73  1960 Altmeister 330ML CAN METAL
Altos Las Hormigas Argentinian Wine     61               Hormi 12 Yr Bottle
Amadeus Contri Sparkling Wine           45               Hormi 12 Yr Bottle
Amadeus Cream Liqueur                   50                             test
Amadeus Sparkling Sparkling Wine        50                             test

现在,您应该选择一些经验法则来剪切错误的数据。对于此示例,ratio <= 50效果很好,但是您可能需要一些研究来定义最佳启发式和正确阈值。另外,您还是会得到一些错误。选择可接受的错误率,即2%,5%...并改进算法直到达到为止(此任务类似于验证机器学习分类算法)。

剪切不正确的“预测”:

result = result_heuristic[result_heuristic.ratio > 50][['Product']]
print(result)

Out:                                                        Product
Brand_var                                                           
Altmeister Bitter                    1960 Altmeister 330ML CAN METAL
Altos Las Hormigas Argentinian Wine               Hormi 12 Yr Bottle

希望有帮助!

PS 当然,此算法非常慢,当您进行“优化”时,您应该进行一些优化,例如,缓存差异等。