Chapter 04

过滤 · 排序 · 选择

用表达式做行过滤、多键排序、取头尾、去重,并掌握 when/then/otherwise 条件表达式——Polars 版的"if-else"。

4.1 filter:按条件筛选行

import polars as pl

df = pl.DataFrame({
    "name": ["Alice", "Bob", "Carol", "Dave"],
    "age": [30, 25, 35, 28],
    "city": ["北京", "上海", "北京", "广州"],
})

# 单条件
df.filter(pl.col("age") > 28)

# 多条件(AND):传多个参数或用 &
df.filter(
    pl.col("age") > 26,
    pl.col("city") == "北京",
)

# OR 条件
df.filter(
    (pl.col("city") == "上海") | (pl.col("age") > 33)
)
多参数 = AND

filter 接受多个表达式参数时,它们之间是逻辑关系。这比 & 写法更清爽,推荐。要 OR 就得显式用 |

4.2 sort:排序

# 单列升序
df.sort("age")

# 降序
df.sort("age", descending=True)

# 多键排序:先按 city 升,再按 age 降
df.sort(["city", "age"], descending=[False, True])

# 按表达式排序:如按 name 长度
df.sort(pl.col("name").str.len_chars())

# null 放最后
df.sort("age", nulls_last=True)

4.3 head / tail / limit / sample

df.head(2)          # 前 2 行
df.tail(2)          # 后 2 行
df.limit(3)         # 同 head
df.sample(n=2)      # 随机 2 行
df.slice(1, 2)      # 从第 1 行起取 2 行

# top_k / bottom_k:不必全排序即可取极值行
df.top_k(2, by="age")      # age 最大的 2 行

4.4 unique:去重

# 整行去重
df.unique()

# 按子集去重,保留首次出现
df.unique(subset=["city"], keep="first")

# 某列有多少不同取值
df.select(pl.col("city").n_unique())

# 各取值出现次数
df.select(pl.col("city").value_counts())

4.5 条件表达式 when / then / otherwise

Polars 没有 if-else 用在列上,而是用链式的 when().then().otherwise(),向量化、可嵌套。

pl.when(condition)
开始一个条件分支,参数是一个布尔表达式。
.then(value)
条件为真时取的值,可以是字面量,也可以是另一个表达式(如另一列)。
.otherwise(value)
所有条件都不满足时的默认值。省略则为 null
链式多分支
可以 when().then().when().then().otherwise() 表达多路分支,类似 SQL 的 CASE WHEN
向量化
整个条件表达式对整列一次性求值,不像 Python 循环逐行判断,性能高。
与 apply 的区别
能用 when/then 表达的逻辑,绝不要退回到 map_elements(逐行 Python 回调)——后者慢几十倍。
df = pl.DataFrame({"score": [92, 78, 55, 88]})

df.with_columns(
    pl.when(pl.col("score") >= 90).then(pl.lit("A"))
      .when(pl.col("score") >= 80).then(pl.lit("B"))
      .when(pl.col("score") >= 60).then(pl.lit("C"))
      .otherwise(pl.lit("F"))
      .alias("grade")
)
# grade: ["A", "C", "F", "B"]

4.6 链式管道风格

Polars 鼓励把多步操作写成一条清晰的链,每步返回新 DataFrame,无副作用。

result = (
    df
    .filter(pl.col("score") >= 60)
    .with_columns(
        pl.when(pl.col("score") >= 90)
          .then(pl.lit("优秀"))
          .otherwise(pl.lit("及格"))
          .alias("level")
    )
    .sort("score", descending=True)
)
无副作用

Polars 操作永远返回新对象,不修改原 DataFrame(没有 pandas 的 inplace=True,也就没有 SettingWithCopyWarning)。这让链式管道天然安全、易读、易并行。

4.7 小结与下一步