office便捷办公06:根据相似度去掉excel中的重复行
需求说明
给定一个excel表格,希望判断各行与其他行的相似度,只有局部不同,希望根据各行的相似度,将重复度较高的行删掉。
方法1:一个最简单的方法就是将每行文本转化为字符串,然后比较字符串的重复率。
from difflib import SequenceMatcher
import pandas as pd
import os
# 将每行的所有列拼成一个字符串,比较相似度,如果某行与其他行相似度大于0.9,则删除该行
def simple_duplicate_removal(input_file, threshold=0.95):"""简化版本 - 直接比较重复率"""# 读取数据df = pd.read_excel(input_file,engine='openpyxl')original_count = len(df)print(f"读取 {original_count} 行数据")# 将每行转换为字符串rows_as_strings = []for _, row in df.iterrows():row_str = "|".join([str(cell) if pd.notna(cell) else "" for cell in row])rows_as_strings.append(row_str)# 找出要保留的行keep_indices = []for i, current_row in enumerate(rows_as_strings):is_duplicate = False# 与已保留的行比较for j in keep_indices:similarity = SequenceMatcher(None, current_row, rows_as_strings[j]).ratio()if similarity >= threshold:is_duplicate = Trueprint(f"行 {i+1} 与行 {j+1} 相似度: {similarity:.2%}")breakif not is_duplicate:keep_indices.append(i)# 创建结果DataFrameresult_df = df.iloc[keep_indices].reset_index(drop=True)# 保存结果result_df.to_excel(input_file, index=False)print(f"\n处理完成!")print(f"保留 {len(result_df)} 行,删除 {original_count - len(result_df)} 行")return result_dfdef get_all_files(directory, extension):"""获取指定目录下所有指定扩展名的文件Args:directory (str): 目录路径extension (str): 文件扩展名,如 'xlsx'Returns:list: 包含所有匹配文件路径的列表"""files_list = []for root, dirs, files in os.walk(directory):for file in files:if file.endswith('.' + extension):files_list.append(os.path.join(root, file))return files_list
# 使用示例
if __name__ == "__main__":for excel_file in get_all_files(r"G:\res", 'xlsx'):print(excel_file)simple_duplicate_removal(excel_file, 0.95)print(f'{excel_file}已去重')
这种方法的缺点是效率不高,我用一个150行的excel表格。
对于大规模数据,效率最高的方法是使用向量化操作和高效的算法。我们既要考虑相似度(而非完全相等)又要效率,可以考虑使用以下方法:
-
使用局部敏感哈希(LSH)或MinHash来近似计算相似度,这样可以大大减少计算量。
-
如果数据是数值型的,可以考虑使用聚类方法,将相似行聚类,然后每个类中只保留一个代表。
但是,由于问题要求相似度大于95%的行,且我们希望保留一个,这里推荐使用MinHash方法,因为它特别适用于大规模文档去重,可以快速估计相似度。
方法2:下面我们使用MinHash和LSH来快速查找相似行,并删除重复行
import pandas as pd
from datasketch import MinHash, MinHashLSH
import jieba # 用于中文分词,如果数据是英文可以不用def preprocess_text(text):"""预处理文本:这里如果是中文需要分词,英文可以按空格分或者使用n-gram"""# 如果是英文,可以直接用空格分割# return text.split()# 如果是中文,使用结巴分词return jieba.cut(text)def create_minhash(row_str, num_perm=128):"""为一行字符串创建MinHash"""m = MinHash(num_perm=num_perm)# 将字符串分词(或按n-gram)后添加到MinHash中tokens = preprocess_text(row_str)for token in tokens:m.update(token.encode('utf8'))return mdef remove_duplicates_with_minhash(input_file, output_file, threshold=0.95):"""使用MinHash和LSH去除重复行"""# 读取数据df = pd.read_excel(input_file)print(f"原始数据行数: {len(df)}")# 将每行数据转换为字符串row_strings = []for index, row in df.iterrows():row_cleaned = [str(cell) if pd.notna(cell) else "" for cell in row]row_str = " ".join(row_cleaned)row_strings.append(row_str)# 创建LSH索引lsh = MinHashLSH(threshold=threshold, num_perm=128)rows_to_keep = [] # 保存保留的行索引minhashes = {} # 保存每行的MinHash和行索引# 第一遍:为每行创建MinHash并插入LSH,同时记录需要保留的行for idx, row_str in enumerate(row_strings):minhash = create_minhash(row_str)minhashes[idx] = minhash# 查询LSH中是否有与当前行相似的行result = lsh.query(minhash)if not result:# 如果没有相似行,则插入LSH并保留该行lsh.insert(idx, minhash)rows_to_keep.append(idx)else:# 如果找到相似行,则打印信息并跳过(不保留当前行)print(f"行 {idx+1} 与行 {result[0]+1} 相似,跳过")# 根据保留的行索引创建新的DataFramedf_cleaned = df.iloc[rows_to_keep].reset_index(drop=True)# 保存结果df_cleaned.to_excel(output_file, index=False)print(f"去重后行数: {len(df_cleaned)}")print(f"删除行数: {len(df) - len(df_cleaned)}")print(f"结果已保存到: {output_file}")if __name__ == "__main__":input_file = r"G:\res\输入.xlsx"output_file = r"G:\res\输出.xlsx"remove_duplicates_with_minhash(input_file, output_file, 0.95)
耗时5秒。