繁体   English   中英

如何根据运行时指定的列名并忽略少数列来比较 2 个巨大的 CSV 文件?

[英]How to compare 2 huge CSV files, based on column names specified at run time and ignoring few columns?

我需要编写一个程序来比较 2 个 CSV 文件并报告 excel 文件中的差异。 它根据主键(有时是一些辅助键)比较记录,而忽略指定的其他列的列表。 所有这些参数都是从 excel 中读取的。 我已经编写了一个代码来执行此操作并且对于小文件可以正常工作,但是对于大文件(要比较的某些文件的行数超过 200K),性能非常差。

当前逻辑使用 csv.DictReader 读取文件。 我逐行遍历第一个文件的行,每次在第二个文件中找到相应的记录(比较主键和辅助键)。 如果找到记录,我会比较所有列,忽略 excel 中指定的列。 如果任何列有差异,我会在 excel 报告中写下两条记录,突出显示差异。 下面是我到目前为止的代码。 如果有人可以提供任何优化此程序的提示或建议不同的方法,那将是非常友好的。

primary_key = wb['Parameters'].cell(6,2).value              #Read Primary Key

secondary_keys = []                                         #Read Secondary Keys into a list
col = 4
while wb['Parameters'].cell(6,col).value:
    secondary_keys.append(wb['Parameters'].cell(6,col).value)
    col += 1
len_secondary_keys = len(secondary_keys)

ignore_col = []                                             #Read Columns to be ignored into a list
row = 8
while wb['Parameters'].cell(row,2).value:
    ignore_col.append(wb['Parameters'].cell(row,2).value)
    row += 1

with open (filename1) as csv_file_1, open (filename2) as csv_file_2:
    file1_reader = csv.DictReader(filename1, delimiter='~')
    for row_file1 in file1_reader:
        record_found = False
        file2_reader = csv.DictReader(filename2, delimiter='~')
        for row_file2 in file2_reader:
            if row_file2[primary_key] == row_file1[primary_key]:
                for key in secondary_keys:
                    if row_file2[key] != row_file1[key]:
                        break
                compare(row_file1, row_file2)
                record_found = True
                break
        if not record_found:
            report_not_found(sheet_name1, row_file1, row_no_file1)

def compare(row_file1, row_file2):
    global row_diff
    data_difference = False
    for key in row_file1:
        if key not in ignore_col:
            if (row_file1[key] != row_file2[key]):
                data_difference = True
                break
    if data_difference:
        c = 1
        for key in row_file1:
            wb_report['DW_Diff'].cell(row = row_diff, column = c).value = row_file1[key]
            wb_report['DW_Diff'].cell(row = row_diff+1, column = c).value = row_file2[key]
            if (row_file1[key] != row_file2[key]):
                wb_report['DW_Diff'].cell(row = row_diff+1, column = c).fill = PatternFill(patternType='solid',
                                        fill_type='solid', 
                                        fgColor=Color('FFFF0000'))
            c += 1
        row_diff += 2

由于比较的结构,您遇到了速度问题。 您正在使用嵌套循环将一个集合中的每个条目与另一个集合中的每个条目进行比较,这是 O(N^2) 慢。

您可以稍微修改代码的一种方法是重做摄取数据的方式,而不是使用csv.DictReader为每个文件制作字典列表,而是使用主要 & 手动创建每个文件的单个字典辅助键作为字典键。 通过这种方式,您可以非常轻松地比较两个词典之间的条目,并且时间恒定。

此构造假定您在每个文件中都有唯一的主键/辅助键,这似乎是您从上面假设的。

这是一个玩具示例。 在这里,我只是使用整数和动物类型作为(主键,辅助键)键的元组

In [7]: file1_dict = {(1, 'dog'): [45, 22, 66], (3, 'bird'): [55, 20, 1], (15, '
   ...: cat'): [6, 8, 90]}                                                      

In [8]: file2_dict = {(1, 'dog'): [45, 22, 66], (3, 'bird'): [4, 20, 1]}        

In [9]: file1_dict                                                              
Out[9]: {(1, 'dog'): [45, 22, 66], (3, 'bird'): [55, 20, 1], (15, 'cat'): [6, 8, 90]}

In [10]: file2_dict                                                             
Out[10]: {(1, 'dog'): [45, 22, 66], (3, 'bird'): [4, 20, 1]}

In [11]: for k in file1_dict: 
    ...:     if k in file2_dict: 
    ...:         if file1_dict[k] == file2_dict[k]: 
    ...:             print('matched %s' % str(k)) 
    ...:         else: 
    ...:             print('different %s' % str(k)) 
    ...:     else: 
    ...:         print('no corresponding key for %s' % str(k)) 
    ...:                                                                        
matched (1, 'dog')
different (3, 'bird')
no corresponding key for (15, 'cat')

我能够按照@Vaibhav Jadhav 的建议使用 Pandas 库实现这一点,使用以下步骤: 1. 将 2 个 CSV 文件导入数据帧。 例如:

try:
    data1 = pd.read_csv(codecs.open(filename1, 'rb', 'utf-8', errors = 'ignore'), sep = delimiter1, dtype='str', error_bad_lines=False)
    print (data1[keys[0]])
except:
    data1 = pd.read_csv(codecs.open(filename1, 'rb', 'utf-16', errors = 'ignore'), sep = delimiter1, dtype='str', error_bad_lines=False)
  1. 从两个数据框中删除不进行比较的列。

for col in data1.columns: if col in ignore_col: del data1[col] del data2[col]

  1. 将 2 个数据框与indicator=True合并

merged = pd.merge(data1, data2, how='outer', indicator=True)

  1. 从合并的数据框中,删除两个数据框中都可用的行。

merged = merged[merged._merge != 'both']

  1. 使用键对数据框进行排序

merged.sort_values(by = keys, inplace = True, kind = 'quicksort')

  1. 迭代数据帧的行,比较前两行的键。 如果键不同,则 row1 仅存在于 2 个 CSV 文件之一中。 如果键相同,则迭代各个列并进行比较以找出哪个列值不同。

这是 Apache Beam 的一个很好的用例。

“groupbykey”等功能将使按键匹配更加高效。

使用合适的运行器,您可以有效地扩展到更大的数据集。

可能没有 Excel IO,但您可以输出到 csv、数据库等。

https://beam.apache.org/documentation/
https://beam.apache.org/documentation/transforms/python/aggregation/groupbykey/
https://beam.apache.org/documentation/runners/capability-matrix/
https://beam.apache.org/documentation/io/built-in/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM