Python Pandas DataFrame 数据查询与筛选:深度解析多种高效查找方法323


在数据分析领域,Python的Pandas库无疑是中流砥柱,而其核心数据结构DataFrame更是处理表格数据的利器。数据框(DataFrame)的强大之处不仅在于其灵活的数据组织方式,更在于其提供了丰富多样、高效便捷的数据查找、筛选和定位机制。无论是进行数据清洗、特征工程、探索性数据分析,还是最终的数据报告,精准地从庞大数据集中提取出我们所需的信息,都是至关重要的一步。

本文将作为一份详尽的指南,深入探讨Pandas DataFrame中各种查找和筛选数据的方法。我们将从最基础的列选择与行切片开始,逐步深入到基于标签、位置的精准定位,再到强大的布尔索引、SQL风格的查询以及针对特定数据类型的筛选技术,并辅以丰富的代码示例,帮助读者全面掌握DataFrame的数据查找精髓。

一、准备工作:创建示例DataFrame

为了更好地演示各种查找方法,我们首先创建一个包含多种数据类型的示例DataFrame。
import pandas as pd
import numpy as np
# 创建示例DataFrame
data = {
'学生ID': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010],
'姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十', '郑一', '刘二'],
'性别': ['男', '女', '男', '男', '女', '男', '女', '男', '女', '男'],
'年龄': [20, 21, 22, 20, 23, 21, 22, 20, 23, 21],
'专业': ['计算机', '金融', '统计', '计算机', '金融', '统计', '计算机', '金融', '统计', '计算机'],
'成绩': [85.5, 92.0, 78.5, 88.0, 95.5, 80.0, 90.0, 75.0, 89.0, 82.5],
'是否住宿': [True, False, True, True, False, False, True, True, False, True],
'入学日期': pd.to_datetime(['2020-09-01', '2020-09-01', '2019-09-01', '2020-09-01', '2019-09-01',
'2020-09-01', '2019-09-01', '2020-09-01', '2019-09-01', '2020-09-01']),
'家庭住址': ['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '重庆', '南京', '天津']
}
df = (data)
df.set_index('学生ID', inplace=True) # 将学生ID设为索引,方便后续演示标签查找
[1003, '成绩'] = # 制造一个缺失值
[1007, '专业'] = # 制造一个缺失值
print("示例DataFrame:")
print(df)

二、核心查找方法详解

1. 基本列选择与行切片 (`[]` 操作符)


这是Pandas DataFrame最直观的查找方式,类似于Python列表和字典的操作。它主要用于选择列或对行进行简单的位置切片。
选择单列: 返回一个Series。
选择多列: 返回一个DataFrame,需要传入一个列名列表。
行切片: 类似于列表切片,基于整数位置,左闭右开。


print("1.1 选择单列 '姓名':")
print(df['姓名'])
print("1.2 选择多列 ['姓名', '年龄', '专业']:")
print(df[['姓名', '年龄', '专业']])
print("1.3 行切片(前5行):")
print(df[0:5]) # 从第0行到第4行

注意: 虽然 `[]` 可以进行行切片,但它不适用于基于标签的行选择,也不支持同时选择行和列。在更复杂的场景中,我们应优先使用 `.loc` 或 `.iloc`。

2. 基于标签的查找:`.loc[]`


`.loc` 是Pandas DataFrame中进行基于标签(label-based) 索引和选择的主要方法。它允许我们通过行标签和列标签来精确地选择数据。`.loc` 的切片是包含结束标签的,这一点与Python的列表切片不同。

语法: `[行标签, 列标签]`
选择单行单列: `[行标签, 列标签]`
选择单行所有列: `[行标签]` 或 `[行标签, :]`
选择多行单列: `[[行标签1, 行标签2], 列标签]`
选择多行多列: `[[行标签1, 行标签2], [列标签1, 列标签2]]`
基于标签的切片: `[起始行标签:结束行标签, 起始列标签:结束列标签]`
布尔索引: `[布尔条件, 列标签]` (后面会详细讲解布尔索引)


print("2.1 使用 .loc 选择学生ID为1001的姓名和年龄:")
print([1001, ['姓名', '年龄']])
print("2.2 使用 .loc 选择学生ID为1002的所有信息:")
print([1002])
print("2.3 使用 .loc 选择学生ID从1003到1006的所有列:")
# 注意:标签切片是包含结束标签的
print([1003:1006])
print("2.4 使用 .loc 选择学生ID为1001和1005的专业和成绩:")
print([[1001, 1005], ['专业', '成绩']])

3. 基于位置的查找:`.iloc[]`


`.iloc` 是Pandas DataFrame中进行基于整数位置(integer-position-based) 索引和选择的主要方法。它允许我们像操作Python列表一样,通过行和列的整数位置来选择数据。`.iloc` 的切片是左闭右开的,不包含结束位置。

语法: `[行位置, 列位置]`
选择单行单列: `[行位置, 列位置]`
选择单行所有列: `[行位置]` 或 `[行位置, :]`
选择多行单列: `[[行位置1, 行位置2], 列位置]`
选择多行多列: `[[行位置1, 行位置2], [列位置1, 列位置2]]`
基于位置的切片: `[起始行位置:结束行位置, 起始列位置:结束列位置]`


print("3.1 使用 .iloc 选择第0行(索引1001)的第1列(姓名):")
print([0, 1])
print("3.2 使用 .iloc 选择前3行和前4列:")
# 注意:位置切片是左闭右开的
print([0:3, 0:4])
print("3.3 使用 .iloc 选择第1、3、5行的所有列:")
print([[1, 3, 5]])
print("3.4 使用 .iloc 选择所有行,但只选择第2到第4列(性别、年龄、专业):")
print([:, 2:5])

4. 精确到单个元素的查找:`.at[]` 和 `.iat[]`


当只需要访问或修改DataFrame中的单个标量值时,`.at` 和 `.iat` 提供了比 `.loc` 和 `.iloc` 更快的访问方式。
`.at[行标签, 列标签]`: 基于标签,用于访问单个标量值。
`.iat[行位置, 列位置]`: 基于整数位置,用于访问单个标量值。


print("4.1 使用 .at 获取学生ID为1002的专业:")
print([1002, '专业'])
print("4.2 使用 .iat 获取第1行(索引1002)的第3列(年龄):")
print([1, 3])
# 修改单个值
[1001, '成绩'] = 99.0
print("4.3 修改学生ID 1001 的成绩为 99.0 后:")
print([1001, '成绩'])
[1001, '成绩'] = 85.5 # 改回原值

三、高级数据筛选与查询

上述方法主要用于直接选择数据,而数据分析中更常见的是根据特定条件来筛选数据。Pandas提供了强大的布尔索引和一系列方法来实现这一点。

1. 布尔索引 (Boolean Indexing):最强大的筛选工具


布尔索引是Pandas中最灵活、最强大的数据筛选方式。它的核心是创建一个与DataFrame行数相同,包含 `True` 或 `False` 值的布尔序列(或DataFrame),然后将其作为索引传递给DataFrame。`True` 对应的行将被选中,`False` 对应的行将被排除。
单条件筛选:
多条件组合筛选: 使用逻辑运算符 `&` (AND), `|` (OR), `~` (NOT)。


print("5.1 布尔索引:筛选所有性别为 '女' 的学生:")
female_students = df[df['性别'] == '女']
print(female_students)
print("5.2 布尔索引:筛选年龄大于21岁且专业为 '计算机' 的学生:")
# 注意:每个条件表达式都必须用括号括起来
senior_cs_students = df[(df['年龄'] > 21) & (df['专业'] == '计算机')]
print(senior_cs_students)
print("5.3 布尔索引:筛选成绩小于80分或性别为 '女' 的学生:")
low_score_or_female = df[(df['成绩'] < 80) | (df['性别'] == '女')]
print(low_score_or_female)
print("5.4 布尔索引:筛选不住宿的学生 (使用 ~ 非运算符):")
non_dorm_students = df[~df['是否住宿']]
print(non_dorm_students)
print("5.5 布尔索引:筛选入学日期在2020年及以后的学生:")
after_2020_admission = df[df['入学日期']. >= 2020]
print(after_2020_admission)
print("5.6 布尔索引:筛选专业为缺失值的学生:")
missing_major = df[df['专业'].isna()]
print(missing_major)
print("5.7 布尔索引:筛选成绩不为缺失值的学生:")
not_missing_score = df[df['成绩'].notna()]
print(not_missing_score)

2. 使用 `.query()` 方法:SQL风格查询


`.query()` 方法允许你使用字符串表达式来查询DataFrame,其语法风格类似于SQL的`WHERE`子句。对于复杂的、涉及多个列和条件的查询,`.query()` 可以提高代码的可读性,尤其是在条件字符串比较长时。

特点:
支持DataFrame列名直接作为变量使用。
可以使用 `@` 符号引用局部或全局变量。


print("6.1 使用 .query() 筛选年龄大于21岁且专业为 '计算机' 的学生:")
query_students = ('年龄 > 21 and 专业 == "计算机"')
print(query_students)
print("6.2 使用 .query() 筛选成绩小于80分或性别为 '女' 的学生:")
query_students_or = ('成绩 < 80 or 性别 == "女"')
print(query_students_or)
# 使用局部变量
min_age = 22
query_with_variable = ('年龄 >= @min_age and 性别 == "男"')
print(f"6.3 使用 .query() 筛选年龄大于等于 {min_age} 且性别为 '男' 的学生:")
print(query_with_variable)

3. 成员资格筛选:`.isin()` 方法


`.isin()` 方法用于检查DataFrame或Series中的每个元素是否存在于一个给定的序列(如列表、元组或另一个Series)中。这在筛选满足“或”关系的一组值时非常有用。
print("7.1 使用 .isin() 筛选专业为 '计算机' 或 '金融' 的学生:")
it_finance_students = df[df['专业'].isin(['计算机', '金融'])]
print(it_finance_students)
print("7.2 使用 .isin() 筛选家庭住址在北京、上海或广州的学生:")
major_cities_students = df[df['家庭住址'].isin(['北京', '上海', '广州'])]
print(major_cities_students)

