【问题标题】:Is there a way to modify this code to reduce run time?有没有办法修改此代码以减少运行时间?
【发布时间】:2021-07-22 10:56:35
【问题描述】:

所以我希望修改此代码以减少fuzzywuzzy 库的运行时间。目前,一个800行的数据集大约需要一个小时,当我在一个4.5K行的数据集上使用它时,它运行了将近6个小时,仍然没有结果。我不得不停止内核。

我需要在至少 20K 的数据上使用此代码。任何人都可以建议对此代码进行任何编辑以更快地获得结果吗?这是代码 -

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

df = pd.read_csv(r'path')
df.head()

data = df['Body']
print(data)

clean = []
threshold = 80 
for row in data:
  # score each sentence against each other
  # [('string', score),..]
  scores = process.extract(row, data, scorer=fuzz.token_set_ratio)
  # basic idea is if there is a close second match we want to evaluate 
  # and keep the longer of the two
  if scores[1][1] > threshold:
     clean.append(max([x[0] for x in scores[:2]],key=len))
  else:
     clean.append(scores[0][0])

# remove dupes
clean = set(clean)

#converting 'clean' list to dataframe and giving the column name for the cleaned column
clean_data = pd.DataFrame(clean, columns=['Body'])

clean_data.to_csv(r'path') 

这就是我的数据的样子 -

https://docs.google.com/spreadsheets/d/1p9RC9HznhdJFH4kFYdE_TgnHdoRf8P6gTEAkB3lQWEE/edit?usp=sharing

因此,如果您注意到第 14 和 15 行,并且第 19 和 20 行是部分重复的,我希望代码能够识别这些句子,并删除较短的句子。

更新-

我对@Darryl G给出的rapidfuzz解决方案做了一个小改动,现在代码看起来像这样-

`import pandas as pd
import numpy as np
import openpyxl
from rapidfuzz.fuzz import token_set_ratio as rapid_token_set_ratio
from rapidfuzz import process as process_rapid
from rapidfuzz import utils as rapid_utils
import time

df = pd.read_excel(r'path')

data = df['Body']
print(data)

def excel_sheet_to_dataframe(path):
    '''
        Loads sheet from Excel workbook using openpyxl
    '''
    wb = openpyxl.load_workbook(path)
    ws = wb.active
    data = ws.values
     # Get the first line in file as a header line
    columns = next(data)[0:]
    
    return pd.DataFrame(data, columns=columns)


clean_rapid = []
threshold = 80 

def process_rapid_fuzz(data):
    '''
        Process using rapid fuzz rather than fuzz_wuzzy
    '''
    series = (rapid_utils.default_process(d) for d in data)       # Pre-process to make lower-case and remove non-alphanumeric 
                                                                   # characters (generator)
    processed_data = pd.Series(series)   

    for query in processed_data:
        scores = process_rapid.extract(query, processed_data, scorer=rapid_token_set_ratio, score_cutoff=threshold)
        if len(scores) > 1 and scores[1][1] > threshold:
            m = max(scores[:2], key = lambda k:len(k[0]))                # Of up to two matches above threshold, takes longest
            clean_rapid.append(m[0])                                    # Saving the match index
        else:
            clean_rapid.append(query)

################ Testing
t0 = time.time()
df = excel_sheet_to_dataframe(r'path')   # Using Excel file in working folder

# Desired data in body column
data = df['Body'].dropna()                                           # Dropping None rows (few None rows at end after Excel import)

result_fuzzy_rapid = process_rapid_fuzz(data)
print(f'Elapsed time {time.time() - t0}')

# remove dupes
clean_rapid = set(clean_rapid)

#converting 'clean' list to dataframe and giving the column name for the cleaned column
clean_data = pd.DataFrame(clean_rapid, columns=['Body'])

#exporting the cleaned data
clean_data.to_excel(r'path')`

现在的问题是,在输出文件中,所有的句号等都被删除了。我怎样才能保留它们?

