思路
假设第一行是标题行,且两个文件的标题行内容一致(可能顺序不同)。
读取两个文件,获取DataFrame
- 假设csv或xlsx
检查两个DataFrame的形状(行数和列数)是否一致,如果不一致,则直接返回错误
检查两个DataFrame的列名是否包含相同的列(不考虑顺序)
- 如果列名集合不同,则返回错误。
- 如果列名相同但顺序不同,调整第二个DataFrame的列顺序与第一个相同。
比较两个DataFrame的内容
- 如果完全相同,则输出相同提示。
- 如果存在差异,则生成一个差异报告Excel文件,包含两个sheet:
- 第一个sheet:统计每个列中差异的数量。
- 第二个sheet:将两个表中存在差异的行整合到一起(显示两个表中对应的行,并标记出差异的单元格)。
V 1.0
import pandas as pd
import numpy as np
from openpyxl import Workbook
from openpyxl.styles import PatternFill
def compare_dataframes(df1, df2):
"""
比较两个DataFrame,返回差异报告
参数:
df1: 第一个DataFrame
df2: 第二个DataFrame
返回:
如果相同返回True,否则返回差异报告DataFrame和列差异统计
"""
# 检查形状是否一致
if df1.shape != df2.shape:
return False, None, None
# 检查列名是否一致(不考虑顺序)
if set(df1.columns) != set(df2.columns):
return False, None, None
# 调整列顺序使其一致
df2 = df2[df1.columns]
# 比较内容
if df1.equals(df2):
return True, None, None
# 找出差异位置
diff_mask = df1 != df2
# 处理NaN情况 (NaN != NaN 会返回True)
both_nan = df1.isna() & df2.isna()
diff_mask = diff_mask & ~both_nan
# 创建差异统计
diff_stats = pd.DataFrame({
'列名': df1.columns,
'差异数量': diff_mask.sum(axis=0).values
})
# 创建详细差异报告
diff_rows = []
for row_idx in range(df1.shape[0]):
row_diffs = diff_mask.iloc[row_idx]
if row_diffs.any(): # 如果这一行有差异
for col_idx, col_name in enumerate(df1.columns):
if row_diffs.iloc[col_idx]:
diff_rows.append({
'行号': row_idx + 2, # +2是因为Excel行号从1开始,且第一行是标题
'列名': col_name,
'文件1值': df1.iloc[row_idx, col_idx],
'文件2值': df2.iloc[row_idx, col_idx]
})
diff_details = pd.DataFrame(diff_rows)
return False, diff_stats, diff_details
def create_difference_report(diff_stats, diff_details, output_file):
"""
创建差异报告Excel文件
"""
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
# 写入统计信息
diff_stats.to_excel(writer, sheet_name='差异统计', index=False)
# 写入详细信息
diff_details.to_excel(writer, sheet_name='详细差异', index=False)
# 获取工作簿和工作表对象
workbook = writer.book
stats_sheet = writer.sheets['差异统计']
details_sheet = writer.sheets['详细差异']
# 设置样式
red_fill = PatternFill(start_color="FFFF0000", end_color="FFFF0000", fill_type="solid")
# 在详细差异表中高亮差异值
for idx, row in diff_details.iterrows():
# 文件1值单元格 (C列)
details_sheet.cell(row=idx+2, column=3).fill = red_fill
# 文件2值单元格 (D列)
details_sheet.cell(row=idx+2, column=4).fill = red_fill
# 调整列宽
for sheet in [stats_sheet, details_sheet]:
for column in sheet.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = min(max_length + 2, 50)
sheet.column_dimensions[column_letter].width = adjusted_width
def compare_files(file1, file2, file1_sheet=None, file2_sheet=None, output_file='difference_report.xlsx'):
"""
比较两个文件的主函数
"""
# 读取文件
if file1.endswith('.csv'):
df1 = pd.read_csv(file1)
else:
df1 = pd.read_excel(file1, sheet_name=file1_sheet)
if file2.endswith('.csv'):
df2 = pd.read_csv(file2)
else:
df2 = pd.read_excel(file2, sheet_name=file2_sheet)
# 比较DataFrame
is_same, diff_stats, diff_details = compare_dataframes(df1, df2)
if is_same is None:
print("文件结构不一致,无法比较")
return False
elif is_same:
print("两个表格内容完全相同!")
return True
else:
print("表格内容存在差异,生成差异报告中...")
create_difference_report(diff_stats, diff_details, output_file)
print(f"差异报告已保存至: {output_file}")
return False
# 使用示例
if __name__ == "__main__":
compare_files('file1.csv', 'file2.csv')
V2.0
主要改进点包括:
- 使用向量化操作替代循环提高性能
- 优化NaN值处理
- 改进列宽调整逻辑
- 增强错误处理
- 优化内存使用
import pandas as pd
import numpy as np
from openpyxl import Workbook
from openpyxl.styles import PatternFill
from openpyxl.utils import get_column_letter
def compare_dataframes(df1, df2):
"""
比较两个DataFrame,返回差异报告
参数:
df1: 第一个DataFrame
df2: 第二个DataFrame
返回:
如果相同返回True,否则返回差异报告DataFrame和列差异统计
"""
# 检查形状是否一致
if df1.shape != df2.shape:
return False, None, None
# 检查列名是否一致(不考虑顺序)
if set(df1.columns) != set(df2.columns):
return False, None, None
# 调整列顺序使其一致
df2 = df2[df1.columns]
# 比较内容 - 使用更高效的方法
if df1.equals(df2):
return True, None, None
# 找出差异位置 - 使用向量化操作
# 处理NaN情况: 使用pd.NA安全的比较方法
diff_mask = ~(df1.fillna('NA').astype(str) == df2.fillna('NA').astype(str))
# 创建差异统计 - 使用向量化操作
diff_counts = diff_mask.sum()
diff_stats = pd.DataFrame({
'列名': df1.columns,
'差异数量': diff_counts.values
})
# 如果没有差异,直接返回
if diff_counts.sum() == 0:
return True, None, None
# 创建详细差异报告 - 使用更高效的方法
# 获取差异位置的行列索引
diff_indices = np.argwhere(diff_mask.values)
# 构建差异详情DataFrame
diff_rows = []
for row_idx, col_idx in diff_indices:
col_name = df1.columns[col_idx]
diff_rows.append({
'行号': row_idx + 2, # +2是因为Excel行号从1开始,且第一行是标题
'列名': col_name,
'文件1值': df1.iloc[row_idx, col_idx],
'文件2值': df2.iloc[row_idx, col_idx]
})
diff_details = pd.DataFrame(diff_rows)
return False, diff_stats, diff_details
def create_difference_report(diff_stats, diff_details, output_file):
"""
创建差异报告Excel文件
"""
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
# 写入统计信息
diff_stats.to_excel(writer, sheet_name='差异统计', index=False)
# 写入详细信息
diff_details.to_excel(writer, sheet_name='详细差异', index=False)
# 获取工作簿和工作表对象
workbook = writer.book
stats_sheet = writer.sheets['差异统计']
details_sheet = writer.sheets['详细差异']
# 设置样式
red_fill = PatternFill(start_color="FFFF0000", end_color="FFFF0000", fill_type="solid")
# 在详细差异表中高亮差异值 - 使用更高效的方法
for row_idx in range(2, len(diff_details) + 2): # 从第2行开始(标题在第1行)
details_sheet.cell(row=row_idx, column=3).fill = red_fill
details_sheet.cell(row=row_idx, column=4).fill = red_fill
# 优化列宽调整 - 使用更高效的方法
for sheet in [stats_sheet, details_sheet]:
for col_idx, column in enumerate(sheet.columns, 1):
max_length = 0
col_letter = get_column_letter(col_idx)
# 检查标题和内容
header_length = len(str(column[0].value))
max_length = max(max_length, header_length)
# 只检查前100行以避免性能问题
for cell in column[1:101]:
try:
cell_length = len(str(cell.value))
if cell_length > max_length:
max_length = cell_length
except:
pass
adjusted_width = min(max_length + 2, 50)
sheet.column_dimensions[col_letter].width = adjusted_width
def compare_files(file1, file2, file1_sheet=None, file2_sheet=None, output_file='difference_report.xlsx'):
"""
比较两个文件的主函数
"""
try:
# 读取文件 - 添加更多错误处理
if file1.endswith('.csv'):
df1 = pd.read_csv(file1)
else:
df1 = pd.read_excel(file1, sheet_name=file1_sheet or 0, engine='openpyxl')
if file2.endswith('.csv'):
df2 = pd.read_csv(file2)
else:
df2 = pd.read_excel(file2, sheet_name=file2_sheet or 0, engine='openpyxl')
# 比较DataFrame
is_same, diff_stats, diff_details = compare_dataframes(df1, df2)
if is_same is None:
print("文件结构不一致,无法比较")
return False
elif is_same:
print("两个表格内容完全相同!")
return True
else:
print("表格内容存在差异,生成差异报告中...")
create_difference_report(diff_stats, diff_details, output_file)
print(f"差异报告已保存至: {output_file}")
return False
except Exception as e:
print(f"比较过程中发生错误: {str(e)}")
return False
# 使用示例
if __name__ == "__main__":
compare_files('file1.csv', 'file2.csv')
详细优化点:
- NaN值处理优化:
使用 fillna(‘NA’) 和 astype(str) 安全地比较包含NaN的值
避免了双重NaN检查逻辑
- 性能优化:
使用向量化操作替代循环(特别是 diff_mask 计算)
使用 np.argwhere 高效获取差异位置
限制列宽计算只检查前100行以避免性能问题
- 内存优化:
避免创建不必要的中间DataFrame
使用更高效的数据结构存储差异位置
- 错误处理增强:
添加了全局异常处理
明确了Excel读取引擎
处理了sheet_name为None的情况
- 列宽调整优化:
使用 get_column_letter 简化列索引转换
同时考虑标题长度
- 高亮逻辑优化:
- 直接根据行索引高亮,避免DataFrame迭代