4. 字符串方法查找 (针对文本数据)


Pandas Series对象提供了一组强大的字符串方法(通过 `.str` 访问器),用于对文本列进行复杂的模式匹配和筛选。
`.(pattern, case=True, regex=True)`: 检查字符串是否包含特定模式(支持正则表达式)。
`.(prefix)`: 检查字符串是否以特定前缀开头。
`.(suffix)`: 检查字符串是否以特定后缀结尾。


print("8.1 使用 .() 筛选姓名中包含 '三' 或 '李' 的学生:")
name_contains = df[df['姓名'].('三|李', regex=True)]
print(name_contains)
print("8.2 使用 .() 筛选专业以 '计' 开头的学生:")
major_starts_with_ji = df[df['专业'].('计', na=False)] # na=False 忽略NaN值
print(major_starts_with_ji)
print("8.3 使用 .() 进行不区分大小写的搜索 (需要先转为小写):")
# 假设家庭住址可能是 'beijing' 或 'Beijing'
df_case_insensitive = df[df['家庭住址'].().('京', na=False)]
print(df_case_insensitive)

5. 其他筛选方法



`.where()`: 基于条件选择,不满足条件的元素将被替换(默认为NaN)。它保留了DataFrame的形状,而不是直接过滤行。
`.filter()`: 根据索引标签或列标签的精确匹配、`like`(子字符串)或 `regex`(正则表达式)来选择行或列。


print("9.1 使用 .where() 筛选出成绩大于90分的学生,其余成绩置为NaN:")
# .where() 结果会保留原始DataFrame的形状,不符合条件的行/列会被NaN填充
high_score_where = (df['成绩'] > 90)
print(high_score_where)
# 如果想直接筛选,还是用布尔索引更直接:df[df['成绩'] > 90]
print("9.2 使用 .filter() 筛选包含 '姓' 字的列:")
filter_columns_like = (like='姓', axis=1) # axis=1 表示在列上过滤
print(filter_columns_like)
print("9.3 使用 .filter() 筛选行索引中包含 '003' 的行:")
# 注意:这里我们设置了学生ID为索引,所以可以通过索引标签进行过滤
filter_rows_like = (like='003', axis=0) # axis=0 表示在行上过滤
print(filter_rows_like)
print("9.4 使用 .filter() 结合正则表达式筛选列 (例如,筛选所有以 '日期' 结尾的列):")
filter_columns_regex = (regex='日期$', axis=1)
print(filter_columns_regex)

四、性能考量与最佳实践
优先使用向量化操作: Pandas的查找和筛选方法通常是高度优化的C语言实现,远比手动编写Python循环快。避免在DataFrame上进行迭代(如`for i, row in ():`),除非万不得已。
理解 `.loc` 与 `.iloc` 的区别: 始终明确你是基于标签还是基于位置进行操作。这能避免混淆和潜在的错误。
避免链式索引 (`SettingWithCopyWarning`): 当你通过 `df[some_condition]['column'] = value` 这样的链式操作来赋值时,Pandas可能会发出 `SettingWithCopyWarning`。这是因为第一个操作 `df[some_condition]` 可能返回了一个视图(view)或副本(copy),对它进行赋值不一定会修改原始DataFrame。正确的做法是使用 `.loc`:`[some_condition, 'column'] = value`。
使用 `.at` 和 `.iat` 访问单个元素: 如果目标是单个标量值,`.at` 或 `.iat` 比 `.loc` 或 `.iloc` 效率更高。
善用布尔索引: 它是最强大、最灵活的筛选方式。对于复杂的多条件筛选,使用括号明确逻辑运算的优先级。
`.query()` 的适用场景: 当筛选条件字符串较长,且涉及多个变量时,`.query()` 可以提高代码可读性。但在简单场景下,布尔索引可能更直接。
处理缺失值: 在进行条件筛选时,记住缺失值(``)的行为可能与预期不同(例如,` == ` 是 `False`)。使用 `.isna()` 和 `.notna()` 来显式处理它们。

五、总结

Pandas DataFrame提供了无与伦比的数据查找和筛选能力,从简单的列选择到复杂的布尔逻辑和字符串模式匹配,几乎涵盖了数据分析中所有可能的场景。理解并熟练运用 `.loc`、`.iloc`、布尔索引以及 `.query()` 等核心方法,是成为一名高效Pandas用户的基础。

选择哪种方法取决于你的具体需求:是需要精确到标签还是位置,是需要单个元素还是整个子集,是需要简单筛选还是复杂的多条件查询。掌握这些工具,你将能更加自如地驾驭数据,从庞大的信息海洋中迅速捕获你所需的数据洞察。

记住,实践是最好的老师。多尝试,多探索,你将在Pandas数据框的查找世界中游刃有余。

2025-11-06


上一篇:精通Python内置函数:解锁高效编程的核心奥秘

下一篇:Python字符串绘图:从ASCII艺术到文本界面的奇妙旅程