【问题标题】:Finding and replacing values in a list based on fuzzy matching基于模糊匹配查找和替换列表中的值
【发布时间】:2021-05-15 21:40:38
【问题描述】:

我正在尝试循环遍历 pandas 中列的值并更改所有相似的值,以使它们协调一致。我首先将该列提取为一个列表,并希望循环遍历每一行,用相似的值替换找到的相似值,然后将列表放回数据框中替换该列。例如这样的列:

Cool
Awesome
cool
CoOl
Awesum
Awesome
Mathss
Math
Maths
Mathss

会变成:

CoOl
Awesome
coOol
CoOl
Awesome
Awesome
Mathss
Mathss
Mathss
Mathss

代码如下:

def matchbrands():
    conn = sqlite3.connect('/Users/XXX/db.sqlite3')
    c = conn.cursor()
    matchbrands_df = pd.read_sql_query("SELECT * from removeduplicates", conn)

    brands = [x for x in matchbrands_df['brand']]

    i=1

    for x in brands:
        if fuzz.token_sort_ratio(x, brands[i]) > 85:
            x = brands[i]
        else:
            i += 1

    n = matchbrands_df.columns[7]
    matchbrands_df.drop(n, axis=1, inplace=True)
    matchbrands_df[n] = brands

    matchbrands_df.to_csv('/Users/XXX/matchedbrands.csv')
    matchbrands_df.to_sql('removeduplicates', conn, if_exists="replace")

但是这根本不会改变列。我不确定为什么。任何帮助将不胜感激

【问题讨论】:

  • 你上面的代码应该是一个最小可重现的例子。
  • 可能首先使用print() 来检查变量中的内容。分配x = ... 不会改变brands 中的原始值——您必须使用索引并执行brand[index] = brands[i]。所有这些for-loop 看起来很奇怪,我不相信它是否真的能满足你的要求。你应该在这个for-loop 中使用print() 来检查它的作用。至于我,它可能需要第二个for-loop 才能正确完成,但我不知道fuzz.token_sort_ratio 是如何工作的。也许可以使用 .apply 来完成,而不是将列转换为列表。
  • 较短:brands = matchbrands_df['brand'].to_list()brands = list(matchbrands_df['brand'])

标签: python pandas dataframe replace fuzzywuzzy


【解决方案1】:

你的代码没有意义。

首先:使用x =...,您无法更改列表brands 上的值。你需要brands[index] = ...

第二:它需要嵌套for-loop 来比较xbrands 中的所有其他词

for index, word in enumerate(brands):
    for other in brands[index+1:]:
        #print(word, other, fuzz.token_sort_ratio(word, other))
        if fuzz.token_sort_ratio(word, other) > 85:
            brands[index] = other

最少的工作代码

import pandas as pd
import fuzzywuzzy.fuzz as fuzz

data = {'brands':
'''Cool
Awesome
cool
CoOl
Awesum
Awesome
Mathss
Math
Maths
Mathss'''.split('\n')
}  # rows

df = pd.DataFrame(data)

print('--- before ---')
print(df)

brands = df['brands'].to_list()

print('--- changes ---')
for index, word in enumerate(brands):
    #for other_index, other_word in enumerate(brands):
    for other_index, other_word in enumerate(brands[index+1:], index+1):
        #if word != other_word:
            result = fuzz.token_sort_ratio(word, other_word)
            
            if result > 85:
                print(f'OK | {result:3} | {index:2} {word:7} -> {other_index:2} {other_word}')                
            elif result > 50:
                print(f'   | {result:3} | {index:2} {word:7} -> {other_index:2} {other_word}')
                
            if result > 85:
                brands[index] = other_word
                #break
                #word = other_word

df['brands'] = brands

print('--- after ---')
print(df)

结果:

--- before ---
    brands
0     Cool
1  Awesome
2     cool
3     CoOl
4   Awesum
5  Awesome
6   Mathss
7     Math
8    Maths
9   Mathss
--- changes ---
OK | 100 |  0 Cool    ->  2 cool
OK | 100 |  0 Cool    ->  3 CoOl
   |  77 |  1 Awesome ->  4 Awesum
OK | 100 |  1 Awesome ->  5 Awesome
OK | 100 |  2 cool    ->  3 CoOl
   |  77 |  4 Awesum  ->  5 Awesome
   |  80 |  6 Mathss  ->  7 Math
OK |  91 |  6 Mathss  ->  8 Maths
OK | 100 |  6 Mathss  ->  9 Mathss
OK |  89 |  7 Math    ->  8 Maths
   |  80 |  7 Math    ->  9 Mathss
OK |  91 |  8 Maths   ->  9 Mathss
--- after ---
    brands
0     CoOl
1  Awesome
2     CoOl
3     CoOl
4   Awesum
5  Awesome
6   Mathss
7    Maths
8   Mathss
9   Mathss

它不会将Awesum 更改为Awesome,因为它会得到77

它不会将Math 更改为Mathss,因为它得到了80。但它得到89Maths

如果你在for-loop 中使用word = other_word,那么它可以将Math 转换为Maths (89),然后将Maths 转换为Mathss (91)。但是这种方式可能会改变很多次,最后变成原来可以给出的值比85小得多的单词。 75 而不是 85 也可以获得预期结果。

但是这种方法得到的最后一个单词的值是>85,而不是最大的值——所以可以有更好的匹配单词,它不会使用它。使用 break 它得到>85 的第一个单词。也许它应该使用>85 获取所有单词并选择具有最大价值的单词。它必须跳过相同但在不同行中的单词。但这一切都会造成奇怪的情况。

在代码中的 cmets 中,我保留了其他修改的想法。


编辑:

>75 和颜色相同。

import pandas as pd
import fuzzywuzzy.fuzz as fuzz
from colorama import Fore as FG, Back as BG, Style as ST

data = {'brands':
'''Cool
Awesome
cool
CoOl
Awesum
Awesome
Mathss
Math
Maths
Mathss'''.split('\n')
}  # rows

df = pd.DataFrame(data)

print('--- before ---')
print(df)

brands = df['brands'].to_list()

print('--- changes ---')
for index, word in enumerate(brands):
    print('-', index, '-')
    #for other_index, other_word in enumerate(brands):
    for other_index, other_word in enumerate(brands[index+1:], index+1):
        #if word != other_word:
            result = fuzz.token_sort_ratio(word, other_word)
            
            if result > 85:
                color = ST.BRIGHT + FG.GREEN
                info  = 'OK'
            elif result > 75:
                color = ST.BRIGHT + FG.YELLOW
                info  = ' ?'
            elif result > 50:
                color = ST.BRIGHT + FG.WHITE
                info  = '  '
            else:
                color = ST.BRIGHT + FG.RED
                info  = ' -'
            
            print(f'{color}{info} | {result:3} | {index:2} {word:7} -> {other_index:2} {other_word}{ST.RESET_ALL}')
                
            if result > 75:
                brands[index] = other_word
                #break
                #word = other_word
    
df['brands'] = brands

print('--- after ---')
print(df)

【讨论】:

  • 我在考虑使用df.apply(func),但这只会减少外部for-loop`,它仍然需要内部for-loop for other_index, other_word内部func
  • 顺便说一句:我很少更改设置颜色的代码。并添加了print('-')
  • 嗨@furas,你能帮我吗?:stackoverflow.com/q/70051704/6907424 谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-24
  • 1970-01-01
相关资源
最近更新 更多