在数据分析和数据清洗的日常工作中,我们经常面临的一个挑战就是处理重复数据。无论你是处理来自数据库的日志文件,还是整合多个来源的 Excel 表格,重复的行数据都可能导致分析结果偏差,甚至引发模型训练的错误。因此,掌握如何在 Pandas DataFrame 中高效地识别和处理重复行,是每一位数据科学者和分析师的必备技能。
在本文中,我们将深入探讨如何使用 Pandas 库中的 duplicated() 方法来查找 DataFrame 中的重复行。我们将不仅限于如何调用函数,还会一起探讨基于所有列或特定列进行判断的各种场景,并结合实际代码示例,帮助你理解其中的细微差别。让我们准备好 Jupyter Notebook,一起开始这段数据清洗之旅吧。
准备工作:创建示例 DataFrame
为了演示查找重复行的各种情况,我们需要先准备一份包含重复数据的样本。想象一下,我们正在处理一份公司的员工信息表。由于人为录入错误或系统合并,这份表格中可能包含相同的记录。
让我们使用 Python 创建一个包含 INLINECODE8086a54a(姓名)、INLINECODEe5611879(年龄)和 ‘City‘(城市)三列的 DataFrame,并特意在其中加入一些重复的数据行。
# 导入 pandas 库
import pandas as pd
# 定义员工数据列表(包含元组)
# 注意:这里我们特意添加了一些重复的数据,例如 ‘Saumya‘ 出现了多次,且组合不同
employees = [(‘Stuti‘, 28, ‘Varanasi‘),
(‘Saumya‘, 32, ‘Delhi‘),
(‘Aaditya‘, 25, ‘Mumbai‘),
(‘Saumya‘, 32, ‘Delhi‘), # 与第1行完全重复
(‘Saumya‘, 32, ‘Delhi‘), # 与第1行完全重复
(‘Saumya‘, 32, ‘Mumbai‘), # 注意:这里城市不同,但姓名和年龄重复
(‘Aaditya‘, 40, ‘Dehradun‘),
(‘Seema‘, 32, ‘Delhi‘)
]
# 创建 DataFrame 对象,指定列名
df = pd.DataFrame(employees, columns=[‘Name‘, ‘Age‘, ‘City‘])
# 打印原始 DataFrame
print("原始员工数据表:")
df
输出结果:
原始员工数据表:
Name Age City
0 Stuti 28 Varanasi
1 Saumya 32 Delhi
2 Aaditya 25 Mumbai
3 Saumya 32 Delhi
4 Saumya 32 Delhi
5 Saumya 32 Mumbai
6 Aaditya 40 Dehradun
7 Seema 32 Delhi
熟悉这份表格是至关重要的。我们可以直观地看到,索引 3 和 4 的行与索引 1 的行完全一致。接下来,我们将编写代码让计算机自动发现这些规律。
深入理解 duplicated() 方法
Pandas 为我们提供了 DataFrame.duplicated() 方法,它返回一个布尔序列,表示每一行是否为重复项。默认情况下,它会将所有列的值都相同的行标记为重复项。
duplicated() 方法有两个非常重要的参数,决定了我们如何定义“重复”:
- INLINECODE6f0da62d:用于标识重复的列标签或列标签序列。默认 INLINECODE5d8c0c71,表示考虑所有列。如果我们只想检查 INLINECODEf78ca7a8 和 INLINECODEdf0f6032 相同的行,就可以传入
[‘Name‘, ‘Age‘]。 -
keep:这是我们在处理重复逻辑时的核心参数,它决定了标记哪些重复项。可选值有:
* INLINECODEe407afa2(默认):将除了第一次出现之外的所有重复项标记为 INLINECODEe03d5062。
* INLINECODEbdc13ced:将除了最后一次出现之外的所有重复项标记为 INLINECODE4a67a61a。
* INLINECODE8cbe8d97:将所有重复项标记为 INLINECODEb2d18ccf。
让我们通过具体的场景来看看如何灵活运用这两个参数。
场景一:基于所有列查找完全相同的重复行
这是最严格的查重方式。只有当一行中的所有字段(Name, Age, City)都与之前的某一行完全相同时,才会被认为是重复行。
#### 1.1 保留第一次出现的记录(Keep = ‘first‘)
在大多数业务场景中,我们希望保留第一条数据,而将后续出现的相同数据视为冗余。这是默认行为,所以我们不需要显式指定 keep 参数,但为了代码的可读性,建议写出来。
# 使用 duplicated() 查找重复行
# subset=None 默认检查所有列
# keep=‘first‘ 表示保留第一次出现的,将后续重复的标记为 True
duplicate_rows = df[df.duplicated()]
print("基于所有列的重复行(保留第一次出现):")
duplicate_rows
输出结果:
基于所有列的重复行(保留第一次出现):
Name Age City
3 Saumya 32 Delhi
4 Saumya 32 Delhi
代码解析:
你可以看到,索引为 3 和 4 的行被输出了。这是因为索引 1 已经是第一次出现,索引 3 和 4 与之完全相同,因此被标记为重复。索引 5 虽然名字也是 Saumya,但因为城市是 Mumbai 而非 Delhi,所以不满足“所有列相同”的条件,未被判定为重复。
#### 1.2 保留最后一次出现的记录(Keep = ‘last‘)
有时候,我们可能认为最新的数据才是最准确的,想要保留最后一次出现的数据,而将之前的重复项去除。这时,我们可以将 INLINECODEac31403e 参数设为 INLINECODEc2687806。
# keep=‘last‘:保留最后一次出现的,将之前的重复项标记为 True
duplicate_rows_last = df[df.duplicated(keep=‘last‘)]
print("基于所有列的重复行(保留最后一次出现):")
duplicate_rows_last
输出结果:
基于所有列的重复行(保留最后一次出现):
Name Age City
1 Saumya 32 Delhi
3 Saumya 32 Delhi
代码解析:
这次的结果是索引 1 和 3。因为索引 4 是 Saumya/Delhi 的最后一次出现,所以被保留,而它之前的索引 1 和 3 被标记为重复。
#### 1.3 标记所有重复项(Keep = False)
如果我们想要找出所有参与重复的数据行(即删除所有非唯一的行),可以将 INLINECODE3d627463 设为 INLINECODE45ee768a。这在清洗唯一性数据时非常有用。
# keep=False:将所有重复出现的行都标记为 True
duplicate_rows_all = df[df.duplicated(keep=False)]
print("所有参与重复的行(所有重复项):")
duplicate_rows_all
输出结果:
所有参与重复的行(所有重复项):
Name Age City
1 Saumya 32 Delhi
3 Saumya 32 Delhi
4 Saumya 32 Delhi
代码解析:
这里我们将索引 1、3 和 4 全部选了出来。因为它们都是重复数据集合的一部分。如果你想获取一份“绝对唯一”的列表,你只需要过滤掉这些行即可。
场景二:基于特定列查找重复行(部分匹配)
在实际工作中,我们更常遇到的情况是“基于特定字段的重复”。例如,我们可能认为同一个名字的人即使是不同城市也可能被系统误录为两次,或者我们只想关注来自同一个城市的重复记录。
#### 2.1 使用单列查找重复
假设我们认为:只要两个记录的 ‘City‘(城市) 相同,它们就在某种程度上重复了。我们可以将列名字符串传递给 subset 参数。
# 基于单列 ‘City‘ 查找重复行
# 检查 ‘City‘ 列是否有重复,保留第一次出现的
duplicate_city = df[df.duplicated(‘City‘)]
print("基于 ‘City‘ 列的重复行:")
duplicate_city
输出结果:
基于 ‘City‘ 列的重复行:
Name Age City
3 Saumya 32 Delhi
4 Saumya 32 Delhi
5 Saumya 32 Mumbai
7 Seema 32 Delhi
代码解析:
让我们分析一下输出。DataFrame 中的 ‘City‘ 包含:Varanasi, Delhi, Mumbai, Delhi, Delhi, Mumbai, Dehradun, Delhi。
- Varanasi(索引0):第一次出现,不标记。
- Delhi(索引1):第一次出现,不标记。
- Mumbai(索引2):第一次出现,不标记。
- Dehradun(索引6):第一次出现,不标记。
- 索引 3 (Delhi):重复,标记。
- 索引 4 (Delhi):重复,标记。
- 索引 5 (Mumbai):重复,标记。
- 索引 7 (Delhi):重复,标记。
这给了我们极大的灵活性。想象一下,如果你的数据集中有主键冲突,但你怀疑实际上是同一个用户在不同城市登录导致的重复,这种基于单列的查重就能快速帮你定位问题。
#### 2.2 使用多列查找重复
这是最强大的功能之一。让我们设想一个场景:在我们的员工数据中,我们定义“重复”为“姓名和年龄相同,但城市不同”的记录(可能是搬家记录未更新),或者是“姓名和城市相同”的记录。
现在,我们只想找出那些 ‘Name‘ 和 ‘Age‘ 相同的记录,忽略 ‘City‘。也就是说,如果一个 ‘Saumya‘, 32 出现在 Delhi,另一个 ‘Saumya‘, 32 出现在 Mumbai,我们依然认为它们在关键信息上构成了重复。
# 基于多列 [‘Name‘, ‘Age‘] 查找重复行
# subset 接受一个列表
subset_cols = [‘Name‘, ‘Age‘]
duplicate_multi_cols = df[df.duplicated(subset=subset_cols)]
print(f"基于 {subset_cols} 列的重复行:")
duplicate_multi_cols
输出结果:
基于 [‘Name‘, ‘Age‘] 列的重复行:
Name Age City
3 Saumya 32 Delhi
4 Saumya 32 Delhi
5 Saumya 32 Mumbai
代码解析:
这里发生了有趣的事情。
- 第一条 ‘Saumya‘, 32 出现在索引 1(City: Delhi)。
- 索引 3 和 4 的 ‘Saumya‘, 32 (City: Delhi) 与索引 1 的 Name 和 Age 完全相同,因此被标记为重复。
- 注意索引 5:它的数据是 ‘Saumya‘, 32, ‘Mumbai‘。虽然 City 与索引 1 不同,但因为我们只传入了
subset=[‘Name‘, ‘Age‘],Pandas 在比较时忽略了 City 列。所以,索引 5 也因为姓名和年龄与前面的记录相同而被标记为重复。
场景三:结合排序与去重(进阶技巧)
有时候,我们不仅仅是查找重复,还想确保去重时保留的是最重要的那一行。例如,如果有多条重复的员工记录,我们可能想保留年龄最大的那一条,或者保留最近更新的那条。
虽然 INLINECODE3ab6f4e4 方法本身不负责删除数据,但它通常与 INLINECODE28816624 和 INLINECODE22808963 配合使用。让我们来看看如果先排序,再使用 INLINECODE842bb7da 会发生什么。
假设我们想对 ‘Saumya‘ 的重复记录进行处理,并且我们想保留年龄较大(或较新)的那一条。如果数据是乱序的,直接去重可能会保留意想不到的行。我们先对数据进行排序。
# 先按 Name 和 Age 排序,确保我们想要的行在前面(或后面)
df_sorted = df.sort_values(by=[‘Name‘, ‘Age‘], ascending=[True, False])
# 然后,基于全列查找重复
# keep=‘first‘ 会保留排序后的第一行(即年龄最大的那个 Saumya)
deduplicated_data = df_sorted[df_sorted.duplicated(keep=‘first‘)]
print("排序后的数据(预览):")
print(df_sorted)
print("
被标记为重复的行(在排序后的表中):")
deduplicated_data
输出解析:
在排序后的表中,‘Saumya‘ 的记录会排列在一起。如果我们按 Age 降序排列,那么 32岁的 ‘Saumya‘ 会排在 25岁的 ‘Saumya‘(如果有的话)前面,或者仅仅是保持相同年龄的特定顺序。通过先排序,我们可以有效地控制 keep=‘first‘ 到底保留了哪一份数据。
常见错误与最佳实践
在处理大量数据时,查找重复行有一些陷阱需要避免。
1. 忽略大小写问题
默认情况下,Pandas 的字符串匹配是区分大小写的。INLINECODE89ee1182 和 INLINECODEf4dc0051 会被视为两个不同的值。
解决方案: 在查找重复之前,先将列数据转换为小写:
df[‘City‘] = df[‘City‘].str.lower()
# 现在再查找重复
duplicates = df[df.duplicated(subset=[‘City‘])]
2. 忽略空格
INLINECODE72de04d2 和 INLINECODEf9e7c657(后面有空格)也是不同的。
解决方案: 使用 .str.strip() 去除首尾空格。
3. 忘记重置索引
当你使用 df[df.duplicated()] 筛选数据时,结果的索引会保留原始的行号(例如索引 1, 3, 4)。这可能会在后续的合并或循环操作中造成混淆。
解决方案: 使用 .reset_index(drop=True) 来重新生成索引。
总结
在本文中,我们系统地学习了如何使用 Pandas 的 INLINECODE19d138a1 方法来查找 DataFrame 中的重复行。我们不仅学会了如何查找完全重复的所有列,还掌握了如何利用 INLINECODE9d5d2fb9 参数基于特定列(单列或多列)来识别重复数据。
回顾一下关键点:
- 默认情况下,INLINECODE37943e7a 会检查所有列,并保留第一次出现的记录(INLINECODEa02cb957)。
- 使用
subset=[‘col1‘, ‘col2‘]可以让你基于业务逻辑自定义重复的定义。 - 使用 INLINECODE2512ce3c 或 INLINECODE966ff598 可以灵活控制哪些数据被标记为重复。
掌握这些技巧,你就能在面对杂乱无章的数据时游刃有余,为后续的数据分析和建模打下坚实的基础。下次当你面对充满重复值的 Excel 表格时,你知道该怎么做了!继续探索 Pandas 的强大功能吧,你会发现数据清洗其实也可以很高效、很优雅。