【问题讨论】:

  • 您能否提供您 CSV 文件中的一小段摘录?
  • 查看 maxbachmann 在Vectorizing or Speeding up Fuzzywuzzy String Matching on PANDAS Column 中的答案,该答案对类似问题产生了 10 倍的改进。
  • @AndyKnight 嗨,我添加了我的数据外观的 sn-p。希望对你有帮助
  • @Shrumo——不仅仅是使用一个数据框的单列(即列“org_name”)的引用。它不是使用fuzzy_wuzzy 来查找整列中每一行的最接近的匹配项吗?这似乎与您正在做的事情相似。
  • @DarrylG 嗨,我确实看到了该解决方案,但是,我的目标是识别此类重复项并将其删除。我分享的代码可以完成这项工作,只是它对于投标数据集非常耗时且不切实际。希望有一些意见。

标签: python data-cleaning fuzzywuzzy drop-duplicates rapidfuzz


【解决方案1】:

该方法基于Vectorizing or Speeding up Fuzzywuzzy String Matching on PANDAS Column 中的答案中的 RapidFuzz。

结果

  • OP Fuzzy Wuzzy 方法):2565.7 秒
  • RapidFuzz 方法:649.5 秒

因此:提高了 4 倍

快速模糊实施

import pandas as pd
import numpy as np
import openpyxl
from rapidfuzz.fuzz import token_set_ratio as rapid_token_set_ratio
from rapidfuzz import process as process_rapid
from rapidfuzz import utils as rapid_utils
import time

def excel_sheet_to_dataframe(path):
    '''
        Loads sheet from Excel workbook using openpyxl
    '''
    wb = openpyxl.load_workbook(path)
    ws = wb.active
    data = ws.values
     # Get the first line in file as a header line
    columns = next(data)[0:]
    
    return pd.DataFrame(data, columns=columns)

def process_rapid_fuzz(data):
    '''
        Process using rapid fuzz rather than fuzz_wuzzy
    '''
    series = (rapid_utils.default_process(d) for d in data)       # Pre-process to make lower-case and remove non-alphanumeric 
                                                                   # characters (generator)
    processed_data = pd.Series(series)   

    clean_rapid = []
    threshold = 80 
    n = 0
    for query in processed_data:
        scores = process_rapid.extract(query, processed_data, scorer=rapid_token_set_ratio, score_cutoff=threshold)
        
        m = max(scores[:2], key = lambda k:len(k[0]))                # Of up to two matches above threshold, takes longest
        clean_rapid.append(m[-1])                                    # Saving the match index
        
    clean_rapid = set(clean_rapid)                                   # remove duplicate indexes

    return data[clean_rapid]                                         # Get actual values by indexing to Pandas Series

################ Testing
t0 = time.time()
df = excel_sheet_to_dataframe('Duplicates1.xlsx')   # Using Excel file in working folder

# Desired data in body column
data = df['Body'].dropna()                                           # Dropping None rows (few None rows at end after Excel import)

result_fuzzy_rapid = process_rapid_fuzz(data)
print(f'Elapsed time {time.time() - t0}')

用于比较的发布代码版本

import pandas as pd
import numpy as np
from fuzzywuzzy import fuzz, process
import openpyxl
import time

def excel_sheet_to_dataframe(path):
    '''
        Loads sheet from Excel workbook using openpyxl
    '''
    wb = openpyxl.load_workbook(path)
    ws = wb.active
    data = ws.values
     # Get the first line in file as a header line
    columns = next(data)[0:]
    
    return pd.DataFrame(data, columns=columns)

def process_fuzzy_wuzzy(data):
    clean = []
    threshold = 80 
   
    for idx, query in enumerate(data):
        # score each sentence against each other
        # [('string', score),..]
        scores = process.extract(query, data, scorer=fuzz.token_set_ratio)
        # basic idea is if there is a close second match we want to evaluate 
        # and keep the longer of the two
        if len(scores) > 1 and scores[1][1] > threshold:    # If second one is close
            m = max(scores[:2], key=lambda k:len(k[0]))
            clean.append(m[-1])
        else:
            clean.append(idx)

    # remove duplicates
    clean = set(clean)
    return data[clean]                                        # Get actual values by indexing to Pandas Series

################ Testing
t0 = time.time()
# Get DataFrame for sheet from Excel
df = excel_sheet_to_dataframe('Duplicates1.xlsx')  

# Will Process data in 'body' column of DataFrame
data = df['Body'].dropna()                                    # Dropping None rows (few None rows at end after Excel import)

# Process Data (Pandas Series)
result_fuzzy_wuzzy = process_fuzzy_wuzzy(data)
print(f'Elapsed time {time.time() - t0}')

