【问题标题】:Find matches between list and excel entries and mark them查找列表和 Excel 条目之间的匹配项并标记它们
【发布时间】:2022-08-22 13:50:22
【问题描述】:

我有一个包含各种文档名称的列表,可能如下所示:

C:\\folder\\somepath\\1234_456_2.pdf
C:\\folder\\somepath\\whatever\\5932194_123.pdf
C:\\folder\\somepath\\2022_10_10_5932194_123.pdf
C:\\folder\\somepath\\January\\123_5932192.pdf
C:\\folder\\somepath\\whatever\\123_59321911_1234.pdf
C:\\folder\\somepath\\whatever\\123_5932197.pdf
...

该列表不会太大,包含约 3000 个条目。

在我的 excel 文件中,我有一列包含一堆值,总共大约 100 万个。如果该列的单元格中的值存在于字符串中,则整行的背景颜色将为绿色。

我尝试用 openpyxl 解决这个问题,它在一定程度上起作用。

for col in sheet.columns:
    column = get_column_letter(col[0].column)
    if sheet[column + str(1)].value == \"Column I am looking for\":
        for j in range(1, sheet.max_row):
            if str(sheet[column + str(j)].value) in str(the_list):
                 column2 = get_column_letter(col[0].column)
                 sheet[column2 + str(j)].fill = PatternFill(\"solid\", fgColor=\"92D050\")

它在较小的文件上运行良好,但在较大的文件上却需要很长时间。我不知道如何在 pandas 中实现类似的东西,也不知道如何使用 openpyxl 让它运行得更好。我怎样才能解决这个问题?

编辑: 我忘了添加 excel 文件中的列可能是什么样的。

Some Col.    Other Col.    Relevant Col.
asdf         1111          5932194
fdsa         3214          5342133

if str(sheet[column + str(j)].value) in str(the_list): 由于具有相关值的列是 int,因此我必须将其转换为字符串。

我发现一旦 excel 文件变得太大,每个单独的单元格检查都会相当缓慢。

  • 四个嵌套的for循环?有你的问题。仔细阅读 openpyxl,您应该能够改进:API 的存在是有原因的。
  • @CharlieClark我已经做了一些工作,但我没有得出任何其他方式的结论,阅读openpyxl的文档一无所获。
  • 您要查找的列是否始终相同?跨工作簿还是跨您正在检查的列表中的每个项目?
  • @Alan 保证始终具有相同的名称,这就是我遍历列以查找它的原因。但是,可以假设它总是在同一列中。
  • @Vitalizzare 我添加了一个示例作为编辑。至于为什么我将列表转换为str(),如果我不这样做,它就不会找到它应该找到的任何匹配项。

标签: python pandas openpyxl


【解决方案1】:

解释

您访问 Excel 文件的频率越高,该过程所需的时间就越长。在您的示例中,您访问文件(方式)太多次。这就是为什么它这么慢。

首先,在达到所需的列之前,不要迭代所有列。您应该直接从正确的列开始。

其次,您可以在一次访问中将整个列表作为 pandas 数据框检索,而不是在每个单元格中访问文件(在您的示例中为 100 万次)。

最后,pandas 并不是为迭代大型数据帧而设计的。当可能有更快的解决方案时,请避免迭代。您可以过滤以直接检索它们,而不是迭代数据框以查找匹配值。欲了解更多信息,请参阅How to iterate over rows in a DataFrame in Pandas

不幸的是,不可能在一次调用中将所有单元格设置为绿色,因为中间可能有一些白色单元格。所以你将不得不迭代来改变背景颜色。欲了解更多信息,请参阅How to get/set different colours of the same range from an Excel file using xlwings in python?

示例 1

我将从一个与列表值完全匹配的简单示例开始。如果我们想检查这个 Excel 文件的 C 列:

并将其与此匹配值列表进行比较:

list_of_values = ["Matching 1", "Matching 2", "Matching 3"]

以下代码将匹配值设置为绿色。

import xlwings as xw

# Define the RGB code of the color green
green = (226, 239, 218)

# Define the matching values
list_of_values = ["Matching 1", "Matching 2", "Matching 3"]

# Connect to the example Excel file
wb = xw.Book('Test.xlsx')
sht = wb.sheets['Sheet1']
column_to_inspect = 'C'

# Retrieve the values of the column to inspect
df = sht.range('{column}1:{column}7'.format(column=column_to_inspect)).options(pd.DataFrame, index=False, header=True).value

# Set in green the matching values
for i in df[df['Path'].isin(list_of_values)].index:
    # +2 is needed as you skip the Header and the index start iterating at 0, excel starts at 1. Increase this value if your first row is not 2.
    sht["{column}{row}".format(column=column_to_inspect, row=i+2)].color = green

示例 2

最后,这个示例应该与您需要的非常相似,因为它基于子字符串列表。

import xlwings as xw

# Define the RGB code of the color green
green = (226, 239, 218)

# Define the matching values
list_of_substrings = ["USA", "UK", "Japan"]

# Connect to the example Excel file
wb = xw.Book('Test.xlsx')
sht = wb.sheets['Sheet1']
column_to_inspect = 'C'

# Retrieve the values of the column to inspect
df = sht.range('{column}1:{column}7'.format(column=column_to_inspect)).options(pd.DataFrame, index=False, header=True).value

# Set in green the cells that contain a substring
for i in df[df['Path'].str.contains('|'.join(list_of_substrings))].index:
    # +2 is needed as you skip the Header and the index start iterating at 0, excel starts at 1. Increase this value if your first row is not 2.
    sht["{column}{row}".format(column=column_to_inspect, row=i+2)].color = green

更多信息How to test if a string contains one of the substrings in a list, in pandas?

【讨论】:

    【解决方案2】:

    我支持 Romain 的 cmets,您应该限制读取文件的次数,如果可以使用 set 操作,迭代是不好的。

    在我看来,您可以在操作中设置行颜色,而无需下拉到 xlwings 来执行此操作。

    我将在下面列出一些示例来解释不同的方法:

    选项1- 迭代

    import numpy as np
    import pandas as pd
    
    # Set up the requirements for the row to be coloured
    # this will make more sense later
    def color(row):
        if row["check"] == "matched":
            return ['background-color: red'] * len(row)
        return [''] * len(row)
    
    # Note this are raw strings to handle the Windows backslash path character
    values_to_check = [r'C:\folder\somepath\1234_456_2.pdf', r'C:\folder\somepath\whatever\5932194_123.pdf']
    
    df = pd.read_excel('data.xlsx', sheet_name='My Data')
    # Add a blank column as a placeholder
    df["check"] = ""
    
    for i  in range(len(df)):
        # this tests if any of the entries in the file list match the current record
        if any(df.loc[i, "value"] in x for x in values_to_check):
            df.loc[i, "check"] = "matched"
        else:
            df.loc[i, "check"] = "not matched"
    
    # now we can apply the colour option
    
    # associate a styler object with the dataframe
    styler = df.style
    
    # apply the colour function to select and change the rows
    styler.apply(color, axis=1)
    
    # use ExcelWriter rather than using to_Excel directly in order to give access to the append & replace functions
    with pd.ExcelWriter("data.xlsx", engine="openpyxl", mode="a", if_sheet_exists="replace") as writer:
        styler.to_excel(writer, 'My Data', index=False)
    
    

    这给出了一个带有附加列的输出,用于标记它是否匹配。

    选项 2 - 集合操作(​​Pandas 合并)

    import numpy as np
    import pandas as pd
    import pathlib
    
    def color_joined(row):
        if row["_merge"] == "both":
            return ['background-color: red'] * len(row)
        return [''] * len(row)
    
    def clean_inputs(input_item:str) -> str:
        # Using PureWindowsPath vs Path to handle the backslashes
        # stem returns the filename only, no path or extension
        # get rid of the underscores to apply int comparisons based on your comments
        return int(pathlib.PureWindowsPath(input_item).stem.replace('_',''))
    
    values_to_check = [r'C:\folder\somepath\1234_456_2.pdf', r'C:\folder\somepath\whatever\5932194_123.pdf']
    
    # Let's have only the filenames, and without the underscores, as int
    # you may need to fiddle with this a bit to match your real-world data
    cleaned_filenames = [ clean_inputs(x) for x in values_to_check ]
    
    # No need to invent a blank check column here
    df = pd.read_excel('data.xlsx', sheet_name='My Data')
    
    # Instead, convert the value list into a dataframe too
    lookup_list = pd.DataFrame(cleaned_filenames, columns=['value'])
    
    # this uses a left join and leaves a flag 
    joined_df = df.merge(lookup_list, on='value', how='left', indicator=True)
    # the result is a df with all of the records, plus a column called "_merge"
    # the values of this column will be either "left_only" for no match or "both" for a match
    
    styler = joined_df.style
    styler.apply(color_joined, axis=1)
    # Drop the _merge column by writing out only the specified columns
    with pd.ExcelWriter("output.xlsx", engine="openpyxl", mode="a", if_sheet_exists="replace") as writer:
        styler.to_excel(writer, 'Merged', index=False, columns=['title', 'description', 'value', 'extra_column'])
    
    

    这给出了与上面相同的输出。从理论上讲,它应该比简单地使用多个循环更优化,但一如既往,您应该测试特定数据的性能。

    笔记:

    如果您想要仅包含匹配项的列表,请使用选项 2,这可以由 s1 = pd.merge(df, lookup_list, how='inner', on=['value']) 完成。

    理论上,您应该能够在写入 Excel 之前使用 styler.hide(subset=['_merge', 'check'], axis="columns") 删除列;但是,我无法在我的测试中使用它。有关详细信息,请参阅styler.hide documentation

    您可以通过指定列的数据类型(例如 int vs dtype)来节省内存(并加快处理速度),因为默认情况下是使用 dtype 对象。

    【讨论】:

      【解决方案3】:

      用迷你数据管道解决

      1. 将 xls 转换为 csv [快速]
      2. 查找匹配项并保存为单元格列表以标记["C5","C768576",...] [快速]
      3. 通过更新cells_to_mark 列表的填充颜色来更新xls。 [比遍历整个列更快]

      【讨论】:

        猜你喜欢
        • 2022-10-01
        • 1970-01-01
        • 2020-07-01
        • 1970-01-01
        • 2021-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多