Python高级技术之:`Pandas`的`query()`方法:如何用类`SQL`语法进行数据筛选。

各位观众老爷,大家好!今天咱们来聊聊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非常相似,但也有一些细微的差别。下面我们来详细讲解一下常用的表达式规则:

  1. 比较运算符:

    • >: 大于
    • <: 小于
    • >=: 大于等于
    • <=: 小于等于
    • ==: 等于
    • !=: 不等于

    例如:

    # 筛选年龄大于等于30岁的员工
    filtered_df = df.query('age >= 30')
    print(filtered_df)
  2. 逻辑运算符:

    • 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)
  3. 成员运算符:

    • 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)
  4. 字符串操作:

    • ==: 完全匹配
    • !=: 不完全匹配
    • 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'

  5. 使用变量:

    可以使用@符号在查询表达式中引用本地变量。

    例如:

    min_age = 30
    max_salary = 6000
    filtered_df = df.query('age > @min_age and salary < @max_salary')
    print(filtered_df)
  6. 使用算术运算符:

    • +: 加
    • -: 减
    • *: 乘
    • /: 除
    • **: 幂

    例如,假设我们想筛选出薪水是年龄的两倍以上的员工:

    filtered_df = df.query('salary > age * 2')
    print(filtered_df)
  7. 访问DataFrame的属性和方法:

    虽然query()主要用于筛选行,但你也可以在查询表达式中访问DataFrame的属性和方法。这通常与@符号结合使用。

    例如,如果你想筛选出名字长度大于5的员工(虽然这通常不应该用query()做,这里只是为了演示):

    filtered_df = df.query('name.str.len() > 5', engine='python')
    print(filtered_df)

    同样,由于默认引擎不支持字符串长度计算,需要指定engine='python'

query()的进阶用法

  1. inplace参数:

    如果你想直接修改原始DataFrame,可以将inplace参数设置为True

    df.query('age > 30', inplace=True)
    print(df)  # df已经被修改

    注意: 使用inplace=True要谨慎,因为它会直接修改原始数据,可能会导致一些不可预料的错误。

  2. 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)

    这种方式可以避免变量名冲突,使代码更清晰。

  3. 处理列名中的空格和特殊字符

如果你的 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)
  1. 多条件组合与优先级

在复杂的查询中,你可能需要组合多个条件。可以使用括号来明确指定条件的优先级。

例如,筛选出年龄大于 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()方法 可以按标签筛选行或列,支持正则表达式 主要用于标签筛选,不适合基于数值条件的筛选 需要按标签筛选行或列,或者需要使用正则表达式进行筛选的场景

总结

Pandasquery()方法是一个非常实用的工具,它可以让你用更简洁、更易读的语法来筛选数据。虽然它的性能可能不如传统的Pandas方式,但对于大多数场景来说,query()已经足够使用了。掌握了query()方法,可以大大提高你的数据分析效率,让你的代码更优雅、更Pythonic!

彩蛋:query()的调试技巧

如果你的query()表达式总是报错,或者返回的结果不符合预期,可以尝试以下调试技巧:

  1. 打印查询表达式: 在执行query()之前,先将查询表达式打印出来,检查是否有语法错误。
  2. 逐步分解查询: 将复杂的查询表达式分解成多个简单的查询,逐步调试,找出问题所在。
  3. 使用try-except语句: 在执行query()时,使用try-except语句捕获异常,可以更好地了解错误信息。
  4. 查阅官方文档: 如果遇到难以解决的问题,可以查阅Pandas官方文档,或者在Stack Overflow等社区提问。

好了,今天的讲座就到这里。希望大家能够熟练掌握query()方法,让你的数据分析工作更加轻松愉快! 散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注