【讨论】:

  • @DarryIG 非常感谢!我会试试这段代码。我假设第二个代码是我的代码中的修改,所以这两个都行吗?抱歉,我还是新手,我在这段代码中看到了一些新功能,只是想确认一下
  • @Shrumo——它基本上是您的代码,但稍作修改,主要区别在于使用 Excel 文件。
  • @Shrumo--使用 Excel 而不是 Google Sheet 版本,因为使用 API 更复杂。
  • 好的,知道了!我会及时通知你它的表现。非常感谢您的帮助!
  • @Shrumo——刚刚注意到我的运行时比使用原始代码的运行时要好得多。我正在使用一台旧电脑(约 7 岁),所以不应该这样。也许我使用索引而不是实际数据的模式也加快了您的原始代码。
【解决方案2】:

这回答了您问题的第二部分。 processed_data 包含预处理的字符串,因此查询已经过预处理。默认情况下,预处理由process.extract 完成。 DarrylG 将此预处理移至循环前面,因此不会对字符串进行多次预处理。如果您不想在不对其进行预处理的情况下比较字符串,则可以直接遍历原始数据: 改变:

series = (rapid_utils.default_process(d) for d in data)
processed_data = pd.Series(series)   

for query in processed_data:

for query in data:

如果您想要原始行为,但想要结果中未处理的字符串,您可以使用结果字符串的索引来提取未处理的字符串。

def process_rapid_fuzz(data):
    '''
        Process using rapid fuzz rather than fuzz_wuzzy
    '''
    series = (rapid_utils.default_process(d) for d in data)
    processed_data = pd.Series(series)   

    for query in processed_data:
        scores = process_rapid.extract(query, processed_data,
            scorer=rapid_token_set_ratio,
            score_cutoff=threshold,
            limit=2)
        m = max(scores[:2], key = lambda k:len(k[0]))
        clean_rapid.append(data[m[2]])

在实现中还有一些可能的进一步改进:

  1. 您可以通过将processed_data 中的None 替换为None 来确保当前query 不会匹配,然后使用process.extractOne 查找高于阈值的下一个最佳匹配。这至少与process.extract 一样快,并且可能会明显更快。
  2. 您将processed_data 的每个元素与processed_data 的每个元素进行比较。这意味着您始终执行比较data[n] <-> data[m]data[m] <-> data[n],即使它们保证具有相同的结果。只需执行一次比较即可节省大约 50% 的运行时间。
def process_rapid_fuzz(data):
    '''
        Process using rapid fuzz rather than fuzz_wuzzy
    '''
    series = (rapid_utils.default_process(d) for d in data)
    processed_data = pd.Series(series)   

    for idx, query in enumerate(processed_data):
        # None is skipped by process.extract/extractOne, so it will never be part of the results
        processed_data[idx] = None
        match = process_rapid.extractOne(query, processed_data,
            scorer=rapid_token_set_ratio,
            score_cutoff=threshold)
        # compare the length using the original strings
        # alternatively len(match[0]) > len(query)
        # if you do want to compare the length of the processed version
        if match and len(data[match[2]]) > len(data[idx]):
            clean_rapid.append(data[match[2]])
        else:
            clean_rapid.append(data[idx])

【讨论】:

  • 抱歉这个问题,提前。在您建议的这一行中 - 'clean_rapid.append(data[m[2]])'。这是代码的“if”部分还是“else”?
  • 在这种情况下您不需要 if + else,因为查询是此版本中 process.extract 结果的一部分 -> 您有一个或两个结果。在这两种情况下,max 都有效。
  • 哦,好吧......所以当我在我的一个数据集中运行代码时,我收到一个错误,上面写着 - “类型错误:句子必须是一个字符串”。我的假设是可能有一些行没有字符串值。但是假设我想确定是哪一行导致了这个问题,我该怎么做呢?因为当我在 excel 中使用过滤器进行搜索时,我找不到任何非字符串的值
  • 您是否从输入中删除了空行?
  • 是的,我做到了.. 我的意思是,我首先从数据中删除了所有空白,然后运行此代码
猜你喜欢
  • 1970-01-01
  • 2016-07-24
  • 1970-01-01
  • 2021-10-22
  • 1970-01-01
  • 2021-09-11
  • 1970-01-01
  • 1970-01-01
  • 2012-10-08
相关资源
最近更新 更多