在数据分析和处理过程中,高效地选择和过滤数据是最基础也是最重要的操作之一。Pandas 作为 Python 数据分析的核心库,提供了丰富而灵活的数据选择与过滤方法。本文将全面介绍 Pandas 中的数据选择与过滤技巧,从基础操作到高级应用,帮助您掌握这一关键技能。
一、Pandas 数据结构回顾
在深入探讨数据选择与过滤之前,让我们先简要回顾一下 Pandas 的两种核心数据结构:
-
Series:一维数组,带有标签的轴(索引)
-
DataFrame:二维表格型数据结构,包含行索引和列标签
理解这两种数据结构的特点对于掌握数据选择方法至关重要,因为不同的选择方法可能返回不同类型的对象。
二、基础选择方法
1. 列选择
选择列是 Pandas 中最简单的操作之一,有以下几种方式:
# 选择单列(返回Series)
single_column = df['column_name']
# 选择多列(返回DataFrame)
multiple_columns = df[['col1', 'col2', 'col3']]
# 使用点记号(仅适用于列名是有效的Python标识符时)
single_column_dot = df.column_name
注意:点记号方法虽然简洁,但不推荐在生产代码中使用,因为它无法处理列名中有空格或与Python关键字冲突的情况。
2. 行选择
Pandas 提供了多种行选择方法,主要分为基于标签和基于整数位置两种方式。
基于标签的选择 (loc)
# 选择单行
single_row = df.loc['row_label']
# 选择多行
multiple_rows = df.loc[['row1', 'row2', 'row3']]
# 选择行范围
row_range = df.loc['row1':'row4'] # 包含结束行
基于整数位置的选择 (iloc)
# 选择单行
single_row = df.iloc[0]
# 选择多行
multiple_rows = df.iloc[[0, 2, 4]]
# 选择行范围
row_range = df.iloc[1:4] # 不包含结束位置
三、布尔索引与条件过滤
布尔索引是 Pandas 中最强大且常用的数据过滤方法,它允许我们根据条件表达式选择数据。
1. 基本布尔索引
# 单条件过滤
filtered_df = df[df['column'] > value]
# 示例:选择年龄大于30的记录
older_than_30 = df[df['Age'] > 30]
2. 多条件组合
# AND条件(使用&)
filtered_df = df[(df['col1'] > value1) & (df['col2'] < value2)]
# OR条件(使用|)
filtered_df = df[(df['col1'] > value1) | (df['col2'] < value2)]
# NOT条件(使用~)
filtered_df = df[~(df['column'] == value)]
# 示例:选择年龄大于30且来自NY的记录
ny_adults = df[(df['Age'] > 30) & (df['City'] == 'NY')]
重要提示:在使用多条件组合时,必须用括号将每个条件括起来,否则会因为运算符优先级问题导致错误。
3. isin() 方法
当需要检查值是否在一组可能的值中时,isin() 方法非常有用:
# 选择城市为NY或LA的记录
cities = ['NY', 'LA']
filtered_df = df[df['City'].isin(cities)]
# 也可以用于排除某些值
excluded_cities = ['Chicago']
filtered_df = df[~df['City'].isin(excluded_cities)]
4. between() 方法
对于范围检查,between() 方法比组合两个不等式更简洁:
# 选择年龄在25到35之间的记录
filtered_df = df[df['Age'].between(25, 35)]
# 包含参数可以控制是否包含边界
filtered_df = df[df['Age'].between(25, 35, inclusive='neither')]
四、高级选择技巧
1. loc 和 iloc 的行列混合选择
loc 和 iloc 不仅可以用于行选择,还可以同时指定行和列:
# 使用loc选择特定行和列
subset = df.loc[rows_selection, columns_selection]
# 示例:选择年龄>30的记录的姓名和城市列
result = df.loc[df['Age'] > 30, ['Name', 'City']]
# 使用iloc基于位置选择
result = df.iloc[1:4, 0:2] # 选择第2-4行,第1-2列
2. query() 方法
query() 方法提供了一种更直观的字符串表达式方式来过滤数据:
# 基本查询
result = df.query('Age > 30')
# 多条件查询
result = df.query('Age > 30 and City == "NY"')
# 使用变量
min_age = 30
result = df.query('Age > @min_age') # 注意使用@符号引用变量
query() 方法的优势在于语法简洁,特别是对于复杂条件,而且通常性能也更好。
3. where() 和 mask() 方法
where() 和 mask() 方法提供了条件替换的功能:
# where(): 不满足条件的值替换为NaN(或其他指定值)
result = df.where(df > 0) # 保留大于0的值,其他为NaN
# mask(): 满足条件的值替换为NaN
result = df.mask(df > 0) # 大于0的值变为NaN,其他保留
这两个方法不会过滤掉数据,而是保留原始DataFrame的形状,只是替换不符合条件的值。
4. 字符串方法过滤
对于文本数据,Pandas 通过 str 访问器提供了丰富的字符串操作:
# 以特定前缀开头
filtered_df = df[df['Name'].str.startswith('A')]
# 包含特定子串
filtered_df = df[df['Name'].str.contains('lie')]
# 正则表达式匹配
filtered_df = df[df['Name'].str.match('^C.*e$')]
# 字符串长度过滤
filtered_df = df[df['Name'].str.len() > 4]
5. 按数据类型选择
有时我们需要根据列的数据类型进行选择:
# 选择数值型列
numeric_cols = df.select_dtypes(include=['number'])
# 排除数值型列
non_numeric_cols = df.select_dtypes(exclude=['number'])
五、性能优化技巧
在处理大型数据集时,选择方法的性能变得尤为重要。以下是一些优化建议:
1. 避免链式索引
链式索引是指连续使用多个索引操作,如:
# 不推荐的链式索引
result = df[df['Age'] > 30]['Name']
# 推荐的做法
result = df.loc[df['Age'] > 30, 'Name']
链式索引可能会导致不可预测的行为,并且通常性能较差。
2. 使用query()提高性能
对于复杂条件,query() 方法通常比布尔索引更快,特别是对于大型DataFrame:
# 比传统的布尔索引更快
result = df.query('Age > 30 & City == "NY"')
3. 使用numpy.where进行条件选择
对于非常大型的数据集,可以考虑使用numpy的where函数:
import numpy as np
condition = df['Age'] > 30
result = df[np.where(condition, True, False)]
4. 使用eval()进行表达式求值
对于复杂的数值运算,eval() 可以提供性能提升:
result = df.eval('(A + B) / (C - D)')
六、实际应用案例
让我们通过一个完整的示例来演示这些技巧的综合应用:
import pandas as pd
import numpy as np
# 创建示例数据集
data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank', 'Grace', 'Heidi'],
'Age': [25, 32, 18, 47, 29, 31, 23, 50],
'City': ['NY', 'LA', 'Chicago', 'NY', 'LA', 'Chicago', 'NY', 'LA'],
'Salary': [70000, 85000, 45000, 120000, 65000, 95000, 55000, 110000],
'Department': ['HR', 'Engineering', 'Marketing', 'Engineering',
'HR', 'Engineering', 'Marketing', 'Engineering']
}
df = pd.DataFrame(data)
# 案例1:选择年龄在25-40之间且工资高于平均工资的工程师
avg_salary = df['Salary'].mean()
case1 = df[
(df['Age'].between(25, 40)) &
(df['Salary'] > avg_salary) &
(df['Department'] == 'Engineering')
]
# 案例2:使用query方法实现相同功能
case2 = df.query('25 <= Age <= 40 and Salary > @avg_salary and Department == "Engineering"')
# 案例3:选择名字长度大于4且不以元音字母开头的员工
case3 = df[
df['Name'].str.len() > 4
].loc[
~df['Name'].str.lower().str.startswith(('a', 'e', 'i', 'o', 'u'))
]
# 案例4:按部门和城市分组统计平均工资
summary = df.groupby(['Department', 'City'])['Salary'].mean().unstack()
七、常见问题与解决方案
1. SettingWithCopyWarning 警告
这是一个常见的警告,通常发生在修改链式索引的结果时。解决方案是明确使用loc或iloc:
# 可能引发警告的方式
df[df['Age'] > 30]['Salary'] = 0
# 正确的方式
df.loc[df['Age'] > 30, 'Salary'] = 0
2. 处理缺失值的选择
# 选择非空值
non_null = df[df['column'].notna()]
# 选择空值
null_values = df[df['column'].isna()]
3. 多条件过滤时的括号问题
# 错误:缺少括号
df[df['A'] > 0 & df['B'] < 0] # 会报错
# 正确
df[(df['A'] > 0) & (df['B'] < 0)]
4. 分类数据的过滤
对于分类数据(categorical data),使用isin()通常比直接比较更高效:
# 对于分类变量
df[df['Category'].isin(['A', 'B'])] # 比 == 更高效
八、总结
Pandas 提供了丰富多样的数据选择与过滤方法,从简单的列选择到复杂的多条件过滤,可以满足各种数据分析需求。掌握这些技巧的关键在于:
-
理解不同方法适用的场景
-
熟悉布尔索引和多条件组合
-
了解性能优化的方法
-
避免常见的陷阱和错误
在实际工作中,建议:
-
对于简单选择,直接使用列索引或loc/iloc
-
对于复杂条件过滤,考虑使用query()方法
-
对于大型数据集,关注性能优化技巧
-
始终注意避免链式索引
通过灵活运用这些方法,您可以高效地从复杂的数据集中提取所需信息,为后续的分析和建模打下坚实基础。