如何使用Pandas保留DataFrame中指定列的非首次重复行?

来源:Ruby教程作者:高永康头衔:资深程序员
导读:本期聚焦于高永康创作的《如何使用Pandas保留DataFrame中指定列的非首次重复行?》,敬请观看详情。处理表格数据时,我们常常需要定位重复记录,但有时并不是简单地删除所有重复项,而是想保留那些从第二次开始出现的重复行。这篇文章会从Pandas的duplicated方法入手,讲解如何结合subset参数指定判断列,再利用布尔索引精确筛选出非首次重复的数据。文中会对比drop_duplicates保留首次记录的逻辑,给出完整的代码示例,帮助你在数据清洗和异常检测场景中灵活运用这一技巧,避免误删有效信息或漏掉关键重复记录。

在数据清洗过程中,我们经常会遇到需要处理重复记录的场景。多数教程会教你如何删除重复行,也就是保留每组重复数据的第一条,这通常通过drop_duplicates实现。然而,实际业务中还存在另一种需求:只保留那些第二次及以后出现的重复行,用来排查重复录入、异常数据或追踪数据变更历史。Pandas并没有直接提供一个保留非首次重复行的方法,但借助duplicated方法可以非常容易地实现这一目标。

如何使用Pandas保留DataFrame中指定列的非首次重复行?

我们需要重点关注duplicated方法的返回结果和subset参数的使用方式。默认情况下,duplicated会把每组重复项中第一次出现的行标记为False,后续重复项标记为True。这个布尔序列恰好就是非首次重复行的定位标记,直接用它过滤DataFrame就能得到目标数据。下面将通过几个层次逐步展开说明。

先理解duplicated方法的标记逻辑

DataFrame.duplicated()会返回一个与行数等长的布尔Series,其中True表示该行是前面某个行的重复项,False表示首次出现或唯一行。核心判断依据是所有列的值是否完全相同。例如一个包含三行、两列的数据,前两行完全相同,第三行不同,那么调用df.duplicated()会得到[False, True, False]。这个方法并不会修改原数据,只负责生成标记,因此非常适合用于条件筛选。

与之相对的drop_duplicates()方法默认行为是保留重复组合中的第一条记录,内部实现本质上就是取duplicated()为False的行。所以如果你只需要保留首次重复行,直接使用drop_duplicates即可;但如果你想保留非首次重复行,只需要取duplicated()结果为True的行,也就是直接对布尔Series进行过滤。

这里要注意的是,duplicated默认会对所有列进行比较。当DataFrame包含较多无关列时,基于全列的重复判断可能会漏掉你真正关心的重复情况。例如一个订单表包含订单ID、商品ID和下单时间,你可能只关心同一商品ID在同一订单中是否重复出现,而不需要比较下单时间。这时候就需要用到subset参数来指定参与重复判断的列。

使用subset参数指定判断列

subset参数接受一个列名或列名列表,只有这些列的值被用于判断重复。比如你有一个员工信息表,包含员工编号、姓名、部门和入职日期,想找出同一部门中重复出现的姓名(同名员工),就可以指定subset=['部门', '姓名']。这样,只要部门和姓名相同,即使员工编号或入职日期不同,也会被视为重复行。

示例代码如下:

import pandas as pd

# 构建示例DataFrame
data = {
    '员工编号': [101, 102, 103, 104],
    '姓名': ['张三', '李四', '张三', '王五'],
    '部门': ['技术部', '市场部', '技术部', '人事部'],
    '入职日期': ['2023-01-05', '2023-02-10', '2023-03-15', '2023-04-20']
}
df = pd.DataFrame(data)

# 仅根据部门与姓名判断重复
dup_mask = df.duplicated(subset=['部门', '姓名'])
print(dup_mask)
# 输出: 0    False
#       1    False
#       2     True
#       3    False

从输出可以看到,第2行(索引2)被标记为True,因为它与第0行的部门和姓名都相同,尽管员工编号和入职日期不同。这就是subset的作用:将比较范围缩小到关键列,避免了其他动态变化字段干扰重复判断。

在实际使用中,建议将subset参数与业务主键或业务唯一性规则对齐。例如在日志数据中,你可能希望基于用户ID和事件类型找出重复触发的事件,而忽略时间戳的细微差异。通过合理设定subset,可以显著提升重复检测的准确性和针对性。

实战:筛选指定列的非首次重复行

