> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Polars 完全指南
- URL: https://blog.vercanti.com/polars-wan-quan-zhi-nan/
- Published: 2026-08-28T14:34:43.000Z
- Updated: 2026-08-28T14:57:09.000Z
- Description: 最后更新：2026-03-27 API 参考：https://docs.pola.rs/api/python/stable/reference/ 1. Polars完全指南 · 安装与版本(/polars-wan-quan-zhi-nan/#%E5%AE%89%E8%A3%85%E4%B8%8E%E7%89%88%E6%9C%AC) 2. Polars完全指南 · 核心概念(/polars-wan-quan-zhi-nan/#%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5) 3. Polars完全指南 · 数据创建(/polar
- Author: yellowdog
- Tags: Python, 框架与库

最后更新：2026-03-27

> 官方文档：<https://docs.pola.rs/>  
> 适用版本：Polars 1.x（2026-05-08 核实）  
> API 参考：<https://docs.pola.rs/api/python/stable/reference/>

---

## 目录

1. [Polars完全指南 · 安装与版本](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%AE%89%E8%A3%85%E4%B8%8E%E7%89%88%E6%9C%AC)
2. [Polars完全指南 · 核心概念](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5)
3. [Polars完全指南 · 数据创建](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E5%88%9B%E5%BB%BA)
4. [Polars完全指南 · 数据读写](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%86%99)
5. [Polars完全指南 · 数据查看](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E6%9F%A5%E7%9C%8B)
6. [Polars完全指南 · 表达式系统（Expression）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%B3%BB%E7%BB%9F%EF%BC%88expression%EF%BC%89)
7. [Polars完全指南 · DataFrame 操作](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#dataframe-%E6%93%8D%E4%BD%9C)
8. [Polars完全指南 · 分组聚合（GroupBy）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%88%86%E7%BB%84%E8%81%9A%E5%90%88%EF%BC%88groupby%EF%BC%89)
9. [Polars完全指南 · 连接操作](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E8%BF%9E%E6%8E%A5%E6%93%8D%E4%BD%9C)
10. [Polars完全指南 · 数据重塑](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E9%87%8D%E5%A1%91)
11. [Polars完全指南 · 合并](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%90%88%E5%B9%B6)
12. [Polars完全指南 · 字符串操作（str namespace）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%93%8D%E4%BD%9C%EF%BC%88str-namespace%EF%BC%89)
13. [Polars完全指南 · 日期时间操作（dt namespace）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4%E6%93%8D%E4%BD%9C%EF%BC%88dt-namespace%EF%BC%89)
14. [Polars完全指南 · 惰性执行（LazyFrame）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%83%B0%E6%80%A7%E6%89%A7%E8%A1%8C%EF%BC%88lazyframe%EF%BC%89)
15. [Polars完全指南 · 流式处理（Streaming）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%B5%81%E5%BC%8F%E5%A4%84%E7%90%86%EF%BC%88streaming%EF%BC%89)
16. [Polars完全指南 · 窗口函数](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E7%AA%97%E5%8F%A3%E5%87%BD%E6%95%B0)
17. [Polars完全指南 · 性能特性](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%80%A7%E8%83%BD%E7%89%B9%E6%80%A7)
18. [Polars完全指南 · 常用配合库](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%B8%B8%E7%94%A8%E9%85%8D%E5%90%88%E5%BA%93)
19. [Polars完全指南 · 最佳实践](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5)
20. [Polars完全指南 · 应用场景](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF)
21. [Polars完全指南 · 常见陷阱与注意事项](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%B8%B8%E8%A7%81%E9%99%B7%E9%98%B1%E4%B8%8E%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)

---

## 安装与版本

### 安装

```bash
# 基础安装
pip install polars

# 包含所有可选依赖（推荐生产环境）
pip install polars[all]

# 仅包含 pandas 互操作依赖
pip install polars[pandas]

# 仅包含 numpy 依赖
pip install polars[numpy]

# 包含 Excel 读写支持
pip install polars[xlsx2csv,openpyxl]

# 包含数据库连接支持
pip install polars[connectorx]

```

### 验证安装

```python
import polars as pl

print(pl.__version__)

```

### Polars vs Pandas 核心对比

| 维度     | Polars                     | Pandas               |
| ------ | -------------------------- | -------------------- |
| API 风格 | 表达式系统（Expression），链式调用     | 命令式，索引驱动             |
| 执行引擎   | Rust 编写，原生多线程              | Python + NumPy，默认单线程 |
| 内存模型   | Apache Arrow 列式存储          | NumPy 数组，非列式         |
| 多线程    | 自动并行，无需配置                  | 需借助 Dask/Modin 等     |
| 惰性求值   | 内置 LazyFrame，支持查询优化        | 无原生惰性支持              |
| 索引     | 无行索引（无 index 概念）           | 有行索引（Index 对象）       |
| 内存占用   | 通常低 30%\~60%               | 较高，有对象列开销            |
| 处理速度   | 大数据集通常快 5\~20 倍            | 小数据集差距不明显            |
| 生态成熟度  | 较新（2021 年），生态仍在完善          | 成熟，生态完整              |
| 缺失值处理  | null（整数也可为 null）与 NaN 严格区分 | NaN 和 None 混用        |

---

## 核心概念

### Series 和 DataFrame（Eager 模式）

Series 是一维数组，DataFrame 是二维表格，均基于 Apache Arrow 内存格式。

Eager 模式下操作立即执行并返回结果，适合探索性分析和小数据集。

```python
import polars as pl

# Series
s = pl.Series("age", [25, 30, 35, 28])
print(s.dtype)    # Int64
print(s.mean())   # 29.5

# DataFrame
df = pl.DataFrame({
    "name": ["Alice", "Bob", "Charlie"],
    "age": [25, 30, 35],
    "score": [88.5, 92.0, 79.5],
})
print(df)

```

### LazyFrame（Lazy 模式）

LazyFrame 记录操作但不立即执行，调用 `.collect()` 时才触发计算。Polars 在执行前对查询计划进行优化（谓词下推、投影下推等）。

```python
# 从 DataFrame 转换
lf = df.lazy()

# 构建查询计划（不执行）
result = (
    lf
    .filter(pl.col("age") > 25)
    .select(["name", "score"])
    .sort("score", descending=True)
)

# 查看执行计划
print(result.explain())

# 触发执行
df_result = result.collect()

```

### Expression（表达式系统）

表达式是 Polars 最核心的概念。表达式描述对列的操作，可以任意组合，Polars 自动并行执行多个表达式。

```python
# 表达式描述操作，不立即执行
expr = pl.col("age") * 2 + 1

# 在 select/filter/with_columns 中使用
df.select(expr)
df.with_columns(expr.alias("age_double"))

```

### Apache Arrow 内存模型

Polars 底层使用 Apache Arrow 列式存储：

- 同一列的数据在内存中连续存储，CPU 缓存命中率高
- 支持零拷贝与其他 Arrow 兼容库（PyArrow、DuckDB 等）互操作
- 整数列支持 null（不需要像 pandas 那样将整数列转为 float64 以存储 NaN）

---

## 数据创建

### pl.Series()

创建一维 Series。

```python
pl.Series(name, values, dtype=None, *, strict=True, nan_to_null=False)

```

| 参数            | 类型             | 默认值   | 说明                               |
| ------------- | -------------- | ----- | -------------------------------- |
| name          | str            | 必填    | Series 名称                        |
| values        | array-like     | 必填    | 数据，支持 list/numpy/pyarrow 等       |
| dtype         | PolarsDataType | None  | 指定数据类型，None 则自动推断                |
| strict        | bool           | True  | True 时值不符合 dtype 则报错，False 则尝试转换 |
| nan\_to\_null | bool           | False | 将浮点 NaN 转为 null                  |

```python
s1 = pl.Series("scores", [1, 2, 3, 4, 5])
s2 = pl.Series("prices", [1.5, 2.3, None, 4.1], dtype=pl.Float32)
s3 = pl.Series("flags", [True, False, True])
s4 = pl.Series("names", ["Alice", "Bob", "Charlie"])

```

### pl.DataFrame()

创建二维 DataFrame。

```python
pl.DataFrame(data=None, schema=None, *, schema_overrides=None, strict=True, orient=None, infer_schema_length=100, nan_to_null=False)

```

| 参数                    | 类型                               | 默认值   | 说明               |
| --------------------- | -------------------------------- | ----- | ---------------- |
| data                  | dict / list / numpy / pandas     | None  | 输入数据             |
| schema                | list\[str\] / dict\[str, dtype\] | None  | 列名或列名到类型的映射      |
| schema\_overrides     | dict\[str, dtype\]               | None  | 覆盖部分列的类型推断结果     |
| strict                | bool                             | True  | 类型不匹配时是否报错       |
| orient                | "col" / "row"                    | None  | data 为嵌套列表时的方向   |
| infer\_schema\_length | int                              | 100   | 推断 schema 时扫描的行数 |
| nan\_to\_null         | bool                             | False | 将 NaN 转为 null    |

```python
# 从字典创建（最常用）
df = pl.DataFrame({
    "id": [1, 2, 3],
    "name": ["Alice", "Bob", "Charlie"],
    "score": [88.5, 92.0, 79.5],
})

# 从列表创建（按行）
df = pl.DataFrame(
    [[1, "Alice", 88.5], [2, "Bob", 92.0]],
    schema=["id", "name", "score"],
    orient="row",
)

# 从列表创建（按列）
df = pl.DataFrame(
    [[1, 2, 3], ["Alice", "Bob", "Charlie"]],
    schema=["id", "name"],
    orient="col",
)

# 指定类型
df = pl.DataFrame(
    {"id": [1, 2, 3], "score": [88.5, 92.0, 79.5]},
    schema={"id": pl.Int32, "score": pl.Float32},
)

# schema_overrides 只覆盖部分列
df = pl.DataFrame(
    {"id": [1, 2, 3], "name": ["Alice", "Bob", "Charlie"], "score": [88, 92, 79]},
    schema_overrides={"score": pl.Float64},
)

```

### 从 Pandas 互转

```python
# pl.from_pandas(df, schema_overrides=None, rechunk=True, nan_to_null=True, include_index=False)

```

| 参数                | 类型                               | 默认值   | 说明                     |
| ----------------- | -------------------------------- | ----- | ---------------------- |
| df                | pandas.DataFrame / pandas.Series | 必填    | 输入的 pandas 对象          |
| schema\_overrides | dict\[str, dtype\]               | None  | 覆盖部分列的类型               |
| rechunk           | bool                             | True  | 重新分配内存为单块，提升后续性能       |
| nan\_to\_null     | bool                             | True  | 将 NaN 转为 null          |
| include\_index    | bool                             | False | 是否将 pandas index 作为列导入 |

```python
import pandas as pd
import polars as pl

pdf = pd.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]})

# pandas -> polars
df = pl.from_pandas(pdf)
df = pl.from_pandas(pdf, include_index=True)

# polars -> pandas
pdf2 = df.to_pandas()
pdf2 = df.to_pandas(use_pyarrow_extension_array=True)  # 保留 null（不转 NaN）

```

### 从 NumPy 和 Arrow 创建

```python
import numpy as np
import pyarrow as pa

# 从 numpy 创建
arr = np.array([[1, 2, 3], [4, 5, 6]])
df = pl.from_numpy(arr, schema=["a", "b", "c"])

# Series from numpy
s = pl.Series("data", np.array([1.0, 2.0, 3.0]))

# 从 Arrow Table 创建
table = pa.table({"a": [1, 2, 3], "b": ["x", "y", "z"]})
df = pl.from_arrow(table)

# Polars -> Arrow
arrow_table = df.to_arrow()

```

---

## 数据读写

### pl.read\_csv()

读取 CSV 文件为 DataFrame（Eager 模式）。

```python
pl.read_csv(source, *, has_header=True, columns=None, new_columns=None, separator=",", comment_prefix=None, quote_char='"', skip_rows=0, skip_rows_after_header=0, dtypes=None, schema=None, schema_overrides=None, null_values=None, missing_utf8_is_empty_string=False, ignore_errors=False, try_parse_dates=False, n_threads=None, infer_schema_length=100, batch_size=8192, n_rows=None, encoding="utf8", low_memory=False, rechunk=True, use_pyarrow=False, storage_options=None, skip_rows=0, row_index_name=None, row_index_offset=0, sample_size=1024, eol_char="\n", raise_if_empty=True, truncate_ragged_lines=False, decimal_comma=False)

```

常用参数：

| 参数                        | 类型                 | 默认值    | 说明                                  |
| ------------------------- | ------------------ | ------ | ----------------------------------- |
| source                    | str / Path / bytes | 必填     | 文件路径、URL 或字节内容                      |
| has\_header               | bool               | True   | 第一行是否为表头                            |
| separator                 | str                | ","    | 字段分隔符                               |
| columns                   | list\[str/int\]    | None   | 只读取指定列（列名或列索引）                      |
| new\_columns              | list\[str\]        | None   | 为列重命名（按顺序替换）                        |
| dtypes                    | dict\[str, dtype\] | None   | 指定列的数据类型（旧参数，建议用 schema\_overrides） |
| schema\_overrides         | dict\[str, dtype\] | None   | 覆盖部分列的推断类型                          |
| null\_values              | str / list / dict  | None   | 将指定字符串识别为 null                      |
| ignore\_errors            | bool               | False  | 解析失败的行用 null 填充而非报错                 |
| try\_parse\_dates         | bool               | False  | 尝试自动解析日期列                           |
| n\_rows                   | int                | None   | 只读取前 N 行                            |
| skip\_rows                | int                | 0      | 跳过文件开头 N 行                          |
| skip\_rows\_after\_header | int                | 0      | 跳过表头后的 N 行                          |
| encoding                  | str                | "utf8" | 文件编码，常用 "utf8-lossy" 容错             |
| low\_memory               | bool               | False  | 降低内存使用（牺牲速度）                        |
| infer\_schema\_length     | int                | 100    | 推断类型时扫描的行数，None 表示全部                |
| n\_threads                | int                | None   | 并行线程数，None 表示自动                     |
| rechunk                   | bool               | True   | 读取后重整内存块                            |
| row\_index\_name          | str                | None   | 添加行号列，指定列名                          |
| row\_index\_offset        | int                | 0      | 行号起始值                               |
| truncate\_ragged\_lines   | bool               | False  | 列数不足的行截断而不报错                        |

```python
# 基础读取
df = pl.read_csv("data.csv")

# 常用选项
df = pl.read_csv(
    "data.csv",
    separator="\t",                          # Tab 分隔
    columns=["id", "name", "score"],         # 只读指定列
    null_values=["", "NA", "N/A", "null"],   # 多种 null 表示
    try_parse_dates=True,                    # 自动解析日期
    n_rows=10000,                            # 只读前 1 万行
    schema_overrides={"id": pl.Int32},       # 覆盖类型
    encoding="utf8-lossy",                   # 容忍编码错误
)

# 无表头文件
df = pl.read_csv("data.csv", has_header=False, new_columns=["a", "b", "c"])

```

### pl.read\_parquet()

读取 Parquet 文件为 DataFrame。

```python
pl.read_parquet(source, *, columns=None, n_rows=None, use_statistics=True, hive_partitioning=None, hive_schema=None, try_convert_dates=False, parallel="auto", row_index_name=None, row_index_offset=0, low_memory=False, storage_options=None, use_pyarrow=False, pyarrow_options=None, memory_map=True, rechunk=False)

```

| 参数                 | 类型                                          | 默认值    | 说明                  |
| ------------------ | ------------------------------------------- | ------ | ------------------- |
| source             | str / Path / list                           | 必填     | 文件路径，支持通配符和列表       |
| columns            | list\[str/int\]                             | None   | 只读指定列               |
| n\_rows            | int                                         | None   | 只读前 N 行             |
| use\_statistics    | bool                                        | True   | 利用 Parquet 统计信息加速过滤 |
| parallel           | "auto" / "columns" / "row\_groups" / "none" | "auto" | 并行读取策略              |
| low\_memory        | bool                                        | False  | 降低内存使用              |
| row\_index\_name   | str                                         | None   | 添加行号列               |
| hive\_partitioning | bool                                        | None   | 是否解析 Hive 分区路径      |

```python
df = pl.read_parquet("data.parquet")
df = pl.read_parquet("data.parquet", columns=["id", "value"])
df = pl.read_parquet("data/*.parquet")  # 读取多个文件

```

### pl.read\_json() / pl.read\_ndjson()

```python
# JSON 数组格式：[{...}, {...}]
df = pl.read_json("data.json")

# NDJSON（每行一个 JSON 对象）格式：
# {"a": 1, "b": "x"}
# {"a": 2, "b": "y"}
df = pl.read_ndjson("data.ndjson")
df = pl.read_ndjson("data.ndjson", schema_overrides={"a": pl.Int32})

```

### pl.read\_excel()

```python
pl.read_excel(source, *, sheet_id=None, sheet_name=None, engine=None, engine_options=None, read_options=None, schema_overrides=None, infer_schema_length=100, columns=None, raise_if_empty=True)

```

| 参数                    | 类型                 | 默认值  | 说明                                           |
| --------------------- | ------------------ | ---- | -------------------------------------------- |
| source                | str / Path / bytes | 必填   | 文件路径                                         |
| sheet\_id             | int                | None | 按索引选择 Sheet（从 1 开始）                          |
| sheet\_name           | str                | None | 按名称选择 Sheet                                  |
| engine                | str                | None | 解析引擎："calamine"（推荐）/ "openpyxl" / "xlsx2csv" |
| schema\_overrides     | dict               | None | 覆盖列类型                                        |
| infer\_schema\_length | int                | 100  | 推断类型扫描行数                                     |
| columns               | list               | None | 只读指定列                                        |

```python
# 需要安装：pip install polars[xlsx2csv] 或 pip install polars[openpyxl]
df = pl.read_excel("data.xlsx")
df = pl.read_excel("data.xlsx", sheet_name="Sheet2", engine="calamine")

```

### pl.scan\_csv() / pl.scan\_parquet()

返回 LazyFrame，不立即读取数据，配合惰性执行使用。读取大文件的首选方式。

```python
# scan_csv 参数与 read_csv 基本相同，返回 LazyFrame
lf = pl.scan_csv("large_data.csv")
lf = pl.scan_csv(
    "large_data.csv",
    separator=",",
    null_values=[""],
    try_parse_dates=True,
    schema_overrides={"id": pl.Int32},
)

# scan_parquet 支持通配符和目录
lf = pl.scan_parquet("data.parquet")
lf = pl.scan_parquet("data/*.parquet")
lf = pl.scan_parquet("hive_data/", hive_partitioning=True)

# 配合惰性操作，只读取需要的列和行
result = (
    pl.scan_csv("large_data.csv")
    .filter(pl.col("date") >= "2024-01-01")
    .select(["id", "name", "value"])
    .collect()
)

```

### 写出文件

#### df.write\_csv()

```python
df.write_csv(file=None, *, has_header=True, separator=",", line_terminator="\n", quote_char='"', batch_size=1024, datetime_format=None, date_format=None, time_format=None, float_precision=None, null_value="", quote_style="necessary")

```

| 参数               | 类型                | 默认值         | 说明                                                     |
| ---------------- | ----------------- | ----------- | ------------------------------------------------------ |
| file             | str / Path / None | None        | 输出路径，None 则返回字符串                                       |
| has\_header      | bool              | True        | 是否写出表头                                                 |
| separator        | str               | ","         | 字段分隔符                                                  |
| null\_value      | str               | ""          | null 的字符串表示                                            |
| float\_precision | int               | None        | 浮点数精度                                                  |
| datetime\_format | str               | None        | 日期时间格式字符串                                              |
| quote\_style     | str               | "necessary" | 引号策略："necessary" / "always" / "never" / "non\_numeric" |

```python
df.write_csv("output.csv")
df.write_csv("output.csv", separator="\t", null_value="NULL")
csv_str = df.write_csv()  # 返回字符串

```

#### df.write\_parquet()

```python
df.write_parquet(file, *, compression="zstd", compression_level=None, statistics=False, row_group_size=None, data_page_size=None, use_pyarrow=False, pyarrow_options=None)

```

| 参数                 | 类型         | 默认值    | 说明                                                                          |
| ------------------ | ---------- | ------ | --------------------------------------------------------------------------- |
| file               | str / Path | 必填     | 输出路径                                                                        |
| compression        | str        | "zstd" | 压缩算法："lz4" / "uncompressed" / "snappy" / "gzip" / "lzo" / "brotli" / "zstd" |
| compression\_level | int        | None   | 压缩级别（依算法而定）                                                                 |
| statistics         | bool       | False  | 写入列统计信息（加速谓词下推）                                                             |
| row\_group\_size   | int        | None   | 每个 row group 的行数                                                            |

```python
df.write_parquet("output.parquet")
df.write_parquet("output.parquet", compression="snappy", statistics=True)

```

#### df.write\_json() / df.write\_ndjson()

```python
df.write_json("output.json")
df.write_json("output.json", pretty=True)

df.write_ndjson("output.ndjson")

```

---

## 数据查看

### 基础查看方法

```python
df.head(5)        # 前 N 行，默认 5
df.tail(5)        # 后 N 行，默认 5
df.sample(10)     # 随机采样 N 行
df.sample(fraction=0.1)  # 随机采样 10%

```

#### sample() 参数表

| 参数                | 类型    | 默认值   | 说明                  |
| ----------------- | ----- | ----- | ------------------- |
| n                 | int   | None  | 采样行数，与 fraction 二选一 |
| fraction          | float | None  | 采样比例（0.0\~1.0）      |
| with\_replacement | bool  | False | 是否有放回采样             |
| shuffle           | bool  | False | 是否打乱顺序              |
| seed              | int   | None  | 随机种子，保证可复现          |

### 结构信息

```python
df.shape           # (行数, 列数) 元组
df.columns         # 列名列表
df.dtypes          # 类型列表（与 columns 一一对应）
df.schema          # {列名: 类型} 字典
df.height          # 行数
df.width           # 列数

```

### describe()

生成描述性统计汇总。

```python
df.describe()
df.describe(percentiles=[0.25, 0.5, 0.75])

```

| 参数            | 类型            | 默认值                 | 说明      |
| ------------- | ------------- | ------------------- | ------- |
| percentiles   | list\[float\] | \[0.25, 0.5, 0.75\] | 要计算的分位数 |
| interpolation | str           | "nearest"           | 分位数插值方式 |

### glimpse()

Polars 特有方法，纵向展示每列的名称、类型和前几个值，适合列数多的 DataFrame。

```python
df.glimpse()
df.glimpse(max_items_per_column=5, max_colname_length=30)

```

输出示例：

```
Rows: 3
Columns: 3
$ id     <i64> 1, 2, 3
$ name   <str> 'Alice', 'Bob', 'Charlie'
$ score  <f64> 88.5, 92.0, 79.5

```

---

## 表达式系统（Expression）

表达式是 Polars 最核心的概念。表达式是对列操作的描述，可自由组合，Polars 会自动并行执行多个无依赖关系的表达式。表达式在 `select()`、`filter()`、`with_columns()`、`agg()` 等上下文中使用。

### pl.col() — 列引用

```python
# 单列
pl.col("name")
pl.col("age")

# 多列（返回多个表达式）
pl.col("age", "score")
pl.col(["age", "score"])

# 正则匹配列名（以 ^ 开头，以 $ 结尾）
pl.col("^score.*$")     # 匹配所有以 score 开头的列

# 按 dtype 选择
pl.col(pl.Int64)         # 所有 Int64 列
pl.col(pl.Utf8)          # 所有字符串列（Utf8 即 String）
pl.col(pl.NUMERIC_DTYPES)  # 所有数值类型列

```

### pl.lit() — 字面量

创建常量表达式。

```python
pl.lit(42)
pl.lit("hello")
pl.lit(3.14)
pl.lit(True)
pl.lit(None)

# 使用场景：与列组合
df.with_columns((pl.col("score") + pl.lit(10)).alias("score_adjusted"))

```

### pl.all() / pl.exclude()

```python
pl.all()                          # 所有列
pl.exclude("id")                  # 排除单列
pl.exclude(["id", "name"])        # 排除多列
pl.exclude(pl.Utf8)               # 排除字符串列

# 示例
df.select(pl.all())               # 等同于 df
df.select(pl.exclude("id"))       # 删除 id 列
df.select(pl.all().sort())        # 对所有列排序

```

### 算术表达式

```python
pl.col("price") + pl.col("tax")
pl.col("price") * 1.1
pl.col("score") ** 2           # 乘方
pl.col("value") % 10           # 取模
pl.col("a") // pl.col("b")    # 整除

```

### 比较与逻辑表达式

```python
# 比较
pl.col("age") > 25
pl.col("age") >= 25
pl.col("age") == 25
pl.col("age") != 25
pl.col("age").is_between(20, 30)   # 闭区间
pl.col("age").is_between(20, 30, closed="left")  # 左闭右开

# 逻辑（推荐用运算符）
(pl.col("age") > 25) & (pl.col("score") > 80)   # AND
(pl.col("age") > 25) | (pl.col("score") > 90)   # OR
~pl.col("flag")                                   # NOT

# 也可以用方法（更明确）
pl.col("age").gt(25).and_(pl.col("score").gt(80))
pl.col("age").gt(25).or_(pl.col("score").gt(90))
pl.col("flag").not_()

```

### alias() — 别名

```python
pl.col("score").alias("成绩")
(pl.col("price") * 1.1).alias("price_with_tax")
pl.all().name.prefix("new_")    # 为所有列名加前缀
pl.all().name.suffix("_orig")   # 为所有列名加后缀

```

### cast() — 类型转换

```python
pl.col("id").cast(pl.Int32)
pl.col("score").cast(pl.Float32)
pl.col("flag").cast(pl.Boolean)
pl.col("date_str").cast(pl.Date)

```

| 参数     | 类型             | 默认值  | 说明                            |
| ------ | -------------- | ---- | ----------------------------- |
| dtype  | PolarsDataType | 必填   | 目标数据类型                        |
| strict | bool           | True | True 时转换失败报错，False 时失败返回 null |

```python
# 容错转换
pl.col("value").cast(pl.Int64, strict=False)  # 无法转换的变为 null

```

### 空值处理

```python
pl.col("score").is_null()          # 是否为 null，返回布尔 Series
pl.col("score").is_not_null()      # 是否非 null
pl.col("value").is_nan()           # 是否为 NaN（仅浮点列）
pl.col("value").is_not_nan()

# fill_null：填充 null
pl.col("score").fill_null(0)                    # 用常量填充
pl.col("score").fill_null(pl.col("score").mean())   # 用均值填充
pl.col("score").fill_null(strategy="forward")   # 前向填充
pl.col("score").fill_null(strategy="backward")  # 后向填充
pl.col("score").fill_null(strategy="mean")      # 用均值填充
pl.col("score").fill_null(strategy="min")
pl.col("score").fill_null(strategy="max")

```

fill\_null 的 strategy 参数：

| strategy   | 说明             |
| ---------- | -------------- |
| "forward"  | 用前一个非 null 值填充 |
| "backward" | 用后一个非 null 值填充 |
| "mean"     | 用列均值填充         |
| "min"      | 用列最小值填充        |
| "max"      | 用列最大值填充        |
| "zero"     | 用 0 填充         |
| "one"      | 用 1 填充         |

```python
# fill_nan：填充浮点 NaN
pl.col("value").fill_nan(0.0)
pl.col("value").fill_nan(None)   # NaN -> null

# drop_nulls：在 DataFrame 层面删除含 null 的行
df.drop_nulls()
df.drop_nulls(subset=["score"])  # 只检查指定列

```

### 排序表达式

```python
pl.col("score").sort()                         # 升序
pl.col("score").sort(descending=True)          # 降序
pl.col("score").sort(nulls_last=True)          # null 排在末尾
pl.col("score").arg_sort()                     # 返回排序索引（位置）
pl.col("name").sort_by("score")                # 按另一列排序
pl.col("name").sort_by("score", descending=True)

```

### 聚合表达式

```python
pl.col("score").sum()
pl.col("score").mean()
pl.col("score").median()
pl.col("score").std()          # 标准差（ddof=1）
pl.col("score").var()          # 方差（ddof=1）
pl.col("score").min()
pl.col("score").max()
pl.col("id").count()           # 非 null 计数
pl.col("id").len()             # 总行数（包含 null）
pl.col("name").n_unique()      # 唯一值个数
pl.col("name").first()         # 第一个值
pl.col("name").last()          # 最后一个值
pl.col("score").quantile(0.9)  # 分位数
pl.col("score").sum() / pl.col("score").count()  # 自定义聚合

```

### when().then().otherwise() — 条件表达式

Polars 的条件分支，等价于 SQL 的 CASE WHEN，比 pandas apply 快得多。

```python
# 基础用法
pl.when(pl.col("score") >= 90).then(pl.lit("A")).otherwise(pl.lit("B"))

# 多条件链式
grade = (
    pl.when(pl.col("score") >= 90).then(pl.lit("A"))
    .when(pl.col("score") >= 80).then(pl.lit("B"))
    .when(pl.col("score") >= 70).then(pl.lit("C"))
    .otherwise(pl.lit("D"))
)

df = df.with_columns(grade.alias("grade"))

# otherwise 引用其他列
result = (
    pl.when(pl.col("value").is_null())
    .then(pl.col("default_value"))
    .otherwise(pl.col("value"))
    .alias("value_filled")
)

# otherwise 可以省略（null 填充）
pl.when(pl.col("score") > 100).then(pl.lit(100))  # 超过 100 的截断，其余为 null

```

### 字符串表达式（str namespace）

详见 [Polars完全指南 · 字符串操作（str namespace）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%93%8D%E4%BD%9C%EF%BC%88str-namespace%EF%BC%89)

```python
pl.col("name").str.to_uppercase()
pl.col("text").str.contains("python")
pl.col("date_str").str.strptime(pl.Date, "%Y-%m-%d")

```

### 日期时间表达式（dt namespace）

详见 [Polars完全指南 · 日期时间操作（dt namespace）](https://blog.vercanti.com/polars-wan-quan-zhi-nan/#%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4%E6%93%8D%E4%BD%9C%EF%BC%88dt-namespace%EF%BC%89)

```python
pl.col("date").dt.year()
pl.col("datetime").dt.strftime("%Y-%m-%d")

```

### 列表表达式（list namespace）

```python
# 假设列为 List[Int64] 类型
pl.col("scores").list.len()           # 每个列表的长度
pl.col("scores").list.sum()           # 每个列表元素求和
pl.col("scores").list.mean()          # 每个列表均值
pl.col("scores").list.max()
pl.col("scores").list.min()
pl.col("scores").list.first()         # 每个列表的第一个元素
pl.col("scores").list.last()
pl.col("scores").list.get(0)          # 按索引取元素
pl.col("scores").list.contains(90)    # 是否包含某值
pl.col("scores").list.sort()          # 排序每个列表
pl.col("scores").list.unique()        # 去重每个列表
pl.col("a").list.concat(pl.col("b"))  # 连接两个列表列

df = pl.DataFrame({"scores": [[1, 2, 3], [4, 5], [6]]})
df.with_columns(pl.col("scores").list.len().alias("n"))

```

### 结构体表达式（struct namespace）

```python
# 创建结构体
df.select(pl.struct("a", "b").alias("ab"))

# 访问字段
pl.col("struct_col").struct.field("a")
pl.col("struct_col").struct.rename_fields(["x", "y"])

# 解构
df.with_columns(pl.col("struct_col").struct.unnest())

```

---

## DataFrame 操作

### select() — 列选取

选择列，可传入表达式。只返回选中的列。

```python
df.select(expr, *more_exprs)

```

| 参数    | 类型                | 默认值 | 说明         |
| ----- | ----------------- | --- | ---------- |
| exprs | str / Expr / list | 必填  | 列名、表达式或其列表 |

```python
df.select("name")
df.select(["name", "score"])
df.select(pl.col("name"), pl.col("score") * 2)
df.select(pl.all().sort())                  # 对所有列排序
df.select(pl.col("^score.*$"))             # 正则选列
df.select(pl.col(pl.NUMERIC_DTYPES))       # 选所有数值列
df.select((pl.col("score") - pl.col("score").mean()).alias("score_centered"))

```

### filter() — 行过滤

按条件过滤行，保留满足条件的行。

```python
df.filter(expr)

```

| 参数    | 类型                  | 默认值 | 说明                   |
| ----- | ------------------- | --- | -------------------- |
| exprs | Expr / list\[Expr\] | 必填  | 布尔表达式，多个表达式间为 AND 关系 |

```python
df.filter(pl.col("age") > 25)
df.filter((pl.col("age") > 25) & (pl.col("score") > 80))

# 多表达式参数等价于 AND
df.filter(pl.col("age") > 25, pl.col("score") > 80)

# is_in 过滤
df.filter(pl.col("city").is_in(["Beijing", "Shanghai"]))

# 字符串过滤
df.filter(pl.col("name").str.starts_with("A"))

```

### with\_columns() — 添加或修改列

添加新列或修改已有列。原有列不受影响，返回包含所有原列加上新列的 DataFrame。

```python
df.with_columns(expr, *more_exprs)

```

| 参数    | 类型                | 默认值 | 说明               |
| ----- | ----------------- | --- | ---------------- |
| exprs | str / Expr / list | 必填  | 表达式列表，alias 决定列名 |

```python
# 添加新列
df.with_columns((pl.col("price") * 1.1).alias("price_with_tax"))

# 修改已有列（alias 与现有列同名则覆盖）
df.with_columns(pl.col("score").cast(pl.Float32))

# 同时添加多列（并行执行）
df.with_columns(
    (pl.col("price") * 1.1).alias("price_tax"),
    pl.col("name").str.to_uppercase().alias("name_upper"),
    pl.col("score").fill_null(0).alias("score_filled"),
)

```

### rename()

重命名列。

```python
df.rename(mapping)

```

| 参数      | 类型               | 默认值 | 说明            |
| ------- | ---------------- | --- | ------------- |
| mapping | dict\[str, str\] | 必填  | {旧列名: 新列名} 字典 |

```python
df.rename({"name": "user_name", "score": "user_score"})

```

### drop()

删除列。

```python
df.drop(columns, *more_columns)

```

| 参数      | 类型                | 默认值 | 说明     |
| ------- | ----------------- | --- | ------ |
| columns | str / list\[str\] | 必填  | 要删除的列名 |

```python
df.drop("id")
df.drop(["id", "temp_col"])

```

### sort()

按列排序。

```python
df.sort(by, *, descending=False, nulls_last=False, multithreaded=True, maintain_order=False)

```

| 参数              | 类型                  | 默认值   | 说明                 |
| --------------- | ------------------- | ----- | ------------------ |
| by              | str / Expr / list   | 必填    | 排序列或表达式            |
| descending      | bool / list\[bool\] | False | 是否降序，可按列分别指定       |
| nulls\_last     | bool                | False | null 值是否排在末尾       |
| multithreaded   | bool                | True  | 是否多线程排序            |
| maintain\_order | bool                | False | 相同值保持原始顺序（稳定排序，较慢） |

```python
df.sort("score")
df.sort("score", descending=True)
df.sort(["score", "name"], descending=[True, False])
df.sort("score", nulls_last=True)

```

### unique()

去除重复行。

```python
df.unique(subset=None, *, keep="any", maintain_order=False)

```

| 参数              | 类型          | 默认值   | 说明                                                           |
| --------------- | ----------- | ----- | ------------------------------------------------------------ |
| subset          | list\[str\] | None  | 用于判断重复的列，None 则考虑所有列                                         |
| keep            | str         | "any" | 保留哪条："any"（任意一条）/ "first"（第一条）/ "last"（最后一条）/ "none"（删除所有重复） |
| maintain\_order | bool        | False | 保持原始行顺序（较慢）                                                  |

```python
df.unique()
df.unique(subset=["user_id"])
df.unique(subset=["user_id"], keep="last", maintain_order=True)

```

### slice()

按位置切片。

```python
df.slice(offset, length=None)

```

| 参数     | 类型  | 默认值  | 说明              |
| ------ | --- | ---- | --------------- |
| offset | int | 必填   | 起始位置（支持负数，从末尾计） |
| length | int | None | 切片长度，None 则取到末尾 |

```python
df.slice(10, 20)      # 第 10~29 行
df.slice(-5)          # 最后 5 行

```

### 数据提取

```python
# 获取单行（返回 dict）
df.row(0)
df.row(0, named=True)          # 返回 {列名: 值} 的字典

# 获取多行（返回 list of tuple）
df.rows()
df.rows(named=True)            # 返回 list of dict

# 转为 Series
df.to_series(0)                # 按索引
df["name"]                     # 按列名（等同于 df.get_column("name")）

# 转为字典列表
df.to_dicts()                  # [{列名: 值}, ...]

# 转为 numpy
df.to_numpy()                  # 二维 numpy 数组
df["score"].to_numpy()         # 一维数组

```

---

## 分组聚合（GroupBy）

### group\_by()

按列分组。

```python
df.group_by(by, *more_by, maintain_order=False)

```

| 参数              | 类型                | 默认值   | 说明                   |
| --------------- | ----------------- | ----- | -------------------- |
| by              | str / Expr / list | 必填    | 分组列                  |
| maintain\_order | bool              | False | 保持分组顺序（稍慢），默认结果顺序不保证 |

### .agg() — 聚合

```python
# 基础聚合
df.group_by("category").agg(
    pl.col("score").mean().alias("avg_score"),
    pl.col("score").max().alias("max_score"),
    pl.col("id").count().alias("count"),
)

# 对所有数值列求均值
df.group_by("category").agg(pl.col(pl.NUMERIC_DTYPES).mean())

# 多组聚合
df.group_by(["category", "region"]).agg(
    pl.col("revenue").sum(),
    pl.col("quantity").sum(),
    pl.n_unique("user_id").alias("unique_users"),
)

# 收集列值到列表
df.group_by("user_id").agg(pl.col("action").alias("actions"))

# 保证顺序
df.group_by("category", maintain_order=True).agg(pl.col("score").mean())

```

### group\_by\_dynamic() — 动态分组（时序）

基于时间窗口的分组，处理时序数据的核心方法。

```python
df.group_by_dynamic(index_column, *, every, period=None, offset=None, label="left", include_boundaries=False, closed="left", group_by=None, start_by="window")

```

| 参数                  | 类型              | 默认值      | 说明                                        |
| ------------------- | --------------- | -------- | ----------------------------------------- |
| index\_column       | str             | 必填       | 时间索引列（必须已排序）                              |
| every               | str / timedelta | 必填       | 窗口间隔，如 "1d" / "1h" / "30m" / "1w"         |
| period              | str / timedelta | None     | 窗口大小，默认等于 every（滚动窗口）                     |
| offset              | str / timedelta | None     | 窗口偏移量                                     |
| label               | str             | "left"   | 窗口标签："left"（起始）/ "right"（结束）/ "datapoint" |
| include\_boundaries | bool            | False    | 是否在结果中包含窗口起止列                             |
| closed              | str             | "left"   | 窗口边界："left" / "right" / "both" / "none"   |
| group\_by           | str / list      | None     | 额外的分组键                                    |
| start\_by           | str             | "window" | 窗口起始策略                                    |

```python
# 按日聚合
df.sort("timestamp").group_by_dynamic("timestamp", every="1d").agg(
    pl.col("value").sum().alias("daily_sum"),
    pl.col("value").mean().alias("daily_avg"),
)

# 按小时聚合，并按 user_id 额外分组
df.sort("timestamp").group_by_dynamic(
    "timestamp", every="1h", group_by="user_id"
).agg(
    pl.col("event_count").sum(),
)

# 滑动窗口（period > every）
df.sort("date").group_by_dynamic(
    "date", every="1d", period="7d"  # 每天一个 7 天滑动窗口
).agg(pl.col("sales").sum().alias("rolling_7d_sum"))

```

时间间隔字符串说明：

| 字符串   | 含义   |
| ----- | ---- |
| "1ns" | 1 纳秒 |
| "1us" | 1 微秒 |
| "1ms" | 1 毫秒 |
| "1s"  | 1 秒  |
| "1m"  | 1 分钟 |
| "1h"  | 1 小时 |
| "1d"  | 1 天  |
| "1w"  | 1 周  |
| "1mo" | 1 个月 |
| "1q"  | 1 季度 |
| "1y"  | 1 年  |

### group\_by\_rolling()

基于行的滑动窗口分组（已被 group\_by\_dynamic 的 period 参数覆盖，较少使用）。

```python
df.sort("date").group_by_rolling(
    index_column="date",
    period="7d",
    group_by="category",
).agg(pl.col("sales").sum())

```

---

## 连接操作

### join()

DataFrame 连接。

```python
df.join(other, on=None, how="inner", *, left_on=None, right_on=None, suffix="_right", validate="m:m", join_nulls=False, coalesce=None)

```

| 参数          | 类型         | 默认值       | 说明                                   |
| ----------- | ---------- | --------- | ------------------------------------ |
| other       | DataFrame  | 必填        | 右侧 DataFrame                         |
| on          | str / list | None      | 两侧同名的连接键，与 left\_on/right\_on 二选一    |
| how         | str        | "inner"   | 连接类型，见下表                             |
| left\_on    | str / list | None      | 左侧连接键（列名不同时使用）                       |
| right\_on   | str / list | None      | 右侧连接键                                |
| suffix      | str        | "\_right" | 右侧重名列的后缀                             |
| validate    | str        | "m:m"     | 键唯一性校验："1:1" / "1:m" / "m:1" / "m:m" |
| join\_nulls | bool       | False     | null 是否视为相等的键                        |
| coalesce    | bool       | None      | 是否合并同名键列                             |

how 类型说明：

| how     | SQL 等价          | 说明                    |
| ------- | --------------- | --------------------- |
| "inner" | INNER JOIN      | 只保留两侧都有匹配键的行          |
| "left"  | LEFT JOIN       | 保留左侧所有行，右侧无匹配则 null   |
| "right" | RIGHT JOIN      | 保留右侧所有行，左侧无匹配则 null   |
| "full"  | FULL OUTER JOIN | 保留两侧所有行               |
| "semi"  | LEFT SEMI JOIN  | 保留左侧中键在右侧出现的行（不附加右侧列） |
| "anti"  | LEFT ANTI JOIN  | 保留左侧中键不在右侧出现的行        |
| "cross" | CROSS JOIN      | 笛卡尔积，所有行组合            |

```python
df_orders = pl.DataFrame({"order_id": [1, 2, 3], "user_id": [101, 102, 101], "amount": [50.0, 80.0, 120.0]})
df_users = pl.DataFrame({"user_id": [101, 102, 103], "name": ["Alice", "Bob", "Charlie"]})

# 内连接
df_orders.join(df_users, on="user_id")

# 左连接（不同列名）
df_orders.join(df_users, left_on="user_id", right_on="user_id", how="left")

# Anti join：找出没有匹配用户的订单
df_orders.join(df_users, on="user_id", how="anti")

# 列名冲突处理
df1.join(df2, on="id", suffix="_df2")

```

### join\_asof()

时序对齐连接（AsOf Join）。按最近的键匹配，而非精确匹配，常用于将事件数据与价格/汇率等时序数据对齐。

```python
df.join_asof(other, *, on=None, left_on=None, right_on=None, by=None, by_left=None, by_right=None, strategy="backward", suffix="_right", tolerance=None, coalesce=True)

```

| 参数                   | 类型                | 默认值        | 说明                        |
| -------------------- | ----------------- | ---------- | ------------------------- |
| other                | DataFrame         | 必填         | 右侧 DataFrame（必须已按 on 列排序） |
| on                   | str               | None       | 两侧同名的连接键（通常是时间列）          |
| left\_on / right\_on | str               | None       | 两侧列名不同时使用                 |
| by                   | str / list        | None       | 精确匹配的额外分组键（如股票代码）         |
| strategy             | str               | "backward" | 匹配策略，见下表                  |
| tolerance            | int / float / str | None       | 允许的最大偏差（超过则返回 null）       |

strategy 说明：

| strategy   | 说明                         |
| ---------- | -------------------------- |
| "backward" | 取右侧中小于等于左侧键的最大值（向前看价格，最常用） |
| "forward"  | 取右侧中大于等于左侧键的最小值            |
| "nearest"  | 取右侧中距离最近的键                 |

```python
df_trades = pl.DataFrame({
    "time": [1, 2, 5, 8],
    "trade_id": [1, 2, 3, 4],
}).sort("time")

df_prices = pl.DataFrame({
    "time": [1, 3, 6],
    "price": [100.0, 105.0, 110.0],
}).sort("time")

# 每笔交易匹配最近的（不晚于交易时间的）价格
df_trades.join_asof(df_prices, on="time", strategy="backward")

# 按股票分组的时序对齐
df_trades.join_asof(df_prices, on="time", by="symbol", strategy="backward")

# 设置容忍度：超过 2 秒则不匹配
df_trades.join_asof(df_prices, on="time", strategy="backward", tolerance=2)

```

---

## 数据重塑

### unpivot() / melt()

宽格式转长格式（Wide to Long）。`melt()` 是 `unpivot()` 的别名，行为相同。

```python
df.unpivot(on=None, *, index=None, variable_name="variable", value_name="value")

```

| 参数             | 类型          | 默认值        | 说明                               |
| -------------- | ----------- | ---------- | -------------------------------- |
| on             | list\[str\] | None       | 要转为长格式的列（值列），None 则转除 index 外所有列 |
| index          | list\[str\] | None       | 保持不变的标识列（id 列）                   |
| variable\_name | str         | "variable" | 新列名（存放原列名）                       |
| value\_name    | str         | "value"    | 新列名（存放原值）                        |

```python
df = pl.DataFrame({
    "id": [1, 2],
    "jan": [100, 200],
    "feb": [110, 220],
    "mar": [120, 230],
})

df.unpivot(on=["jan", "feb", "mar"], index="id", variable_name="month", value_name="sales")
# 结果：
# id | month | sales
#  1 | jan   |  100
#  1 | feb   |  110
# ...

```

### pivot()

长格式转宽格式（Long to Wide）。

```python
df.pivot(on, *, index=None, values=None, aggregate_function="first", maintain_order=True, sort_columns=False, separator="_")

```

| 参数                  | 类型         | 默认值     | 说明                                                               |
| ------------------- | ---------- | ------- | ---------------------------------------------------------------- |
| on                  | str / list | 必填      | 值将成为新列名的列                                                        |
| index               | str / list | None    | 行标识列，None 则用其余所有列                                                |
| values              | str / list | None    | 填入值的列，None 则用除 on 和 index 外的列                                    |
| aggregate\_function | str        | "first" | 聚合函数："first" / "last" / "min" / "max" / "sum" / "mean" / "count" |
| maintain\_order     | bool       | True    | 保持行顺序                                                            |
| sort\_columns       | bool       | False   | 是否对新生成的列名排序                                                      |

```python
df_long = pl.DataFrame({
    "id": [1, 1, 2, 2],
    "month": ["jan", "feb", "jan", "feb"],
    "sales": [100, 110, 200, 220],
})

df_long.pivot(on="month", index="id", values="sales")
# 结果：
# id | jan | feb
#  1 | 100 | 110
#  2 | 200 | 220

```

### explode()

将列表列展开为多行。

```python
df.explode(columns, *more_columns)

```

| 参数      | 类型         | 默认值 | 说明      |
| ------- | ---------- | --- | ------- |
| columns | str / list | 必填  | 要展开的列表列 |

```python
df = pl.DataFrame({"id": [1, 2], "tags": [["python", "data"], ["sql"]]})
df.explode("tags")
# 结果：
# id | tags
#  1 | python
#  1 | data
#  2 | sql

```

### unnest()

将结构体列（Struct）拆分为多个普通列。

```python
df.unnest(columns, *more_columns)

```

| 参数      | 类型         | 默认值 | 说明       |
| ------- | ---------- | --- | -------- |
| columns | str / list | 必填  | 要展开的结构体列 |

```python
df = pl.DataFrame({"id": [1, 2], "info": [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}]})
df.unnest("info")
# 结果：id | name | age

```

---

## 合并

### pl.concat()

合并多个 DataFrame 或 Series。

```python
pl.concat(items, *, how="vertical", rechunk=False, parallel=True)

```

| 参数       | 类型                                 | 默认值        | 说明                     |
| -------- | ---------------------------------- | ---------- | ---------------------- |
| items    | list\[DataFrame/Series/LazyFrame\] | 必填         | 要合并的对象列表               |
| how      | str                                | "vertical" | 合并方式，见下表               |
| rechunk  | bool                               | False      | 合并后重整内存块（消耗时间，但后续操作更快） |
| parallel | bool                               | True       | 是否并行读取各分片              |

how 类型说明：

| how                 | 说明                  |
| ------------------- | ------------------- |
| "vertical"          | 纵向拼接（行合并），列必须相同     |
| "vertical\_relaxed" | 纵向拼接，类型不同时自动转换      |
| "diagonal"          | 纵向拼接，列可不同，缺失列填 null |
| "diagonal\_relaxed" | 纵向拼接，列可不同，类型不同自动转换  |
| "horizontal"        | 横向拼接（列合并），行数必须相同    |
| "align"             | 按公共列对齐后合并           |

```python
# 纵向合并（行追加）
df_all = pl.concat([df1, df2, df3])

# 列不完全相同的合并（缺失列补 null）
df_all = pl.concat([df1, df2], how="diagonal")

# 横向合并（列追加）
df_wide = pl.concat([df_a, df_b], how="horizontal")

# LazyFrame 合并
lf_all = pl.concat([lf1, lf2])

```

---

## 字符串操作（str namespace）

通过 `pl.col("col").str.方法()` 调用。

### str.contains()

```python
pl.col("text").str.contains(pattern, *, literal=False)

```

| 参数      | 类型   | 默认值   | 说明                      |
| ------- | ---- | ----- | ----------------------- |
| pattern | str  | 必填    | 匹配模式（正则或字面量）            |
| literal | bool | False | True 则视为字面量，False 则视为正则 |

```python
pl.col("text").str.contains("python")
pl.col("text").str.contains(r"py\w+", literal=False)
pl.col("url").str.contains("https://", literal=True)

pl.col("name").str.starts_with("A")
pl.col("email").str.ends_with(".com")

```

### str.replace() / str.replace\_all()

```python
pl.col("text").str.replace(pattern, value, *, literal=False, n=1)
pl.col("text").str.replace_all(pattern, value, *, literal=False)

```

| 参数      | 类型   | 默认值   | 说明                 |
| ------- | ---- | ----- | ------------------ |
| pattern | str  | 必填    | 匹配模式               |
| value   | str  | 必填    | 替换内容，支持正则捕获组引用 $1  |
| literal | bool | False | 是否字面量匹配            |
| n       | int  | 1     | replace() 中替换的最大次数 |

```python
pl.col("text").str.replace("foo", "bar")
pl.col("text").str.replace_all(r"\s+", " ")   # 合并多个空格
pl.col("text").str.replace_all(r"(\w+)", "[$1]", literal=False)  # 正则替换

```

### str.strip\_chars() 系列

```python
pl.col("text").str.strip_chars()              # 去除两端空白
pl.col("text").str.strip_chars(" \t")         # 去除两端指定字符
pl.col("text").str.lstrip_chars()             # 去除左端空白
pl.col("text").str.rstrip_chars()             # 去除右端空白

```

### str.split()

```python
pl.col("text").str.split(by, *, inclusive=False)

```

| 参数        | 类型   | 默认值   | 说明              |
| --------- | ---- | ----- | --------------- |
| by        | str  | 必填    | 分隔符             |
| inclusive | bool | False | 是否在每个分割结果中保留分隔符 |

```python
pl.col("csv_line").str.split(",")               # 返回 List[String] 列
pl.col("csv_line").str.split(",").list.get(0)  # 取第一个分割结果

```

### str.extract() / str.extract\_groups()

```python
# 提取第一个捕获组
pl.col("text").str.extract(r"(\d+)", group_index=1)

# 提取所有捕获组为结构体
pl.col("text").str.extract_groups(r"(\w+)-(\d+)")
# 返回 Struct{field_0: String, field_1: String}

```

| 参数           | 类型  | 默认值 | 说明                   |
| ------------ | --- | --- | -------------------- |
| pattern      | str | 必填  | 正则表达式（必须含捕获组）        |
| group\_index | int | 1   | extract() 中要提取的捕获组索引 |

### 大小写转换

```python
pl.col("text").str.to_uppercase()
pl.col("text").str.to_lowercase()
pl.col("text").str.to_titlecase()

```

### 长度计算

```python
pl.col("text").str.len_chars()   # 字符数（Unicode 字符）
pl.col("text").str.len_bytes()   # 字节数

```

### 数值转换

```python
pl.col("num_str").str.to_integer(base=10, strict=True)   # 字符串转整数
pl.col("num_str").str.to_decimal(inference_length=100)    # 字符串转 Decimal

```

### str.strptime() — 字符串转日期时间

```python
pl.col("date_str").str.strptime(dtype, format=None, *, strict=True, exact=True, cache=True, use_earliest=None, ambiguous="raise")

```

| 参数            | 类型                              | 默认值     | 说明                                               |
| ------------- | ------------------------------- | ------- | ------------------------------------------------ |
| dtype         | pl.Date / pl.Datetime / pl.Time | 必填      | 目标日期时间类型                                         |
| format        | str                             | None    | 格式字符串（如 "%Y-%m-%d"），None 则自动推断                   |
| strict        | bool                            | True    | 解析失败时报错（False 则返回 null）                          |
| exact         | bool                            | True    | 是否要求整串匹配（False 则允许前缀匹配）                          |
| cache         | bool                            | True    | 是否缓存解析结果（重复值多时加速）                                |
| use\_earliest | bool                            | None    | 夏令时模糊时段，是否取最早的时间                                 |
| ambiguous     | str                             | "raise" | 夏令时模糊处理："raise" / "earliest" / "latest" / "null" |

```python
pl.col("date_str").str.strptime(pl.Date, "%Y-%m-%d")
pl.col("dt_str").str.strptime(pl.Datetime, "%Y-%m-%d %H:%M:%S")
pl.col("dt_str").str.strptime(pl.Datetime("us", "Asia/Shanghai"), "%Y-%m-%d %H:%M:%S")
pl.col("date_str").str.strptime(pl.Date, strict=False)  # 解析失败返回 null

```

---

## 日期时间操作（dt namespace）

通过 `pl.col("col").dt.方法/属性` 调用。

### 时间属性

```python
pl.col("date").dt.year()
pl.col("date").dt.month()
pl.col("date").dt.day()
pl.col("date").dt.hour()
pl.col("date").dt.minute()
pl.col("date").dt.second()
pl.col("date").dt.millisecond()
pl.col("date").dt.microsecond()
pl.col("date").dt.nanosecond()
pl.col("date").dt.weekday()      # 星期几（1=周一 ... 7=周日）
pl.col("date").dt.week()         # 一年中的第几周（ISO 周）
pl.col("date").dt.day_of_year()  # 一年中的第几天
pl.col("date").dt.quarter()      # 季度（1~4）
pl.col("date").dt.ordinal_day()  # 儒略历天数

```

### dt.strftime()

```python
pl.col("date").dt.strftime(format)

```

| 参数     | 类型  | 默认值 | 说明                         |
| ------ | --- | --- | -------------------------- |
| format | str | 必填  | 格式字符串，与 Python strftime 相同 |

```python
pl.col("datetime").dt.strftime("%Y-%m-%d")
pl.col("datetime").dt.strftime("%Y/%m/%d %H:%M:%S")
pl.col("datetime").dt.strftime("%Y年%m月%d日")

```

### dt.truncate()

截断到指定时间粒度（如将时间截断到小时）。

```python
pl.col("datetime").dt.truncate(every, offset="0ns")

```

| 参数     | 类型              | 默认值   | 说明                         |
| ------ | --------------- | ----- | -------------------------- |
| every  | str / timedelta | 必填    | 截断粒度，如 "1h" / "1d" / "15m" |
| offset | str / timedelta | "0ns" | 偏移量                        |

```python
pl.col("datetime").dt.truncate("1h")    # 截断到小时
pl.col("datetime").dt.truncate("1d")    # 截断到天
pl.col("datetime").dt.truncate("15m")   # 截断到 15 分钟

```

### dt.offset\_by()

日期偏移运算。

```python
pl.col("date").dt.offset_by(by)

```

| 参数 | 类型  | 默认值 | 说明                                          |
| -- | --- | --- | ------------------------------------------- |
| by | str | 必填  | 偏移量，正数向后，负数向前，如 "1d" / "-7d" / "1mo" / "1y" |

```python
pl.col("date").dt.offset_by("1d")      # 加 1 天
pl.col("date").dt.offset_by("-7d")     # 减 7 天
pl.col("date").dt.offset_by("1mo")     # 加 1 个月（自动处理月末）
pl.col("date").dt.offset_by("1y")      # 加 1 年

```

### Duration 方法

当列为 Duration 类型时使用。

```python
pl.col("duration").dt.total_seconds()
pl.col("duration").dt.total_milliseconds()
pl.col("duration").dt.total_microseconds()
pl.col("duration").dt.total_nanoseconds()
pl.col("duration").dt.total_minutes()
pl.col("duration").dt.total_hours()
pl.col("duration").dt.total_days()

# Duration 通过两个日期相减得到
df.with_columns((pl.col("end_date") - pl.col("start_date")).alias("duration"))

```

### pl.date\_range() / pl.datetime\_range()

生成日期/时间序列。

```python
pl.date_range(start, end, interval="1d", *, eager=False, name=None)
pl.datetime_range(start, end, interval="1h", *, time_unit="us", time_zone=None, eager=False, name=None)

```

| 参数         | 类型                           | 默认值   | 说明                             |
| ---------- | ---------------------------- | ----- | ------------------------------ |
| start      | date / datetime / str / Expr | 必填    | 起始时间                           |
| end        | date / datetime / str / Expr | 必填    | 结束时间（包含）                       |
| interval   | str / timedelta              | "1d"  | 间隔                             |
| eager      | bool                         | False | True 则立即返回 Series，False 则返回表达式 |
| time\_unit | str                          | "us"  | 时间精度："ns" / "us" / "ms"        |
| time\_zone | str                          | None  | 时区，如 "Asia/Shanghai"           |

```python
import datetime

# 生成日期序列
dates = pl.date_range(
    datetime.date(2024, 1, 1),
    datetime.date(2024, 12, 31),
    interval="1mo",
    eager=True,
)

# 在表达式中使用（用于 with_columns）
df.with_columns(
    pl.datetime_range(pl.col("start"), pl.col("end"), "1d").alias("dates")
)

```

### 时区处理

```python
# 为无时区的 Datetime 设置时区（声明，不转换）
pl.col("dt").dt.replace_time_zone("Asia/Shanghai")

# 转换时区（转换，保持绝对时间不变）
pl.col("dt").dt.convert_time_zone("America/New_York")

```

---

## 惰性执行（LazyFrame）

LazyFrame 是 Polars 高性能的核心。所有操作构建执行计划，`collect()` 时触发优化并执行。

### 创建 LazyFrame

```python
# 从 DataFrame 转换
lf = df.lazy()

# 直接从文件扫描（最佳实践）
lf = pl.scan_csv("data.csv")
lf = pl.scan_parquet("data.parquet")
lf = pl.scan_ndjson("data.ndjson")

```

### lf.collect()

触发执行，返回 DataFrame。

```python
lf.collect(*, streaming=False, background=False, comm_subplan_elim=True, projection_pushdown=True, predicate_pushdown=True, cluster_with_columns=True, no_optimization=False, slice_pushdown=True, new_streaming=False)

```

| 参数                   | 类型   | 默认值   | 说明                     |
| -------------------- | ---- | ----- | ---------------------- |
| streaming            | bool | False | 启用流式处理，减少内存峰值（部分操作不支持） |
| no\_optimization     | bool | False | 禁用所有优化（调试用）            |
| projection\_pushdown | bool | True  | 是否启用投影下推               |
| predicate\_pushdown  | bool | True  | 是否启用谓词下推               |
| comm\_subplan\_elim  | bool | True  | 公共子计划消除                |

```python
result = lf.collect()
result = lf.collect(streaming=True)  # 流式执行，适合大数据集

```

### 查看执行计划

```python
# 文本形式的逻辑计划
print(lf.explain())

# 文本形式的优化后计划
print(lf.explain(optimized=True))

# 可视化执行计划（需要安装 graphviz）
lf.show_graph()
lf.show_graph(optimized=True)

# 收集执行统计信息
result, stats = lf.profile()

```

### 谓词下推（Predicate Pushdown）

Polars 自动将 `filter()` 条件尽量靠近数据源执行，减少读取的数据量。

```python
# Polars 会将 filter 推到 scan_csv 层面，只读取满足条件的行
result = (
    pl.scan_csv("large_data.csv")
    .filter(pl.col("year") == 2024)        # 谓词下推：读 CSV 时就过滤
    .filter(pl.col("amount") > 1000)       # 多个 filter 都会被下推
    .select(["id", "date", "amount"])
    .collect()
)

```

### 投影下推（Projection Pushdown）

Polars 自动只读取 `select()` 中实际用到的列，对 Parquet 等列式格式效果显著。

```python
# 只读取 id 和 amount 两列
result = (
    pl.scan_parquet("large_data.parquet")
    .select(["id", "amount"])              # 投影下推：只读这两列
    .filter(pl.col("amount") > 1000)
    .collect()
)

```

### 何时用 LazyFrame，何时用 Eager

| 场景             | 推荐模式                           |
| -------------- | ------------------------------ |
| 数据文件较大（>500MB） | Lazy + scan\_\*                |
| 多步操作链          | Lazy，collect() 一次触发            |
| 交互式探索少量数据      | Eager                          |
| 需要中间结果进行条件分支   | 在关键点 collect()，再 lazy()        |
| 内存受限的大数据集      | Lazy + collect(streaming=True) |

### lf.sink\_parquet() / lf.sink\_csv()

流式写出，不将结果加载到内存。

```python
lf.sink_parquet(path, *, compression="zstd", compression_level=None, statistics=False, row_group_size=None, data_page_size=None, maintain_order=True)

```

| 参数              | 类型         | 默认值    | 说明      |
| --------------- | ---------- | ------ | ------- |
| path            | str / Path | 必填     | 输出路径    |
| compression     | str        | "zstd" | 压缩算法    |
| maintain\_order | bool       | True   | 是否保持行顺序 |

```python
# 大数据集流式写出，不占用内存
(
    pl.scan_csv("large_input.csv")
    .filter(pl.col("status") == "active")
    .with_columns(pl.col("amount") * 1.1)
    .sink_parquet("output.parquet")
)

(
    pl.scan_parquet("large.parquet")
    .select(["id", "name", "value"])
    .sink_csv("output.csv")
)

```

---

## 流式处理（Streaming）

### collect(streaming=True)

```python
result = lf.collect(streaming=True)

```

流式处理原理：Polars 将数据分批处理，每批处理完后释放内存，峰值内存大幅降低。适合数据集大小超出可用内存的场景。

### 适用场景

- 数据集大于可用内存的 50% 时考虑使用
- ETL 管道：扫描大文件，过滤，写出
- 配合 `sink_parquet()` / `sink_csv()` 实现全程流式处理

```python
# 全程流式，内存占用极低
(
    pl.scan_csv("100gb_data.csv")
    .filter(pl.col("date") >= "2024-01-01")
    .group_by("category")
    .agg(pl.col("value").sum())
    .sink_parquet("result.parquet")
)

```

### 流式处理的限制

部分操作目前不支持流式处理，会自动回退到非流式模式：

- `sort()`（全局排序需要全量数据）
- `join()` 中的部分类型
- `pivot()`
- 某些窗口函数

Polars 会自动降级，不会报错，但内存使用会升高。

---

## 窗口函数

### over() — 窗口聚合

Polars 的窗口函数语法，等同于 SQL 的 `OVER (PARTITION BY ...)`。与 group\_by 不同，over() 不减少行数，每行保留，但值替换为窗口聚合结果。

```python
pl.col("expr").over(partition_by, *, order_by=None, mapping_strategy="group_to_rows")

```

| 参数                | 类型         | 默认值               | 说明                              |
| ----------------- | ---------- | ----------------- | ------------------------------- |
| partition\_by     | str / list | 必填                | 分区键，相当于 PARTITION BY            |
| order\_by         | str / Expr | None              | 窗口内排序键（用于 rank/cumsum 等需要顺序的聚合） |
| mapping\_strategy | str        | "group\_to\_rows" | 结果映射策略，一般不需要修改                  |

```python
# 每行添加所在分组的聚合值
df.with_columns(
    pl.col("score").mean().over("department").alias("dept_avg_score"),
    pl.col("score").max().over("department").alias("dept_max_score"),
    pl.col("score").count().over("department").alias("dept_count"),
)

# 组内排名
df.with_columns(
    pl.col("score").rank(descending=True).over("department").alias("dept_rank")
)

# 组内累积和（需要 order_by）
df.with_columns(
    pl.col("sales").cum_sum().over("store", order_by="date").alias("cumulative_sales")
)

# 组内 lag/lead（需要 order_by）
df.with_columns(
    pl.col("price").shift(1).over("symbol", order_by="date").alias("prev_price")
)

```

### rolling() 表达式

在时间窗口内进行滚动计算。

```python
pl.col("value").rolling_mean(window_size, *, weights=None, min_periods=None, center=False)
pl.col("value").rolling_sum(window_size, *, weights=None, min_periods=None, center=False)
pl.col("value").rolling_std(window_size, *, weights=None, min_periods=None, center=False, ddof=1)
pl.col("value").rolling_min(window_size, *, min_periods=None, center=False)
pl.col("value").rolling_max(window_size, *, min_periods=None, center=False)

```

| 参数           | 类型            | 默认值   | 说明                          |
| ------------ | ------------- | ----- | --------------------------- |
| window\_size | int / str     | 必填    | 窗口大小（行数或时间字符串）              |
| weights      | list\[float\] | None  | 窗口权重                        |
| min\_periods | int           | None  | 窗口内最少有效值数，默认等于 window\_size |
| center       | bool          | False | 是否居中窗口（而非右对齐）               |

```python
df.with_columns(
    pl.col("price").rolling_mean(7).alias("ma7"),            # 7 日均线
    pl.col("price").rolling_mean(30, min_periods=1).alias("ma30"),  # 允许不足 30 天
    pl.col("price").rolling_std(20).alias("volatility"),
)

```

---

## 性能特性

### 自动多线程并行

Polars 使用 Rust 的 Rayon 线程池，自动并行化以下操作：

- 多个无依赖的 `with_columns()` 表达式并行执行
- group\_by / join 使用多线程
- 文件读取并行化
- LazyFrame 查询计划中并行节点同步执行

无需任何配置，自动利用所有 CPU 核心。

### Arrow 列式存储优势

- 同列数据内存连续，SIMD 向量化指令生效，聚合操作极快
- 列式读取 Parquet 时可跳过不需要的列（零 IO）
- 与 DuckDB、PyArrow 等 Arrow 生态库零拷贝互操作

### 与 Pandas 性能对比

| 操作                   | Polars 优势说明           |
| -------------------- | --------------------- |
| groupby + agg        | 多线程哈希聚合，通常快 5\~15 倍   |
| filter               | 谓词下推 + SIMD，快 3\~10 倍 |
| join                 | 并行哈希 join，快 3\~10 倍   |
| read\_csv            | 并行解析，快 5\~20 倍        |
| sort                 | 并行排序，快 3\~8 倍         |
| apply（map\_elements） | 接近 pandas apply，优势消失  |

### 内存使用对比

- Polars 使用 Arrow 格式，整数列不需要为 null 转为 float64
- 字符串使用 LargeUtf8 编码，避免对象数组的开销
- 通常比 pandas 少用 30%\~60% 内存
- LazyFrame + streaming 可处理超出 RAM 的数据集

### 用 lf.explain() 分析查询计划

```python
lf = (
    pl.scan_parquet("large.parquet")
    .filter(pl.col("year") == 2024)
    .group_by("category")
    .agg(pl.col("value").sum())
)

# 查看未优化计划
print(lf.explain(optimized=False))

# 查看优化后计划（确认谓词下推是否生效）
print(lf.explain(optimized=True))

# 查看执行耗时分布（用于性能调优）
result, profile = lf.profile()
print(profile)

```

---

## 常用配合库

### NumPy

```python
import numpy as np

# numpy -> polars Series
arr = np.array([1.0, 2.0, 3.0])
s = pl.Series("data", arr)

# numpy -> polars DataFrame
arr2d = np.random.rand(100, 3)
df = pl.from_numpy(arr2d, schema=["a", "b", "c"])

# polars Series -> numpy
arr = df["score"].to_numpy()                    # 无 null 时效率最高
arr = df["score"].to_numpy(allow_copy=True)     # 有 null 时需要 allow_copy

# polars DataFrame -> numpy
arr2d = df.to_numpy()

```

### Pandas

```python
import pandas as pd

# pandas -> polars
df_pl = pl.from_pandas(pdf)
df_pl = pl.from_pandas(pdf, nan_to_null=True, include_index=True)

# polars -> pandas
pdf = df_pl.to_pandas()
pdf = df_pl.to_pandas(use_pyarrow_extension_array=True)  # 保留 null 语义

```

### PyArrow

```python
import pyarrow as pa

# polars -> arrow
arrow_table = df.to_arrow()

# arrow -> polars
df = pl.from_arrow(arrow_table)

# 零拷贝：polars 和 arrow 共享内存（不需要拷贝数据）

```

### Matplotlib / Seaborn 绘图

Polars 无原生绘图支持，通过转换为 pandas 使用：

```python
import matplotlib.pyplot as plt
import seaborn as sns

# 转换后绘图
pdf = df.to_pandas()
pdf.plot(x="date", y="value")
sns.histplot(pdf["score"])
plt.show()

# 或直接取 numpy 数组
plt.scatter(df["x"].to_numpy(), df["y"].to_numpy())

```

### DuckDB — SQL on Polars

DuckDB 可以直接查询 Polars DataFrame（Arrow 协议，无拷贝）：

```python
import duckdb

df = pl.DataFrame({"id": [1, 2, 3], "value": [10, 20, 30]})

# 直接用 SQL 查询 Polars DataFrame
result = duckdb.sql("SELECT id, value * 2 as doubled FROM df").pl()

# 复杂 SQL
conn = duckdb.connect()
result = conn.execute("""
    SELECT category, SUM(amount) as total
    FROM df
    GROUP BY category
    ORDER BY total DESC
""").pl()  # .pl() 返回 Polars DataFrame

```

### Connectorx — 高性能数据库读取

```python
import connectorx as cx

# 高性能从数据库读取，直接返回 Polars DataFrame（Arrow 协议）
df = cx.read_sql(
    "postgresql://user:pass@host:5432/db",
    "SELECT * FROM orders WHERE status = 'active'",
    return_type="polars",
)

```

---

## 最佳实践

### 优先使用 LazyFrame + collect() 模式

```python
# 不推荐：每步操作立即执行，无法优化
df1 = df.filter(pl.col("year") == 2024)
df2 = df1.select(["id", "value"])
df3 = df2.group_by("id").agg(pl.col("value").sum())

# 推荐：构建完整计划后一次执行，Polars 自动优化
result = (
    df.lazy()
    .filter(pl.col("year") == 2024)
    .select(["id", "value"])
    .group_by("id")
    .agg(pl.col("value").sum())
    .collect()
)

```

### 用 select + with\_columns 而不是逐列赋值

```python
# 不推荐（pandas 风格，Polars 不支持就地赋值）
# df["new_col"] = df["price"] * 1.1  # Polars 不支持此语法

# 推荐：with_columns 批量添加（多列并行执行）
df = df.with_columns(
    (pl.col("price") * 1.1).alias("price_tax"),
    pl.col("name").str.to_uppercase().alias("name_upper"),
    (pl.col("score") - pl.col("score").mean()).alias("score_norm"),
)

```

### 链式表达式风格（Method Chaining）

```python
result = (
    pl.scan_csv("data.csv")
    .filter(pl.col("date") >= "2024-01-01")
    .filter(pl.col("amount") > 0)
    .with_columns(
        (pl.col("amount") * pl.col("quantity")).alias("revenue"),
        pl.col("date").str.strptime(pl.Date, "%Y-%m-%d"),
    )
    .group_by(["category", pl.col("date").dt.month().alias("month")])
    .agg(
        pl.col("revenue").sum().alias("total_revenue"),
        pl.col("amount").mean().alias("avg_amount"),
    )
    .sort("total_revenue", descending=True)
    .collect()
)

```

### 用 when/then/otherwise 替代 map\_elements

```python
# 不推荐：map_elements 走 Python 循环，极慢
df.with_columns(
    pl.col("score").map_elements(lambda x: "A" if x >= 90 else "B").alias("grade")
)

# 推荐：when/then/otherwise 在 Rust 层执行，极快
df.with_columns(
    pl.when(pl.col("score") >= 90).then(pl.lit("A"))
    .when(pl.col("score") >= 80).then(pl.lit("B"))
    .otherwise(pl.lit("C"))
    .alias("grade")
)

```

### 避免 map\_elements（相当于 pandas apply）

```python
# 仅在没有原生表达式替代时才使用 map_elements
# 尽量找到对应的原生表达式

# 字符串处理：用 str namespace
pl.col("text").map_elements(lambda x: x.upper())  # 不推荐
pl.col("text").str.to_uppercase()                  # 推荐

# 数学计算：用算术表达式
pl.col("x").map_elements(lambda x: x ** 2)  # 不推荐
pl.col("x") ** 2                             # 推荐

# 条件逻辑：用 when/then/otherwise
# （见上例）

```

### 尽量在 LazyFrame 阶段完成 filter

```python
# 推荐：filter 在 collect 之前，利用谓词下推
result = (
    pl.scan_parquet("huge_file.parquet")
    .filter(pl.col("status") == "active")   # 读取时就过滤
    .collect()
)

# 不推荐：collect 后再 filter，已经将全量数据加载到内存
result = pl.read_parquet("huge_file.parquet").filter(pl.col("status") == "active")

```

### 读大文件用 scan\_\* 而不是 read\_\*

```python
# 不推荐：一次性加载全部数据到内存
df = pl.read_csv("100gb.csv")

# 推荐：惰性扫描，配合过滤和投影只读需要的数据
result = (
    pl.scan_csv("100gb.csv")
    .filter(pl.col("region") == "China")
    .select(["id", "name", "value"])
    .collect()
)

```

---

## 应用场景

### 大型 CSV 数据分析流水线

```python
result = (
    pl.scan_csv(
        "sales_data.csv",
        try_parse_dates=True,
        null_values=["", "N/A"],
        schema_overrides={"amount": pl.Float64},
    )
    .filter(
        (pl.col("date") >= datetime.date(2024, 1, 1)) &
        (pl.col("status") == "completed")
    )
    .with_columns(
        pl.col("amount").fill_null(0).alias("amount"),
        pl.col("date").dt.month().alias("month"),
        pl.col("date").dt.year().alias("year"),
        (pl.col("amount") * pl.col("quantity")).alias("revenue"),
    )
    .group_by(["year", "month", "category"])
    .agg(
        pl.col("revenue").sum().alias("total_revenue"),
        pl.col("amount").mean().alias("avg_amount"),
        pl.col("id").count().alias("order_count"),
    )
    .sort(["year", "month", "total_revenue"], descending=[False, False, True])
    .collect()
)

```

### 时序数据处理

```python
df = pl.read_csv("sensor_data.csv", try_parse_dates=True)

result = (
    df
    .sort("timestamp")
    .group_by_dynamic("timestamp", every="1h", group_by="sensor_id")
    .agg(
        pl.col("temperature").mean().alias("avg_temp"),
        pl.col("temperature").max().alias("max_temp"),
        pl.col("temperature").min().alias("min_temp"),
        pl.col("temperature").std().alias("std_temp"),
        pl.col("value").count().alias("reading_count"),
    )
    .with_columns(
        pl.col("avg_temp").rolling_mean(24).over("sensor_id").alias("ma_24h")
    )
)

```

### ETL 数据转换

```python
result = (
    pl.scan_parquet("raw_orders.parquet")
    .with_columns(
        # 清洗空值
        pl.col("amount").fill_null(0),
        pl.col("customer_name").fill_null("Unknown"),
        # 类型转换
        pl.col("order_date").str.strptime(pl.Date, "%Y-%m-%d"),
        # 派生列
        (pl.col("amount") * pl.col("tax_rate")).alias("tax_amount"),
        # 分类
        pl.when(pl.col("amount") > 10000).then(pl.lit("large"))
        .when(pl.col("amount") > 1000).then(pl.lit("medium"))
        .otherwise(pl.lit("small"))
        .alias("order_size"),
    )
    .filter(pl.col("status").is_in(["active", "completed"]))
    .rename({"customer_name": "name", "order_date": "date"})
    .collect()
)

result.write_parquet("clean_orders.parquet", compression="zstd", statistics=True)

```

### 替换 Pandas 的迁移模式

| 操作     | Pandas 写法                               | Polars 写法                                                         |
| ------ | --------------------------------------- | ----------------------------------------------------------------- |
| 读取 CSV | pd.read\_csv("f.csv")                   | pl.read\_csv("f.csv")                                             |
| 过滤行    | df\[df\["age"\] > 25\]                  | df.filter(pl.col("age") > 25)                                     |
| 选择列    | df"name", "age"                         | df.select(\["name", "age"\])                                      |
| 添加列    | df\["new"\] = df\["a"\] + 1             | df.with\_columns((pl.col("a") + 1).alias("new"))                  |
| 分组聚合   | df.groupby("cat")\["val"\].sum()        | df.group\_by("cat").agg(pl.col("val").sum())                      |
| 排序     | df.sort\_values("col", ascending=False) | df.sort("col", descending=True)                                   |
| 去重     | df.drop\_duplicates(subset=\["id"\])    | df.unique(subset=\["id"\])                                        |
| 宽转长    | df.melt(id\_vars=\["id"\])              | df.unpivot(index=\["id"\])                                        |
| 条件赋值   | df\["col"\] = np.where(cond, a, b)      | df.with\_columns(pl.when(cond).then(a).otherwise(b).alias("col")) |
| apply  | df\["col"\].apply(func)                 | df\["col"\].map\_elements(func)（尽量避免）                             |
| fillna | df\["col"\].fillna(0)                   | df.with\_columns(pl.col("col").fill\_null(0))                     |
| merge  | pd.merge(df1, df2, on="id")             | df1.join(df2, on="id")                                            |
| concat | pd.concat(\[df1, df2\])                 | pl.concat(\[df1, df2\])                                           |
| 字符串操作  | df\["col"\].str.upper()                 | pl.col("col").str.to\_uppercase()                                 |
| 日期提取   | df\["date"\].dt.year                    | pl.col("date").dt.year()                                          |

---

## 常见陷阱与注意事项

### Polars 没有 index

Polars DataFrame 没有行索引（不像 pandas 有 Index 对象）。无法通过 `df.loc[...]` 或 `df.iloc[...]` 访问。用 `filter()` 过滤行，用 `slice()` 按位置切片。

```python
# pandas 风格（在 Polars 中无效）
# df.loc[df["name"] == "Alice"]   # 错误

# Polars 正确写法
df.filter(pl.col("name") == "Alice")
df.row(0, named=True)   # 按位置取单行
df.slice(0, 10)         # 按位置切片

```

### 列名重复会报错

Polars 不允许列名重复。join 时如果两侧有同名列（非连接键），会自动加 suffix，但如果 with\_columns 操作产生同名列则会覆盖（不报错）。

```python
# join 时处理重名列
df1.join(df2, on="id", suffix="_right")

# 主动重命名避免冲突
df2_renamed = df2.rename({"value": "value_right"})

```

### is\_in() vs isin()

Polars 使用 `is_in()`，没有 pandas 的 `isin()`。

```python
pl.col("city").is_in(["Beijing", "Shanghai"])   # Polars
# df["city"].isin(["Beijing", "Shanghai"])       # Pandas（Polars 中不存在）

```

### group\_by 结果顺序不保证

默认情况下 `group_by()` 的结果顺序是不确定的（利用并行哈希表，顺序取决于执行）。如需稳定顺序：

```python
# 方法 1：maintain_order=True（稍慢）
df.group_by("category", maintain_order=True).agg(pl.col("value").sum())

# 方法 2：聚合后 sort（推荐，明确意图）
df.group_by("category").agg(pl.col("value").sum()).sort("category")

```

### map\_elements 比原生表达式慢很多

```python
# map_elements 调用 Python 解释器处理每个值，破坏 Polars 的 Rust 并行优化
# 性能通常比原生表达式慢 10~100 倍

# 碰到需要 map_elements 的场景，先思考是否有原生替代：
# - 字符串操作  -> str namespace
# - 数学运算    -> 算术表达式
# - 条件分支    -> when/then/otherwise
# - 日期操作    -> dt namespace

# 实在没有原生替代时，才使用 map_elements，并指定 return_dtype 提升性能
pl.col("text").map_elements(custom_func, return_dtype=pl.String)

```

### 时区处理：replace\_time\_zone vs convert\_time\_zone

```python
# replace_time_zone：声明时区（不改变时间值，仅附加时区信息）
# 用于：从无时区 Datetime 变为有时区 Datetime
pl.col("dt").dt.replace_time_zone("Asia/Shanghai")

# convert_time_zone：转换时区（改变时间值，保持绝对时间不变）
# 用于：在两个时区之间转换
pl.col("dt").dt.convert_time_zone("America/New_York")

# 典型流程：读入 naive datetime -> 声明时区 -> 转换时区
pl.col("dt").dt.replace_time_zone("UTC").dt.convert_time_zone("Asia/Shanghai")

```

### null vs NaN 的严格区分

Polars 严格区分 null（缺失值）和 NaN（浮点非数值），pandas 通常混用两者。

```python
# null：任何类型都可以有，表示"缺失数据"
pl.col("value").is_null()
pl.col("value").fill_null(0)

# NaN：只有浮点列有，表示"不是数字"（如 0/0 的结果）
pl.col("value").is_nan()
pl.col("value").fill_nan(0.0)
pl.col("value").fill_nan(None)  # NaN -> null

# 从 pandas/numpy 导入时，NaN 默认转为 null（nan_to_null=True）
df = pl.from_pandas(pdf, nan_to_null=True)

# describe() 对 null 和 NaN 的统计行为不同：
# - null 不参与 mean/sum 等聚合
# - NaN 会使 mean/sum 结果为 NaN（需要先 fill_nan）

```

### join 时列名冲突处理

```python
# join 时，右侧与左侧同名但不是连接键的列，会自动加 suffix
df1 = pl.DataFrame({"id": [1, 2], "value": [10, 20], "name": ["a", "b"]})
df2 = pl.DataFrame({"id": [1, 2], "value": [100, 200], "info": ["x", "y"]})

result = df1.join(df2, on="id")
# 结果列：id, value, name, value_right, info
# value_right 是 df2 的 value 列

# 自定义 suffix
result = df1.join(df2, on="id", suffix="_df2")
# 结果列：id, value, name, value_df2, info

```

### 其他常见注意事项

```python
# 1. Polars 列名区分大小写
df.select("Name")  # 与 "name" 是不同的列

# 2. 链式操作不修改原 DataFrame（不可变），必须赋值
df.sort("score")            # 不修改 df
df = df.sort("score")       # 正确

# 3. 多个 filter 等价于 AND，不是 OR
df.filter(pl.col("a") > 1, pl.col("b") > 2)  # a > 1 AND b > 2

# 4. 惰性模式下的 collect 是唯一触发点，不要在循环中 collect
for item in items:
    lf = lf.filter(...)    # 只构建计划，不执行
result = lf.collect()       # 最后一次性执行

# 5. LazyFrame 不支持 len() / shape，需要 collect 后获取
lf.collect().shape
# 或用 lf.select(pl.len()).collect().item() 获取行数（不加载全部数据）

```

---

## 参见

[Pandas完全指南](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/)  
[SQLAlchemy完全指南](https://blog.vercanti.com/sqlalchemy-wan-quan-zhi-nan/)  
[FastAPI完全指南](https://blog.vercanti.com/fastapi-wan-quan-zhi-nan/)