需求说明
给定一个excel表格,希望判断各行与其他行的相似度,只有局部不同,希望根据各行的相似度,将重复度较高的行删掉。
方法1:一个最简单的方法就是将每行文本转化为字符串,然后比较字符串的重复率。
python
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 = True
print(f"行 {i+1} 与行 {j+1} 相似度: {similarity:.2%}")
break
if not is_duplicate:
keep_indices.append(i)
# 创建结果DataFrame
result_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_df
def 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来快速查找相似行,并删除重复行
python
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 m
def 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} 相似,跳过")
# 根据保留的行索引创建新的DataFrame
df_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秒。