有了duplicated的布尔标记,筛选非首次重复行就变得非常简单。只要把这个布尔Series直接作为DataFrame的行索引条件即可。例如延续上面的员工表,我们想保留技术部中第二次出现的“张三”记录,可以这样写:

import pandas as pd

data = {
    '员工编号': [101, 102, 103, 104],
    '姓名': ['张三', '李四', '张三', '王五'],
    '部门': ['技术部', '市场部', '技术部', '人事部'],
    '入职日期': ['2023-01-05', '2023-02-10', '2023-03-15', '2023-04-20']
}
df = pd.DataFrame(data)

# 获取非首次重复行
non_first_duplicates = df[df.duplicated(subset=['部门', '姓名'], keep='first')]
print(non_first_duplicates)

输出结果会只包含索引为2的那一行,也就是第二次出现的“技术部张三”记录。这里显式传入keep='first'是为了清晰表明保留标准,虽然duplicated默认行为就是keep='first'。如果希望保留最后一次出现的重复行,而把之前的重复行标记为True,则可以设置keep='last',这时duplicated会从尾部开始判断,首次出现的概念反转。但需要注意,keep='last'的语义并不能直接用于筛选非首次重复行,需要结合实际情况理解。

如果想把所有重复行都标记出来,包括每个重复组中的第一条,可以设置keep=False。此时duplicated()会对所有有重复的行都返回True,而不管它出现的位置。这个选项在只想查看所有重复记录时非常有用,但不适合用来分离首次与非首次。

另一种常见做法是先分组,再使用groupby结合cumcount来标记出现顺序,这样能更灵活地控制保留第几次出现的记录。例如下面这段代码可以为每个基于姓名和部门的分组生成出现序号:

import pandas as pd

data = {
    '员工编号': [101, 102, 103, 104, 105],
    '姓名': ['张三', '李四', '张三', '王五', '张三'],
    '部门': ['技术部', '市场部', '技术部', '人事部', '技术部'],
    '入职日期': ['2023-01-05', '2023-02-10', '2023-03-15', '2023-04-20', '2023-05-25']
}
df = pd.DataFrame(data)

# 生成组内出现序号(从0开始)
df['出现序号'] = df.groupby(['部门', '姓名']).cumcount()
print(df)

# 筛选出第二次及以后出现的记录(序号大于0)
non_first = df[df['出现序号'] > 0]
print(non_first)

这种方式虽然多了一步生成辅助列,但更加直观,也便于后续根据序号做更多定制化处理,比如只保留第三次出现的记录。cumcount()在分组后从0开始递增,通过判断序号是否大于0就能得到非首次重复行。当然,如果只是简单需求,使用duplicated子集过滤会更加简洁。

与drop_duplicates对比及注意事项

很多人容易混淆duplicateddrop_duplicates的输入输出。drop_duplicates返回的是一个新的DataFrame,其中删除了重复行;而duplicated返回的是布尔Series。因此,如果你想保留首次出现的重复行,最直接的方式是:

first_occurrence = df.drop_duplicates(subset=['部门', '姓名'], keep='first')

如果你想保留非首次重复行,则可以用前面介绍的df[df.duplicated(subset=['部门', '姓名'])]。两种操作互为补集(在全列判断且不考虑索引重置的情况下)。但要注意,drop_duplicates默认会重置索引吗?不会,它会保留原始索引,除非你设置ignore_index=True。在后续处理中如果需要连续索引,记得加上reset_index(drop=True)

此外,处理大型DataFrame时,duplicateddrop_duplicates的性能可能受到列数据类型和内存占用的影响。如果只需要针对少数列判断重复,建议先选取这些列形成一个较小的DataFrame再进行判断,或者利用duplicatedsubset参数减少不必要的数据比较。对于包含空值的情况,Pandas默认会把NaN视为相同值进行重复判断,但在某些业务中可能需要将NaN视为不同,此时需要对缺失值先做填充或标记处理。

最后,如果需要在保留非首次重复行的同时保留一些上下文信息,比如出现次数统计,可以结合groupby().size()transform来实现。但核心思路始终是:先用duplicatedcumcount生成标记,再根据标记过滤行。掌握了这一模式,你就能灵活应对各种重复数据处理需求,而不再局限于简单的去重操作。

PandasDataFrame重复行处理修改时间:2026-08-28 20:34:53

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。