各位观众老爷,大家好!今天咱们来聊聊Pandas里的一个“伪SQL”神器——query()
方法。 啥?伪SQL?别怕,不是让你去考数据库证书,而是用一种类似SQL的语法,在Pandas DataFrame里愉快地筛选数据,让你的代码更简洁、更易读,也更装X!
开场白:为啥要用query()
?
想象一下,你有一张巨大的Excel表格,里面堆满了各种数据。你想找出所有年龄大于30岁,且工资高于5000的员工。如果是用传统的Pandas方式,你可能要这样写:
import pandas as pd
# 假设我们有一个DataFrame
data = {'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 32, 28, 40, 35],
'salary': [4000, 6000, 4500, 7000, 5500],
'city': ['Beijing', 'Shanghai', 'Beijing', 'Shanghai', 'Guangzhou']}
df = pd.DataFrame(data)
# 传统的筛选方式
filtered_df = df[(df['age'] > 30) & (df['salary'] > 5000)]
print(filtered_df)
虽然能实现功能,但是括号套括号,条件与条件之间还要用&
连接,稍微复杂一点的逻辑,代码就变得像一团乱麻,让人看得头晕眼花。
但是!有了query()
,一切都变得不一样了。我们可以用更接近SQL的语法来实现同样的功能:
# 使用query()方法
filtered_df = df.query('age > 30 and salary > 5000')
print(filtered_df)
是不是瞬间感觉清爽多了?代码更简洁,逻辑更清晰,简直是拯救程序员于水火之中!
query()
方法的语法结构
query()
方法的基本语法如下:
DataFrame.query(expr, inplace=False, **kwargs)
expr
: 这是一个字符串,包含了你想要执行的查询表达式,也就是类似SQL的WHERE子句。inplace
: 一个布尔值,决定是否直接修改原始DataFrame。默认为False
,表示返回一个新的DataFrame。如果设置为True
,则会直接在原始DataFrame上进行修改。**kwargs
: 一些额外的关键字参数,用于控制查询的行为。例如,你可以通过local_dict
参数来指定查询时使用的变量。
query()
的表达式规则
query()
方法支持的表达式规则和SQL非常相似,但也有一些细微的差别。下面我们来详细讲解一下常用的表达式规则:
-
比较运算符:
>
: 大于<
: 小于>=
: 大于等于<=
: 小于等于==
: 等于!=
: 不等于
例如:
# 筛选年龄大于等于30岁的员工 filtered_df = df.query('age >= 30') print(filtered_df)
-
逻辑运算符:
and
: 与or
: 或not
: 非
例如:
# 筛选年龄大于30岁,或者工资小于等于4500的员工 filtered_df = df.query('age > 30 or salary <= 4500') print(filtered_df) # 筛选年龄不在25到30之间的员工 filtered_df = df.query('not (age >= 25 and age <= 30)') print(filtered_df)
-
成员运算符:
in
: 包含not in
: 不包含
例如:
# 筛选城市为北京或上海的员工 filtered_df = df.query('city in ["Beijing", "Shanghai"]') print(filtered_df) # 筛选城市不是北京或上海的员工 filtered_df = df.query('city not in ["Beijing", "Shanghai"]') print(filtered_df)
-
字符串操作:
==
: 完全匹配!=
: 不完全匹配str.contains()
: 包含某个子字符串 (需要先用@
引用变量)
例如:
# 筛选城市为北京的员工 filtered_df = df.query('city == "Beijing"') print(filtered_df) # 筛选城市包含"Shang"的员工 (注意:需要先将Series转换为字符串类型) search_term = "Shang" filtered_df = df[df['city'].str.contains(search_term)] #传统方式 print(filtered_df) #使用query(),需要先用@引用变量 filtered_df = df.query('city.str.contains(@search_term)', engine='python') # engine='python',因为默认引擎不支持str.contains() print(filtered_df)
注意: 在
query()
方法中,如果要在字符串中使用变量,需要使用@
符号来引用变量。 并且,默认的numexpr
引擎通常不支持字符串操作,需要指定engine='python'
。 -
使用变量:
可以使用
@
符号在查询表达式中引用本地变量。例如:
min_age = 30 max_salary = 6000 filtered_df = df.query('age > @min_age and salary < @max_salary') print(filtered_df)
-
使用算术运算符:
+
: 加-
: 减*
: 乘/
: 除**
: 幂
例如,假设我们想筛选出薪水是年龄的两倍以上的员工:
filtered_df = df.query('salary > age * 2') print(filtered_df)
-
访问DataFrame的属性和方法:
虽然
query()
主要用于筛选行,但你也可以在查询表达式中访问DataFrame的属性和方法。这通常与@
符号结合使用。例如,如果你想筛选出名字长度大于5的员工(虽然这通常不应该用
query()
做,这里只是为了演示):filtered_df = df.query('name.str.len() > 5', engine='python') print(filtered_df)
同样,由于默认引擎不支持字符串长度计算,需要指定
engine='python'
。
query()
的进阶用法
-
inplace
参数:如果你想直接修改原始DataFrame,可以将
inplace
参数设置为True
。df.query('age > 30', inplace=True) print(df) # df已经被修改
注意: 使用
inplace=True
要谨慎,因为它会直接修改原始数据,可能会导致一些不可预料的错误。 -
local_dict
参数:如果你想更明确地指定查询时使用的变量,可以使用
local_dict
参数。local_vars = {'min_age': 30, 'max_salary': 6000} filtered_df = df.query('age > min_age and salary < max_salary', local_dict=local_vars) print(filtered_df)
这种方式可以避免变量名冲突,使代码更清晰。
-
处理列名中的空格和特殊字符
如果你的 DataFrame 的列名中包含空格或者其他特殊字符,直接使用 query()
可能会遇到问题。为了解决这个问题,你可以使用反引号(`
)将列名括起来。
假设我们有一个包含空格的列名:
data = {'employee name': ['Alice', 'Bob', 'Charlie'],
'age': [25, 30, 35],
'salary': [5000, 6000, 7000]}
df = pd.DataFrame(data)
# 直接使用 query() 会报错
# filtered_df = df.query('employee name == "Alice"') # 报错
# 使用反引号括起来列名
filtered_df = df.query('`employee name` == "Alice"')
print(filtered_df)
- 多条件组合与优先级
在复杂的查询中,你可能需要组合多个条件。可以使用括号来明确指定条件的优先级。
例如,筛选出年龄大于 30 岁,或者薪水大于 6000 且城市为 "Shanghai" 的员工:
filtered_df = df.query('(age > 30) or (salary > 6000 and city == "Shanghai")')
print(filtered_df)
query()
的性能考量
虽然query()
方法很方便,但也要注意它的性能。在处理大型DataFrame时,query()
的性能可能会比传统的Pandas方式差一些。
numexpr
引擎:默认情况下,query()
使用numexpr
引擎来执行查询。numexpr
是一个高性能的数值计算库,可以加速数值运算。但是,numexpr
对于字符串操作的支持有限。python
引擎:如果查询表达式中包含字符串操作,或者numexpr
无法处理的复杂逻辑,可以指定engine='python'
来使用Python引擎。但是,Python引擎的性能通常比numexpr
差。
因此,在使用query()
时,要根据实际情况选择合适的引擎,并尽量避免在查询表达式中使用过于复杂的逻辑。
query()
与其他筛选方法的对比
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
传统Pandas方式 | 灵活,可以处理各种复杂的逻辑 | 代码冗长,可读性差 | 需要处理非常复杂的逻辑,或者对性能有极致要求的场景 |
query() 方法 |
代码简洁,可读性好,语法接近SQL | 性能可能不如传统Pandas方式,对字符串操作支持有限 | 需要进行简单的条件筛选,或者希望代码更简洁易读的场景 |
loc[] 方法 |
结合布尔索引,可以实现灵活的筛选,性能较好 | 代码相对冗长,需要手动构建布尔索引 | 需要结合布尔索引进行复杂的筛选,或者对性能有一定要求的场景 |
filter() 方法 |
可以按标签筛选行或列,支持正则表达式 | 主要用于标签筛选,不适合基于数值条件的筛选 | 需要按标签筛选行或列,或者需要使用正则表达式进行筛选的场景 |
总结
Pandas
的query()
方法是一个非常实用的工具,它可以让你用更简洁、更易读的语法来筛选数据。虽然它的性能可能不如传统的Pandas方式,但对于大多数场景来说,query()
已经足够使用了。掌握了query()
方法,可以大大提高你的数据分析效率,让你的代码更优雅、更Pythonic!
彩蛋:query()
的调试技巧
如果你的query()
表达式总是报错,或者返回的结果不符合预期,可以尝试以下调试技巧:
- 打印查询表达式: 在执行
query()
之前,先将查询表达式打印出来,检查是否有语法错误。 - 逐步分解查询: 将复杂的查询表达式分解成多个简单的查询,逐步调试,找出问题所在。
- 使用
try-except
语句: 在执行query()
时,使用try-except
语句捕获异常,可以更好地了解错误信息。 - 查阅官方文档: 如果遇到难以解决的问题,可以查阅Pandas官方文档,或者在Stack Overflow等社区提问。
好了,今天的讲座就到这里。希望大家能够熟练掌握query()
方法,让你的数据分析工作更加轻松愉快! 散会!