> ## 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.

# Pandas 完全指南
- URL: https://blog.vercanti.com/pandas-wan-quan-zhi-nan/
- Published: 2026-08-28T14:34:42.000Z
- Updated: 2026-08-28T14:57:06.000Z
- Description: 最后更新：2026-03-27 相关文档：SQLAlchemy完全指南(/sqlalchemy-wan-quan-zhi-nan/) MySQL基础完全指南(/mysql-ji-chu-wan-quan-zhi-nan/) 1. Pandas完全指南 · 安装(/pandas-wan-quan-zhi-nan/#%E5%AE%89%E8%A3%85) 2. Pandas完全指南 · 核心数据结构(/pandas-wan-quan-zhi-nan/#%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84
- Author: yellowdog
- Tags: Python, 框架与库

最后更新：2026-03-27

> 官方文档：<https://pandas.pydata.org/docs/>  
> 适用版本：pandas 2.x（2026-05-08 核实）

相关文档：[SQLAlchemy完全指南](https://blog.vercanti.com/sqlalchemy-wan-quan-zhi-nan/) [MySQL基础完全指南](https://blog.vercanti.com/mysql-ji-chu-wan-quan-zhi-nan/)

---

## 目录

1. [Pandas完全指南 · 安装](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E5%AE%89%E8%A3%85)
2. [Pandas完全指南 · 核心数据结构](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)
3. [Pandas完全指南 · 数据读写](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%86%99)
4. [Pandas完全指南 · 数据查看](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E6%9F%A5%E7%9C%8B)
5. [Pandas完全指南 · 数据选取](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E9%80%89%E5%8F%96)
6. [Pandas完全指南 · 数据清洗](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97)
7. [Pandas完全指南 · 数据变换](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2)
8. [Pandas完全指南 · 字符串操作](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%93%8D%E4%BD%9C)
9. [Pandas完全指南 · 日期时间操作](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4%E6%93%8D%E4%BD%9C)
10. [Pandas完全指南 · 分组聚合](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E5%88%86%E7%BB%84%E8%81%9A%E5%90%88)
11. [Pandas完全指南 · 合并与连接](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E5%90%88%E5%B9%B6%E4%B8%8E%E8%BF%9E%E6%8E%A5)
12. [Pandas完全指南 · 透视与重塑](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E9%80%8F%E8%A7%86%E4%B8%8E%E9%87%8D%E5%A1%91)
13. [Pandas完全指南 · 排序](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%8E%92%E5%BA%8F)
14. [Pandas完全指南 · 窗口函数](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E7%AA%97%E5%8F%A3%E5%87%BD%E6%95%B0)
15. [Pandas完全指南 · 性能优化](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96)
16. [Pandas完全指南 · 常用配合库](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E5%B8%B8%E7%94%A8%E9%85%8D%E5%90%88%E5%BA%93)
17. [Pandas完全指南 · 最佳实践](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5)
18. [Pandas完全指南 · 应用场景](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF)
19. [Pandas完全指南 · 常见陷阱与注意事项](https://blog.vercanti.com/pandas-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 pandas

# 常用可选依赖
pip install openpyxl        # Excel 读写（.xlsx）
pip install xlrd            # Excel 读取旧格式（.xls）
pip install pyarrow         # Parquet 读写 / Arrow backend（pandas 2.0+）
pip install fastparquet     # Parquet 读写（另一实现）
pip install sqlalchemy      # read_sql / to_sql
pip install lxml            # read_html / XML 解析
pip install beautifulsoup4  # read_html HTML 解析备选
pip install tables          # HDF5 读写（PyTables）
pip install fsspec          # 远程文件系统（S3/GCS）

# 一次性安装所有常用依赖
pip install "pandas[all]"

```

版本确认：

```python
import pandas as pd
print(pd.__version__)   # 例：2.2.0

```

---

## 核心数据结构

### Series

Series 是带标签的一维数组，可存储任意类型数据。

#### 创建 Series

```python
import pandas as pd
import numpy as np

# 从列表创建
s = pd.Series([10, 20, 30, 40])

# 从字典创建（键成为索引）
s = pd.Series({"a": 1, "b": 2, "c": 3})

# 指定索引
s = pd.Series([10, 20, 30], index=["x", "y", "z"], name="score")

# 从标量广播
s = pd.Series(5, index=range(5))

# 从 NumPy 数组
s = pd.Series(np.arange(5), dtype="float32")

```

#### 常用属性

| 属性                        | 类型                       | 说明                 |
| ------------------------- | ------------------------ | ------------------ |
| index                     | Index                    | 索引对象               |
| values                    | ndarray 或 ExtensionArray | 底层数据数组             |
| dtype                     | dtype                    | 元素数据类型             |
| shape                     | tuple                    | 形状，如 (5,)          |
| name                      | hashable                 | Series 名称，合并时会成为列名 |
| size                      | int                      | 元素总数               |
| nbytes                    | int                      | 占用内存字节数            |
| is\_monotonic\_increasing | bool                     | 是否单调递增             |
| is\_unique                | bool                     | 是否所有值唯一            |

```python
s = pd.Series([10, 20, 30], index=["a", "b", "c"], name="score")
print(s.index)    # Index(['a', 'b', 'c'], dtype='object')
print(s.values)   # [10 20 30]
print(s.dtype)    # int64
print(s.shape)    # (3,)
print(s.name)     # score

```

---

### DataFrame

DataFrame 是带标签的二维表格结构，每列可以是不同类型。

#### 创建 DataFrame

```python
# 从字典（最常用）
df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie"],
    "age": [25, 30, 35],
    "score": [88.5, 92.0, 78.3],
})

# 从列表套字典
df = pd.DataFrame([
    {"name": "Alice", "age": 25},
    {"name": "Bob",   "age": 30},
])

# 从二维列表（需指定列名）
df = pd.DataFrame(
    [[1, 2, 3], [4, 5, 6]],
    columns=["a", "b", "c"],
    index=["row1", "row2"],
)

# 从 NumPy ndarray
arr = np.random.randn(4, 3)
df = pd.DataFrame(arr, columns=["x", "y", "z"])

# 从 records（列表套元组）
df = pd.DataFrame.from_records(
    [(1, "Alice"), (2, "Bob")],
    columns=["id", "name"],
)

# 从 CSV 文件（见数据读写章节）
df = pd.read_csv("data.csv")

```

#### 常用属性

| 属性/方法           | 说明                                          |
| --------------- | ------------------------------------------- |
| columns         | 列标签 Index 对象                                |
| index           | 行标签 Index 对象                                |
| dtypes          | 每列的数据类型（Series）                             |
| shape           | (行数, 列数)                                    |
| size            | 元素总数（行数 × 列数）                               |
| ndim            | 维度，始终为 2                                    |
| info()          | 打印摘要信息（列名、非空数、类型、内存）                        |
| describe()      | 数值列统计摘要（count/mean/std/min/25%/50%/75%/max） |
| memory\_usage() | 每列内存占用（字节）                                  |

```python
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 3 entries, 0 to 2
# Data columns (total 3 columns): ...

df.describe()
#        age      score
# count  3.0   3.000000
# mean  30.0  86.266667
# ...

print(df.dtypes)
# name      object
# age        int64
# score    float64

```

---

## 数据读写

### read\_csv()

读取 CSV 或文本文件。

```python
df = pd.read_csv("data.csv")
df = pd.read_csv("data.csv", sep="\t", encoding="utf-8", nrows=1000)

```

| 参数                      | 类型                       | 默认值     | 说明                                |
| ----------------------- | ------------------------ | ------- | --------------------------------- |
| filepath\_or\_buffer    | str / path / IO          | 必填      | 文件路径、URL 或文件对象                    |
| sep                     | str                      | ','     | 字段分隔符，支持正则；\\t 为制表符               |
| delimiter               | str                      | None    | sep 的别名                           |
| header                  | int / list / None        | 'infer' | 作为列名的行号；None 表示无表头                |
| names                   | list                     | None    | 自定义列名列表；配合 header=0 可替换原表头        |
| index\_col              | int / str / list / False | None    | 作为行索引的列；False 强制不使用索引列            |
| usecols                 | list / callable          | None    | 只读取指定列，可减少内存占用                    |
| dtype                   | dict / str               | None    | 指定各列类型，如 {"id": int, "name": str} |
| parse\_dates            | bool / list / dict       | False   | 解析日期列；True 尝试解析索引                 |
| infer\_datetime\_format | bool                     | False   | 自动推断日期格式（已在 2.0 废弃）               |
| nrows                   | int                      | None    | 只读取前 N 行                          |
| skiprows                | int / list / callable    | None    | 跳过指定行（行号列表或前 N 行）                 |
| skipfooter              | int                      | 0       | 跳过末尾 N 行                          |
| encoding                | str                      | None    | 文件编码，如 'utf-8'、'gbk'              |
| na\_values              | str / list / dict        | None    | 额外视为 NaN 的值                       |
| keep\_default\_na       | bool                     | True    | 是否保留默认 NaN 识别集                    |
| chunksize               | int                      | None    | 分块读取，返回 TextFileReader 迭代器        |
| compression             | str                      | 'infer' | 压缩格式：'gzip'、'bz2'、'zip'、'xz'      |
| thousands               | str                      | None    | 千位分隔符，如 ','                       |
| decimal                 | str                      | '.'     | 小数点符号                             |
| comment                 | str                      | None    | 注释行前缀字符                           |
| low\_memory             | bool                     | True    | 分块推断类型（可能导致混合类型），建议指定 dtype       |
| engine                  | str                      | None    | 解析引擎：'c'、'python'、'pyarrow'       |

```python
# 分块读取大文件
for chunk in pd.read_csv("big.csv", chunksize=10000):
    process(chunk)

# 只读取指定列，并指定类型
df = pd.read_csv(
    "sales.csv",
    usecols=["date", "amount", "region"],
    dtype={"amount": "float32", "region": "category"},
    parse_dates=["date"],
)

```

---

### read\_excel()

读取 Excel 文件（.xlsx 需要 openpyxl，.xls 需要 xlrd）。

```python
df = pd.read_excel("data.xlsx", sheet_name="Sheet1")

```

| 参数           | 类型                      | 默认值   | 说明                           |
| ------------ | ----------------------- | ----- | ---------------------------- |
| io           | str / path / IO         | 必填    | 文件路径或文件对象                    |
| sheet\_name  | str / int / list / None | 0     | 工作表名或序号；None 读取全部，返回 dict    |
| header       | int / list              | 0     | 表头行号                         |
| names        | list                    | None  | 自定义列名                        |
| index\_col   | int / list              | None  | 索引列                          |
| usecols      | str / list / callable   | None  | 指定列，支持 Excel 列字母如 "A:D"      |
| dtype        | dict                    | None  | 各列类型                         |
| nrows        | int                     | None  | 读取行数                         |
| skiprows     | int / list              | None  | 跳过行                          |
| na\_values   | str / list / dict       | None  | 额外 NaN 值                     |
| parse\_dates | bool / list             | False | 解析日期列                        |
| engine       | str                     | None  | 'openpyxl'（xlsx）、'xlrd'（xls） |

```python
# 读取多个 sheet
sheets = pd.read_excel("data.xlsx", sheet_name=None)  # 返回 {name: df}

# 读取指定列范围
df = pd.read_excel("data.xlsx", usecols="A:E", nrows=100)

```

---

### read\_json()

读取 JSON 文件或字符串。

```python
df = pd.read_json("data.json")
df = pd.read_json('{"a":[1,2],"b":[3,4]}')

```

| 参数            | 类型              | 默认值     | 说明                                                             |
| ------------- | --------------- | ------- | -------------------------------------------------------------- |
| path\_or\_buf | str / path / IO | 必填      | 文件路径、URL 或 JSON 字符串                                            |
| orient        | str             | None    | JSON 结构方向：'split'、'records'、'index'、'columns'、'values'、'table' |
| dtype         | bool / dict     | True    | 类型推断或指定类型                                                      |
| lines         | bool            | False   | 每行一个 JSON 对象（JSONL/NDJSON 格式）                                  |
| chunksize     | int             | None    | 配合 lines=True 分块读取                                             |
| encoding      | str             | 'utf-8' | 文件编码                                                           |
| compression   | str             | 'infer' | 压缩格式                                                           |

```python
# 读取 JSONL 格式（每行一个 JSON 对象）
df = pd.read_json("data.jsonl", lines=True)

# orient='records' 对应列表套字典格式
df = pd.read_json('[{"name":"Alice","age":25}]', orient="records")

```

---

### read\_parquet()

读取 Parquet 列式存储文件（需要 pyarrow 或 fastparquet）。

```python
df = pd.read_parquet("data.parquet")

```

| 参数               | 类型         | 默认值    | 说明                               |
| ---------------- | ---------- | ------ | -------------------------------- |
| path             | str / path | 必填     | 文件路径或目录                          |
| engine           | str        | 'auto' | 'pyarrow' 或 'fastparquet'        |
| columns          | list       | None   | 只读取指定列                           |
| filters          | list       | None   | 行级过滤，格式 \[("col", "op", value)\] |
| storage\_options | dict       | None   | 远程存储选项（S3 等）                     |

```python
# 只读取部分列（Parquet 支持列裁剪，效率高）
df = pd.read_parquet("data.parquet", columns=["id", "amount"])

# 带过滤条件（分区裁剪）
df = pd.read_parquet("data.parquet", filters=[("year", "==", 2024)])

```

---

### read\_sql() / read\_sql\_query()

从数据库读取数据（需要 SQLAlchemy）。

```python
from sqlalchemy import create_engine
engine = create_engine("postgresql+psycopg2://user:pass@localhost/db")

df = pd.read_sql("SELECT * FROM users", con=engine)
df = pd.read_sql_query("SELECT id, name FROM users WHERE age > 25", con=engine)
df = pd.read_sql_table("users", con=engine)

```

| 参数            | 类型                                    | 默认值  | 说明                         |
| ------------- | ------------------------------------- | ---- | -------------------------- |
| sql           | str / SQLAlchemy Selectable           | 必填   | SQL 查询语句或表名                |
| con           | SQLAlchemy engine / DBAPI2 connection | 必填   | 数据库连接                      |
| index\_col    | str / list                            | None | 作为索引的列                     |
| coerce\_float | bool                                  | True | 将非字符串数值转为浮点                |
| params        | list / dict                           | None | SQL 参数绑定                   |
| parse\_dates  | list / dict                           | None | 解析日期列                      |
| columns       | list                                  | None | 仅对 read\_sql\_table 有效，指定列 |
| chunksize     | int                                   | None | 分块读取                       |

---

### 写出函数

#### to\_csv()

| 参数            | 类型              | 默认值     | 说明                |
| ------------- | --------------- | ------- | ----------------- |
| path\_or\_buf | str / path / IO | None    | 输出路径；None 返回字符串   |
| sep           | str             | ','     | 分隔符               |
| na\_rep       | str             | ''      | NaN 的替换字符串        |
| columns       | list            | None    | 指定输出列             |
| header        | bool / list     | True    | 是否写出列名            |
| index         | bool            | True    | 是否写出行索引           |
| index\_label  | str / list      | None    | 索引列的列名            |
| mode          | str             | 'w'     | 写入模式，'a' 追加       |
| encoding      | str             | 'utf-8' | 文件编码              |
| compression   | str / dict      | 'infer' | 压缩格式              |
| float\_format | str             | None    | 浮点数格式，如 '%.2f'    |
| date\_format  | str             | None    | 日期格式，如 '%Y-%m-%d' |
| chunksize     | int             | None    | 分块写出              |

```python
df.to_csv("output.csv", index=False, encoding="utf-8-sig")  # utf-8-sig 避免 Excel 乱码

```

#### to\_excel()

| 参数            | 类型                | 默认值      | 说明                      |
| ------------- | ----------------- | -------- | ----------------------- |
| excel\_writer | str / ExcelWriter | 必填       | 文件路径或 ExcelWriter 对象    |
| sheet\_name   | str               | 'Sheet1' | 工作表名                    |
| na\_rep       | str               | ''       | NaN 替换                  |
| columns       | list              | None     | 指定列                     |
| header        | bool / list       | True     | 是否写出表头                  |
| index         | bool              | True     | 是否写出索引                  |
| startrow      | int               | 0        | 起始行（0-based）            |
| startcol      | int               | 0        | 起始列（0-based）            |
| engine        | str               | None     | 'openpyxl'、'xlsxwriter' |
| float\_format | str               | None     | 浮点数格式                   |
| freeze\_panes | tuple             | None     | 冻结窗格，如 (1, 0) 冻结首行      |

```python
# 多 sheet 写出
with pd.ExcelWriter("output.xlsx", engine="openpyxl") as writer:
    df1.to_excel(writer, sheet_name="销售", index=False)
    df2.to_excel(writer, sheet_name="库存", index=False)

```

#### to\_parquet()

| 参数               | 类型         | 默认值      | 说明                               |
| ---------------- | ---------- | -------- | -------------------------------- |
| path             | str / path | None     | 输出路径；None 返回字节                   |
| engine           | str        | 'auto'   | 'pyarrow' 或 'fastparquet'        |
| compression      | str        | 'snappy' | 压缩：'snappy'、'gzip'、'brotli'、None |
| index            | bool       | None     | 是否写出索引                           |
| partition\_cols  | list       | None     | 按列分区存储                           |
| storage\_options | dict       | None     | 远程存储选项                           |

#### to\_json()

| 参数                | 类型              | 默认值     | 说明                                                           |
| ----------------- | --------------- | ------- | ------------------------------------------------------------ |
| path\_or\_buf     | str / path / IO | None    | 输出路径；None 返回字符串                                              |
| orient            | str             | 视对象而定   | JSON 结构：'split'、'records'、'index'、'columns'、'values'、'table' |
| date\_format      | str             | 'epoch' | 日期格式：'epoch'（毫秒时间戳）或 'iso'                                   |
| double\_precision | int             | 10      | 浮点数精度                                                        |
| force\_ascii      | bool            | True    | 强制 ASCII 编码；False 保留 Unicode                                 |
| indent            | int             | None    | JSON 缩进空格数                                                   |
| lines             | bool            | False   | 每行一个 JSON 对象（JSONL）                                          |
| compression       | str / dict      | 'infer' | 压缩格式                                                         |

```python
# 输出 JSONL 格式（常用于流式处理）
df.to_json("output.jsonl", orient="records", lines=True, force_ascii=False)

```

---

## 数据查看

### head() / tail() / sample()

```python
df.head(5)      # 前 5 行（默认）
df.tail(10)     # 后 10 行
df.sample(5)    # 随机 5 行
df.sample(frac=0.1, random_state=42)  # 随机 10%，固定随机种子

```

| 方法                    | 参数                 | 默认值   | 说明          |
| --------------------- | ------------------ | ----- | ----------- |
| head(n)               | n: int             | 5     | 前 N 行       |
| tail(n)               | n: int             | 5     | 后 N 行       |
| sample(n)             | n: int             | 1     | 随机 N 行      |
| sample(frac)          | frac: float        | None  | 随机比例，与 n 互斥 |
| sample(replace)       | replace: bool      | False | 是否有放回抽样     |
| sample(random\_state) | random\_state: int | None  | 随机种子        |

---

### info() / describe() / value\_counts()

```python
df.info()                    # 列名、非空数、类型、内存
df.info(memory_usage="deep") # 精确内存统计

df.describe()                # 数值列统计摘要
df.describe(include="all")   # 包含字符串/category 列
df.describe(include="object")

df["city"].value_counts()               # 频次统计，降序
df["city"].value_counts(normalize=True) # 比例
df["city"].value_counts(dropna=False)   # 包含 NaN

```

### shape / dtypes / columns / index

```python
df.shape      # (100, 5) — (行数, 列数)
df.dtypes     # 每列数据类型
df.columns    # Index(['a', 'b', 'c'])
df.index      # RangeIndex(start=0, stop=100, step=1)

# 修改列名
df.columns = ["col1", "col2", "col3"]

# 列名转列表
cols = df.columns.tolist()

```

---

## 数据选取

这是 pandas 使用最频繁的核心章节。

### \[\] 基础选取

```python
# 选取单列 -> 返回 Series
s = df["name"]

# 选取多列 -> 返回 DataFrame
sub = df"name", "age"

# 行切片（仅支持整数或标签切片）
df[0:5]       # 前 5 行（整数切片，不含第 5 行）
df["a":"c"]   # 标签切片，含端点（适用于有字符串索引的 df）

```

注意：`[]` 用于行切片时是基于位置（整数）或标签，行为依索引类型而定，容易混淆，建议优先使用 `loc` / `iloc`。

---

### loc\[\]

基于**标签**（行索引名和列名）选取数据。

```python
# 单行
df.loc[0]
df.loc["alice"]

# 单个值
df.loc[0, "name"]

# 行范围（含端点）
df.loc[0:5]
df.loc["2024-01-01":"2024-01-31"]

# 行 + 列
df.loc[0:5, "name":"age"]
df.loc[[0, 2, 4], ["name", "score"]]

# 布尔数组
mask = df["age"] > 25
df.loc[mask]
df.loc[mask, "name"]

# 赋值（推荐方式）
df.loc[0, "score"] = 99.0
df.loc[df["age"] > 30, "grade"] = "senior"

```

| 参数           | 类型                                 | 说明        |
| ------------ | ---------------------------------- | --------- |
| row\_indexer | 标签 / 标签列表 / 标签切片 / 布尔数组 / callable | 行选择器      |
| col\_indexer | 标签 / 标签列表 / 标签切片 / 布尔数组 / callable | 列选择器（可省略） |

---

### iloc\[\]

基于**整数位置**选取数据（0-based，不含末端）。

```python
# 单行
df.iloc[0]
df.iloc[-1]   # 最后一行

# 单个值
df.iloc[0, 2]  # 第 0 行第 2 列

# 行范围（不含末端）
df.iloc[0:5]
df.iloc[::2]   # 每隔一行

# 行 + 列
df.iloc[0:5, 0:3]
df.iloc[[0, 2, 4], [1, 3]]

# 赋值
df.iloc[0, 1] = 100

```

| 参数           | 类型                                      | 说明          |
| ------------ | --------------------------------------- | ----------- |
| row\_indexer | int / int 列表 / int 切片 / 布尔数组 / callable | 行位置选择器      |
| col\_indexer | int / int 列表 / int 切片 / 布尔数组 / callable | 列位置选择器（可省略） |

---

### at\[\] / iat\[\]

单值快速访问，比 `loc`/`iloc` 更快（无需额外开销）。

```python
df.at[0, "name"]        # 标签访问单值
df.iat[0, 2]            # 位置访问单值

df.at[0, "score"] = 95  # 赋值
df.iat[0, 2] = 95

```

| 方法                           | 参数       | 说明          |
| ---------------------------- | -------- | ----------- |
| at\[row\_label, col\_label\] | 行标签, 列标签 | 基于标签的单值访问   |
| iat\[row\_pos, col\_pos\]    | 行位置, 列位置 | 基于整数位置的单值访问 |

---

### 布尔索引

```python
# 单条件
df[df["age"] > 25]

# 多条件（必须用括号和位运算符）
df[(df["age"] > 25) & (df["city"] == "北京")]
df[(df["score"] < 60) | (df["score"] > 90)]
df[~(df["city"] == "北京")]  # 取反

# isin（IN 过滤）
df[df["city"].isin(["北京", "上海", "广州"])]

# between（区间过滤，含端点）
df[df["age"].between(20, 30)]

# str.contains（模糊匹配）
df[df["name"].str.contains("张")]

# notna / isna
df[df["score"].notna()]

```

---

### query()

使用字符串表达式过滤，语法更简洁，支持链式调用。

```python
df.query("age > 25")
df.query("city == '北京' and score >= 80")
df.query("city in ['北京', '上海']")
df.query("age.between(20, 30)")  # 需要 numexpr

# 引用外部变量（加 @ 前缀）
threshold = 25
df.query("age > @threshold")

# 列名含空格时用反引号
df.query("`first name` == 'Alice'")

```

| 参数         | 类型   | 默认值   | 说明                         |
| ---------- | ---- | ----- | -------------------------- |
| expr       | str  | 必填    | 过滤表达式字符串                   |
| inplace    | bool | False | 是否原地修改                     |
| \*\*kwargs | \-   | \-    | 传递给 numexpr.evaluate 的额外参数 |

---

### 多条件过滤总结

```python
# 方法一：布尔索引（最通用）
result = df[(df["age"] > 25) & (df["score"] > 80) & (df["city"] == "北京")]

# 方法二：query（最简洁）
result = df.query("age > 25 and score > 80 and city == '北京'")

# 方法三：np.logical_and.reduce（多条件组合）
import numpy as np
conditions = [df["age"] > 25, df["score"] > 80, df["city"] == "北京"]
result = df[np.logical_and.reduce(conditions)]

```

---

## 数据清洗

### 缺失值处理

#### isna() / notna()

```python
df.isna()              # 逐元素 True/False
df.isna().sum()        # 每列缺失数量
df.isna().sum() / len(df)  # 缺失比例
df["score"].isna()
df["score"].notna()

```

#### fillna()

填充缺失值。

| 参数      | 类型                                 | 默认值   | 说明                                                         |
| ------- | ---------------------------------- | ----- | ---------------------------------------------------------- |
| value   | scalar / dict / Series / DataFrame | None  | 填充值；dict 可按列指定不同值                                          |
| method  | str                                | None  | 'ffill'（前向填充）、'bfill'（后向填充）；pandas 2.0 建议用 ffill()/bfill() |
| axis    | int / str                          | 0     | 0/'index' 按行，1/'columns' 按列                                |
| inplace | bool                               | False | 是否原地修改                                                     |
| limit   | int                                | None  | 最多连续填充 N 个缺失值                                              |

```python
df.fillna(0)
df.fillna({"age": 0, "score": df["score"].mean()})
df["score"].fillna(df["score"].median(), inplace=True)
df.ffill()   # 前向填充（推荐替代 method='ffill'）
df.bfill()   # 后向填充

```

#### dropna()

删除含缺失值的行或列。

| 参数      | 类型        | 默认值   | 说明                               |
| ------- | --------- | ----- | -------------------------------- |
| axis    | int / str | 0     | 0 删行，1 删列                        |
| how     | str       | 'any' | 'any'：含任意 NaN 则删；'all'：全为 NaN 才删 |
| thresh  | int       | None  | 至少有 N 个非 NaN 才保留                 |
| subset  | list      | None  | 只检查指定列                           |
| inplace | bool      | False | 是否原地修改                           |

```python
df.dropna()                          # 删除含任意 NaN 的行
df.dropna(subset=["name", "score"])  # 只检查这两列
df.dropna(how="all")                 # 全为 NaN 才删
df.dropna(thresh=3)                  # 保留至少有 3 个非 NaN 的行

```

---

### 重复值处理

#### duplicated()

| 参数     | 类型          | 默认值     | 说明                                    |
| ------ | ----------- | ------- | ------------------------------------- |
| subset | list / str  | None    | 用于判断重复的列；None 使用全部列                   |
| keep   | str / False | 'first' | 'first'：保留首次；'last'：保留末次；False：标记所有重复 |

```python
df.duplicated()                    # 返回布尔 Series
df.duplicated(subset=["name"])     # 只按 name 判断重复
df[df.duplicated()]                # 查看重复行
df.duplicated().sum()              # 重复行数量

```

#### drop\_duplicates()

| 参数            | 类型          | 默认值     | 说明     |
| ------------- | ----------- | ------- | ------ |
| subset        | list / str  | None    | 判断重复的列 |
| keep          | str / False | 'first' | 保留策略   |
| inplace       | bool        | False   | 是否原地修改 |
| ignore\_index | bool        | False   | 是否重置索引 |

```python
df.drop_duplicates()
df.drop_duplicates(subset=["name", "date"], keep="last")

```

---

### 类型转换

#### astype()

| 参数     | 类型                 | 默认值     | 说明                           |
| ------ | ------------------ | ------- | ---------------------------- |
| dtype  | str / dtype / dict | 必填      | 目标类型；dict 可按列指定              |
| copy   | bool               | True    | 是否返回副本（pandas 2.0 CoW 下效果变化） |
| errors | str                | 'raise' | 'raise'：报错；'ignore'：出错时返回原数据 |

```python
df["age"].astype(int)
df["price"].astype("float32")
df["category"].astype("category")
df.astype({"age": int, "score": "float32"})

```

#### pd.to\_numeric()

| 参数       | 类型                     | 默认值     | 说明                                         |
| -------- | ---------------------- | ------- | ------------------------------------------ |
| arg      | scalar / list / Series | 必填      | 要转换的数据                                     |
| errors   | str                    | 'raise' | 'raise'、'coerce'（转 NaN）、'ignore'（返回原值）     |
| downcast | str                    | None    | 'integer'、'signed'、'unsigned'、'float' 降级类型 |

```python
pd.to_numeric(df["score"], errors="coerce")  # 无法转换的变为 NaN

```

#### pd.to\_datetime()

| 参数       | 类型                                    | 默认值     | 说明                                      |
| -------- | ------------------------------------- | ------- | --------------------------------------- |
| arg      | str / int / list / Series / DataFrame | 必填      | 要转换的数据                                  |
| errors   | str                                   | 'raise' | 'raise'、'coerce'、'ignore'               |
| format   | str                                   | None    | 日期格式字符串，如 '%Y-%m-%d'；pandas 2.0+ 推荐显式指定 |
| unit     | str                                   | None    | 时间戳单位：'s'、'ms'、'us'、'ns'                |
| utc      | bool                                  | False   | 是否转为 UTC 时区                             |
| dayfirst | bool                                  | False   | 是否优先解析为日在前的格式                           |

```python
pd.to_datetime(df["date"])
pd.to_datetime(df["date"], format="%Y-%m-%d")
pd.to_datetime(df["ts"], unit="s")    # Unix 时间戳（秒）
pd.to_datetime(df["date"], errors="coerce")  # 非法日期转 NaT

```

---

### 重命名

#### rename()

| 参数      | 类型              | 默认值      | 说明                                   |
| ------- | --------------- | -------- | ------------------------------------ |
| mapper  | dict / function | None     | 应用于 axis 的映射或函数                      |
| index   | dict / function | None     | 行索引重命名                               |
| columns | dict / function | None     | 列名重命名                                |
| axis    | int / str       | 0        | 配合 mapper 使用，0/'index' 或 1/'columns' |
| inplace | bool            | False    | 是否原地修改                               |
| errors  | str             | 'ignore' | 'raise' 在键不存在时报错                     |

```python
df.rename(columns={"old_name": "new_name", "age": "年龄"})
df.rename(columns=str.lower)           # 全部列名转小写
df.rename(index={0: "first_row"})      # 重命名行索引

```

---

### 重置索引

#### reset\_index()

| 参数         | 类型               | 默认值   | 说明               |
| ---------- | ---------------- | ----- | ---------------- |
| level      | int / str / list | None  | 重置的层级（多层索引时使用）   |
| drop       | bool             | False | True 则丢弃原索引，不加入列 |
| inplace    | bool             | False | 是否原地修改           |
| col\_level | int / str        | 0     | 多层列时，将索引插入的层级    |

```python
df.reset_index()             # 原索引成为列，新增 RangeIndex
df.reset_index(drop=True)    # 丢弃原索引，常用于过滤后重置行号

```

#### set\_index()

| 参数                | 类型                          | 默认值   | 说明                |
| ----------------- | --------------------------- | ----- | ----------------- |
| keys              | str / list / Series / Index | 必填    | 作为索引的列名或列名列表      |
| drop              | bool                        | True  | 是否从列中删除选为索引的列     |
| append            | bool                        | False | 是否追加到现有索引（构建多层索引） |
| inplace           | bool                        | False | 是否原地修改            |
| verify\_integrity | bool                        | False | 是否检查索引唯一性         |

```python
df.set_index("id")
df.set_index(["year", "month"])  # 多层索引
df.set_index("date", drop=True)

```

---

## 数据变换

### apply()

对 DataFrame 的行或列（或 Series 的每个元素）应用函数。

| 参数           | 类型        | 默认值   | 说明                                         |
| ------------ | --------- | ----- | ------------------------------------------ |
| func         | callable  | 必填    | 要应用的函数                                     |
| axis         | int / str | 0     | 0/'index'：对每列应用；1/'columns'：对每行应用          |
| raw          | bool      | False | True 则传入 ndarray 而非 Series，可提升性能           |
| result\_type | str       | None  | 仅 axis=1 时有效：'expand'、'reduce'、'broadcast' |
| args         | tuple     | ()    | 传递给 func 的额外位置参数                           |
| \*\*kwargs   | \-        | \-    | 传递给 func 的关键字参数                            |

```python
# 对每列应用
df.apply(lambda col: col.max() - col.min())

# 对每行应用（axis=1）
df.apply(lambda row: row["score"] * 1.1 if row["grade"] == "A" else row["score"], axis=1)

# result_type='expand' 将返回的 list 展开为多列
df.apply(lambda row: [row["x"] + 1, row["y"] * 2], axis=1, result_type="expand")

# 传额外参数
def scale(col, factor):
    return col * factor

df.apply(scale, args=(2.0,))
df.apply(scale, factor=2.0)

```

---

### map() / applymap() / pipe()

```python
# Series.map()：逐元素映射（替代值 or 应用函数）
df["grade"].map({"A": 4.0, "B": 3.0, "C": 2.0})
df["score"].map(lambda x: round(x, 1))

# DataFrame.map()（pandas 2.1+，之前叫 applymap）：逐元素应用函数
df.map(lambda x: str(x).strip())

# applymap()：pandas < 2.1 的写法（2.1 起已弃用，改用 map）
df.applymap(lambda x: x ** 2)

# pipe()：链式传入整个 DataFrame，用于 method chaining
def normalize(df, col):
    df[col] = (df[col] - df[col].mean()) / df[col].std()
    return df

df.pipe(normalize, "score").pipe(normalize, "age")

```

---

### 向量化操作

pandas 支持直接在列之间做算术和比较运算，底层由 NumPy 加速，避免使用循环。

```python
df["total"] = df["price"] * df["quantity"]
df["discount_price"] = df["price"] * (1 - df["discount_rate"])
df["is_adult"] = df["age"] >= 18
df["z_score"] = (df["score"] - df["score"].mean()) / df["score"].std()

# 字符串拼接
df["full_name"] = df["first_name"] + " " + df["last_name"]

# 条件赋值：np.where
import numpy as np
df["label"] = np.where(df["score"] >= 60, "pass", "fail")

# 多条件赋值：np.select
conditions = [df["score"] >= 90, df["score"] >= 60]
choices = ["优秀", "合格"]
df["grade"] = np.select(conditions, choices, default="不合格")

```

---

### assign()

返回新 DataFrame（不修改原数据），适合链式操作。

```python
df2 = (
    df
    .assign(total=df["price"] * df["quantity"])
    .assign(tax=lambda x: x["total"] * 0.13)
    .assign(final=lambda x: x["total"] + x["tax"])
)

```

| 参数         | 类型                | 说明                               |
| ---------- | ----------------- | -------------------------------- |
| \*\*kwargs | scalar / callable | 列名=值或列名=函数；函数接收 df 作为参数，可引用刚新建的列 |

---

### 列的增删改

```python
# 新增列
df["new_col"] = 0
df["new_col"] = df["a"] + df["b"]

# 修改列
df["score"] = df["score"] * 1.1

# 删除列
df.drop(columns=["col1", "col2"])        # 返回新 df
df.drop(columns="col1", inplace=True)    # 原地删除
del df["col1"]                           # 直接删除（原地）
df.pop("col1")                           # 删除并返回该列

# 插入列到指定位置
df.insert(loc=2, column="middle_col", value=0)

```

---

## 字符串操作

通过 `Series.str` 访问器对字符串列进行操作，自动处理 NaN（返回 NaN 而不是报错）。

### str.contains()

| 参数    | 类型     | 默认值  | 说明                       |
| ----- | ------ | ---- | ------------------------ |
| pat   | str    | 必填   | 匹配模式（默认正则）               |
| case  | bool   | True | 是否区分大小写                  |
| flags | int    | 0    | re 模块的标志，如 re.IGNORECASE |
| na    | scalar | None | NaN 的填充值                 |
| regex | bool   | True | False 则作为字面字符串匹配         |

```python
df[df["name"].str.contains("张", na=False)]
df[df["email"].str.contains(r"@gmail\.com", regex=True, na=False)]

```

### str.startswith() / str.endswith()

| 参数  | 类型          | 默认值  | 说明             |
| --- | ----------- | ---- | -------------- |
| pat | str / tuple | 必填   | 前缀/后缀字符串或字符串元组 |
| na  | scalar      | None | NaN 的填充值       |

```python
df[df["filename"].str.endswith((".csv", ".xlsx"), na=False)]

```

### str.replace()

| 参数    | 类型               | 默认值  | 说明              |
| ----- | ---------------- | ---- | --------------- |
| pat   | str / re.Pattern | 必填   | 匹配模式（默认正则）      |
| repl  | str / callable   | 必填   | 替换字符串或函数        |
| n     | int              | \-1  | 最多替换次数；\-1 全部替换 |
| case  | bool             | None | 是否区分大小写         |
| flags | int              | 0    | re 标志           |
| regex | bool             | True | 是否使用正则          |

```python
df["phone"].str.replace(r"\D", "", regex=True)           # 去除非数字
df["text"].str.replace("旧词", "新词", regex=False)      # 字面替换

```

### str.strip() / str.lstrip() / str.rstrip()

| 参数        | 类型  | 默认值  | 说明               |
| --------- | --- | ---- | ---------------- |
| to\_strip | str | None | 要去除的字符；None 去除空白 |

```python
df["name"].str.strip()
df["code"].str.strip("0")  # 去除前后的 "0"

```

### str.split()

| 参数     | 类型   | 默认值   | 说明                                         |
| ------ | ---- | ----- | ------------------------------------------ |
| pat    | str  | None  | 分隔符（默认空白字符）                                |
| n      | int  | \-1   | 最多分割次数                                     |
| expand | bool | False | True 返回 DataFrame，False 返回 Series of lists |
| regex  | bool | None  | 是否使用正则                                     |

```python
df["name"].str.split(" ")                        # 返回 Series of lists
df["name"].str.split(" ", expand=True)           # 展开为多列
df["date"].str.split("-", expand=True, n=2)      # 最多分 2 次

```

### str.extract() / str.findall()

| 方法                              | 参数           | 说明                               |
| ------------------------------- | ------------ | -------------------------------- |
| str.extract(pat, flags, expand) | pat: 含捕获组的正则 | 提取第一个匹配；expand=True 返回 DataFrame |
| str.extractall(pat, flags)      | pat: 含捕获组的正则 | 提取所有匹配，返回 MultiIndex DataFrame   |
| str.findall(pat, flags)         | pat: 正则      | 返回所有匹配列表                         |

```python
# extract：提取捕获组
df["date"].str.extract(r"(\d{4})-(\d{2})-(\d{2})")  # 返回 3 列
df["email"].str.extract(r"(?P<user>\w+)@(?P<domain>\w+)")  # 命名捕获组

# findall：返回所有匹配
df["text"].str.findall(r"\d+")

```

### str.upper() / str.lower() / str.len()

```python
df["name"].str.upper()
df["name"].str.lower()
df["name"].str.title()      # 首字母大写
df["text"].str.len()        # 字符串长度（NaN 返回 NaN）
df["text"].str.count("a")   # 字符出现次数

```

| 方法                             | 说明            |
| ------------------------------ | ------------- |
| str.upper()                    | 全部大写          |
| str.lower()                    | 全部小写          |
| str.title()                    | 标题格式（每词首字母大写） |
| str.capitalize()               | 首字母大写，其余小写    |
| str.len()                      | 字符串长度         |
| str.count(pat)                 | 子串出现次数        |
| str.get(i)                     | 获取第 i 个字符     |
| str.pad(width, side, fillchar) | 填充到指定宽度       |
| str.zfill(width)               | 左侧补零          |
| str.cat(others, sep)           | 字符串连接         |

---

## 日期时间操作

### pd.to\_datetime()

见 [Pandas完全指南 · 类型转换](https://blog.vercanti.com/pandas-wan-quan-zhi-nan/#%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2) 章节中的参数表。

### dt 访问器属性

通过 `Series.dt` 访问器操作 datetime 类型列。

```python
# 先转换为 datetime 类型
df["date"] = pd.to_datetime(df["date"])

# 提取日期部分
df["date"].dt.year
df["date"].dt.month
df["date"].dt.day
df["date"].dt.hour
df["date"].dt.minute
df["date"].dt.second
df["date"].dt.microsecond
df["date"].dt.weekday    # 0=周一, 6=周日
df["date"].dt.day_name() # 星期名称（英文）
df["date"].dt.month_name()
df["date"].dt.dayofyear  # 一年中的第几天
df["date"].dt.quarter    # 季度（1-4）
df["date"].dt.is_leap_year
df["date"].dt.days_in_month

```

| 属性                  | 说明        |
| ------------------- | --------- |
| dt.year             | 年         |
| dt.month            | 月（1-12）   |
| dt.day              | 日（1-31）   |
| dt.hour             | 时（0-23）   |
| dt.minute           | 分（0-59）   |
| dt.second           | 秒（0-59）   |
| dt.weekday          | 星期几（0=周一） |
| dt.dayofweek        | 同 weekday |
| dt.dayofyear        | 一年中的第几天   |
| dt.quarter          | 季度        |
| dt.is\_month\_end   | 是否月末      |
| dt.is\_month\_start | 是否月初      |
| dt.is\_year\_end    | 是否年末      |
| dt.is\_leap\_year   | 是否闰年      |
| dt.days\_in\_month  | 当月天数      |

---

### dt.strftime() / dt.floor() / dt.ceil() / dt.normalize()

```python
df["date"].dt.strftime("%Y年%m月%d日")
df["date"].dt.floor("H")        # 向下取整到小时
df["date"].dt.ceil("T")         # 向上取整到分钟
df["date"].dt.normalize()       # 截断到日（时间部分归零）
df["date"].dt.tz_localize("Asia/Shanghai")    # 本地化时区
df["date"].dt.tz_convert("UTC")               # 转换时区

```

| 方法                        | 参数                | 说明                      |
| ------------------------- | ----------------- | ----------------------- |
| dt.strftime(date\_format) | date\_format: str | 格式化为字符串                 |
| dt.floor(freq)            | freq: str         | 向下取整，freq 如 'H'、'T'、'D' |
| dt.ceil(freq)             | freq: str         | 向上取整                    |
| dt.round(freq)            | freq: str         | 四舍五入取整                  |
| dt.normalize()            | \-                | 归零时间部分                  |

---

### DateOffset / Timedelta

```python
from pandas.tseries.offsets import MonthEnd, BDay

# Timedelta（固定时间差）
pd.Timedelta("1 days 2 hours")
pd.Timedelta(days=1, hours=2)
df["date"] + pd.Timedelta(days=7)

# DateOffset（日历感知）
df["date"] + pd.DateOffset(months=1)   # 下个月同日
df["date"] + MonthEnd(1)               # 下个月末
df["date"] + BDay(5)                   # 5 个工作日后

```

---

### resample()

对时间序列进行重采样（必须以 datetime 为索引）。

| 参数         | 类型               | 默认值          | 说明                                          |
| ---------- | ---------------- | ------------ | ------------------------------------------- |
| rule       | str / DateOffset | 必填           | 重采样频率：'D'、'W'、'M'、'Q'、'Y'、'H'、'T'（分钟）等      |
| axis       | int / str        | 0            | 沿哪个轴重采样（pandas 2.0 废弃，改用 .T.resample()...T） |
| closed     | str              | None         | 区间哪端闭合：'left' 或 'right'                     |
| label      | str              | None         | 用区间哪端标签：'left' 或 'right'                    |
| convention | str              | 'start'      | 对 PeriodIndex 有效                            |
| origin     | str / Timestamp  | 'start\_day' | 起始点对齐                                       |
| offset     | Timedelta / str  | None         | 起始偏移                                        |

```python
# 设置时间索引
df = df.set_index("date")

# 按月聚合
df.resample("M").sum()
df.resample("M").agg({"amount": "sum", "count": "count"})

# 按周求均值
df["price"].resample("W").mean()

# 降采样（粗粒度）
df.resample("Q").last()      # 每季度最后一个值

# 升采样（细粒度）后填充
df.resample("D").ffill()     # 日频，前向填充

```

---

### date\_range()

生成日期范围。

| 参数        | 类型               | 默认值    | 说明                                   |
| --------- | ---------------- | ------ | ------------------------------------ |
| start     | str / datetime   | None   | 起始日期                                 |
| end       | str / datetime   | None   | 结束日期                                 |
| periods   | int              | None   | 生成点数；与 end 二选一                       |
| freq      | str / DateOffset | 'D'    | 频率                                   |
| tz        | str / tzinfo     | None   | 时区                                   |
| normalize | bool             | False  | 是否归零时间部分                             |
| name      | str              | None   | 结果 Index 的名称                         |
| inclusive | str              | 'both' | 'both'、'left'、'right'、'neither' 端点包含 |

```python
pd.date_range("2024-01-01", "2024-12-31", freq="D")   # 全年每天
pd.date_range("2024-01-01", periods=12, freq="MS")     # 12 个月初
pd.date_range("2024-01-01", periods=5, freq="B")       # 5 个工作日

```

---

## 分组聚合

### groupby()

| 参数          | 类型                           | 默认值   | 说明                                |
| ----------- | ---------------------------- | ----- | --------------------------------- |
| by          | str / list / callable / dict | 必填    | 分组依据：列名、列名列表、函数或字典                |
| axis        | int / str                    | 0     | 分组轴（pandas 2.0 废弃列方向分组）           |
| level       | int / str                    | None  | MultiIndex 层级                     |
| as\_index   | bool                         | True  | False 则分组键不作为索引，而是普通列             |
| sort        | bool                         | True  | 是否对分组键排序                          |
| group\_keys | bool                         | True  | apply 时是否在结果中添加分组键                |
| observed    | bool                         | False | 仅对 Categorical 有效；True 只显示实际出现的分组 |
| dropna      | bool                         | True  | 是否丢弃分组键为 NaN 的组                   |

```python
g = df.groupby("city")
g = df.groupby(["city", "year"])
g = df.groupby("city", as_index=False)
g = df.groupby("city", sort=False, observed=True)

```

---

### agg() / aggregate()

| 参数         | 类型                           | 默认值 | 说明                         |
| ---------- | ---------------------------- | --- | -------------------------- |
| func       | str / callable / list / dict | 必填  | 聚合函数；dict 格式 {列名: 函数或函数列表} |
| \*args     | \-                           | \-  | 传递给函数的位置参数                 |
| \*\*kwargs | \-                           | \-  | 传递给函数的关键字参数                |

```python
# 单函数
df.groupby("city")["score"].agg("mean")

# 多函数
df.groupby("city")["score"].agg(["mean", "std", "count"])

# 按列指定不同函数
df.groupby("city").agg({
    "score": ["mean", "max"],
    "age":   "median",
})

# 命名聚合（pandas 0.25+，推荐）
df.groupby("city").agg(
    avg_score=("score", "mean"),
    max_score=("score", "max"),
    count=("id", "count"),
)

```

---

### transform()

对每组应用函数，返回与原 DataFrame 等长的结果（不降维）。

| 参数         | 类型                           | 默认值 | 说明               |
| ---------- | ---------------------------- | --- | ---------------- |
| func       | str / callable / list / dict | 必填  | 转换函数；需返回与输入等长的结果 |
| \*args     | \-                           | \-  | 额外参数             |
| \*\*kwargs | \-                           | \-  | 关键字参数            |

```python
# 组内标准化（z-score）
df["score_z"] = df.groupby("city")["score"].transform(
    lambda x: (x - x.mean()) / x.std()
)

# 用组均值填充缺失值
df["score"] = df.groupby("city")["score"].transform(lambda x: x.fillna(x.mean()))

# 添加组统计到原 df
df["city_avg"] = df.groupby("city")["score"].transform("mean")
df["city_rank"] = df.groupby("city")["score"].transform("rank", ascending=False)

```

---

### filter()

过滤满足条件的组（整组保留或整组丢弃）。

| 参数     | 类型       | 默认值  | 说明                           |
| ------ | -------- | ---- | ---------------------------- |
| func   | callable | 必填   | 接收子 DataFrame/Series，返回 bool |
| dropna | bool     | True | 是否丢弃过滤后的 NaN 行               |

```python
# 保留组内记录数 >= 10 的组
df.groupby("city").filter(lambda g: len(g) >= 10)

# 保留组均值 > 80 的组
df.groupby("city").filter(lambda g: g["score"].mean() > 80)

```

---

### apply() 在 GroupBy 上的用法

```python
# 每组取 score 最高的 3 条
df.groupby("city").apply(lambda g: g.nlargest(3, "score"))

# 每组自定义处理
def process_group(g):
    g = g.copy()
    g["rank"] = g["score"].rank(ascending=False)
    return g

df.groupby("city").apply(process_group)

```

---

### 常用聚合函数汇总

| 函数        | 说明          |
| --------- | ----------- |
| sum()     | 求和          |
| mean()    | 均值          |
| median()  | 中位数         |
| count()   | 非 NaN 数量    |
| size()    | 总行数（含 NaN）  |
| nunique() | 唯一值数量       |
| first()   | 第一个值        |
| last()    | 最后一个值       |
| min()     | 最小值         |
| max()     | 最大值         |
| std()     | 标准差（ddof=1） |
| var()     | 方差          |
| sem()     | 标准误         |
| prod()    | 乘积          |
| cumsum()  | 累计和         |
| cumprod() | 累计积         |
| cummax()  | 累计最大        |
| cummin()  | 累计最小        |

---

## 合并与连接

### merge()

类似 SQL JOIN，默认内连接。

| 参数           | 类型         | 默认值            | 说明                                          |
| ------------ | ---------- | -------------- | ------------------------------------------- |
| left         | DataFrame  | 必填             | 左表                                          |
| right        | DataFrame  | 必填             | 右表                                          |
| how          | str        | 'inner'        | 连接方式：'inner'、'left'、'right'、'outer'、'cross' |
| on           | str / list | None           | 连接键（两表中同名列）；与 left\_on/right\_on 互斥         |
| left\_on     | str / list | None           | 左表连接键                                       |
| right\_on    | str / list | None           | 右表连接键                                       |
| left\_index  | bool       | False          | 用左表索引作为连接键                                  |
| right\_index | bool       | False          | 用右表索引作为连接键                                  |
| suffixes     | tuple      | ('\_x', '\_y') | 重名列的后缀                                      |
| validate     | str        | None           | 校验连接类型：'1:1'、'1:m'、'm:1'、'm:m'              |
| indicator    | bool / str | False          | 添加 \_merge 列标识来源；字符串可自定义列名                  |
| sort         | bool       | False          | 是否按连接键排序结果                                  |

```python
# 基本用法
result = pd.merge(df_left, df_right, on="id")

# 不同列名的连接键
result = pd.merge(df_orders, df_users, left_on="user_id", right_on="id")

# 用索引连接
result = pd.merge(df_left, df_right, left_index=True, right_index=True)

# 左连接，重名列加后缀，添加来源标识
result = pd.merge(
    df_left, df_right,
    on="id",
    how="left",
    suffixes=("_left", "_right"),
    indicator=True,
)
# _merge 列值：'left_only' / 'right_only' / 'both'

# 验证连接基数
result = pd.merge(df_left, df_right, on="id", validate="m:1")

```

---

### 各种 how 的对比

| how     | 保留行     | 说明               |
| ------- | ------- | ---------------- |
| 'inner' | 两表均匹配的行 | 交集，最常用           |
| 'left'  | 左表全部行   | 左外连接，右表无匹配时为 NaN |
| 'right' | 右表全部行   | 右外连接，左表无匹配时为 NaN |
| 'outer' | 两表所有行   | 全外连接，无匹配时为 NaN   |
| 'cross' | 笛卡尔积    | 两表行数之积，无需指定 on   |

```python
# 找出左表独有的行（反连接）
result = pd.merge(df_left, df_right, on="id", how="left", indicator=True)
left_only = result[result["_merge"] == "left_only"].drop(columns="_merge")

```

---

### join()

基于索引的连接，是 merge 的简化版。

| 参数       | 类型                        | 默认值    | 说明               |
| -------- | ------------------------- | ------ | ---------------- |
| other    | DataFrame / Series / list | 必填     | 要连接的对象           |
| on       | str / list                | None   | 左表中用于连接的列（右表用索引） |
| how      | str                       | 'left' | 连接方式             |
| lsuffix  | str                       | ''     | 左表重名列后缀          |
| rsuffix  | str                       | ''     | 右表重名列后缀          |
| sort     | bool                      | False  | 是否排序             |
| validate | str                       | None   | 基数校验             |

```python
df1.join(df2, how="inner")
df1.join(df2, on="id", rsuffix="_right")

```

---

### concat()

沿轴拼接多个对象（行拼接或列拼接）。

| 参数                | 类型          | 默认值     | 说明                                        |
| ----------------- | ----------- | ------- | ----------------------------------------- |
| objs              | list / dict | 必填      | 要拼接的 DataFrame 或 Series 列表                |
| axis              | int / str   | 0       | 0/'index'：纵向拼接（增加行）；1/'columns'：横向拼接（增加列） |
| join              | str         | 'outer' | 'outer'：保留所有列；'inner'：只保留共同列              |
| ignore\_index     | bool        | False   | True 则重置索引                                |
| keys              | list        | None    | 添加多层索引标识来源                                |
| levels            | list        | None    | 多层索引的层级                                   |
| names             | list        | None    | 多层索引的层名                                   |
| verify\_integrity | bool        | False   | 是否检查索引是否有重复                               |
| sort              | bool        | False   | 是否对列排序                                    |
| copy              | bool        | True    | pandas 2.0 起忽略此参数（CoW）                    |

```python
# 纵向拼接（行堆叠）
df_all = pd.concat([df1, df2, df3], ignore_index=True)

# 带来源标识
df_all = pd.concat([df_2023, df_2024], keys=["2023", "2024"])

# 横向拼接（列合并）
df_wide = pd.concat([df_a, df_b], axis=1)

# 只保留共同列
df_all = pd.concat([df1, df2], join="inner", ignore_index=True)

```

---

## 透视与重塑

### pivot\_table()

类似 Excel 数据透视表，可处理重复值（通过聚合）。

| 参数            | 类型                           | 默认值    | 说明                  |
| ------------- | ---------------------------- | ------ | ------------------- |
| data          | DataFrame                    | 必填     | 数据源（也可作为 df 方法调用）   |
| values        | str / list                   | None   | 聚合的值列；None 则聚合所有数值列 |
| index         | str / list / callable        | None   | 行方向的分组键             |
| columns       | str / list / callable        | None   | 列方向的分组键             |
| aggfunc       | str / callable / list / dict | 'mean' | 聚合函数                |
| fill\_value   | scalar                       | None   | 填充缺失单元格             |
| margins       | bool                         | False  | 是否添加"合计"行列          |
| margins\_name | str                          | 'All'  | 合计标签                |
| dropna        | bool                         | True   | 是否丢弃全为 NaN 的列       |
| observed      | bool                         | False  | 仅对 Categorical 有效   |
| sort          | bool                         | True   | 是否排序                |

```python
pd.pivot_table(
    df,
    values="sales",
    index="region",
    columns="product",
    aggfunc="sum",
    fill_value=0,
    margins=True,
)

```

---

### pivot()

将长格式转宽格式，不做聚合（值必须唯一，否则报错）。

| 参数      | 类型         | 默认值  | 说明               |
| ------- | ---------- | ---- | ---------------- |
| index   | str        | None | 行索引列名            |
| columns | str / list | 必填   | 要展开为列的列名         |
| values  | str / list | None | 值列名；None 使用其余所有列 |

```python
df.pivot(index="date", columns="city", values="temperature")

```

---

### melt()

将宽格式转长格式（逆透视）。

| 参数            | 类型         | 默认值        | 说明                             |
| ------------- | ---------- | ---------- | ------------------------------ |
| id\_vars      | str / list | None       | 保持不变的标识列                       |
| value\_vars   | str / list | None       | 要"融化"的列；None 使用除 id\_vars 外所有列 |
| var\_name     | str        | 'variable' | 变量列的列名                         |
| value\_name   | str        | 'value'    | 值列的列名                          |
| col\_level    | int / str  | None       | 多层列时使用的层级                      |
| ignore\_index | bool       | True       | 是否重置索引                         |

```python
pd.melt(
    df,
    id_vars=["id", "name"],
    value_vars=["2022_sales", "2023_sales", "2024_sales"],
    var_name="year",
    value_name="sales",
)

```

---

### stack() / unstack()

```python
# stack()：将最内层列转为行（宽转长）
df.stack()

# unstack()：将最内层行索引转为列（长转宽）
df.unstack()
df.unstack(level=0)     # 指定层级
df.unstack(level="city")

```

---

### crosstab()

交叉频率表，统计两个或多个因子的频次。

| 参数            | 类型           | 默认值   | 说明                               |
| ------------- | ------------ | ----- | -------------------------------- |
| index         | array / list | 必填    | 行分组因子                            |
| columns       | array / list | 必填    | 列分组因子                            |
| values        | array        | None  | 聚合的值；需配合 aggfunc                 |
| aggfunc       | callable     | None  | 聚合函数                             |
| rownames      | list         | None  | 行维度名称                            |
| colnames      | list         | None  | 列维度名称                            |
| margins       | bool         | False | 是否添加合计                           |
| margins\_name | str          | 'All' | 合计标签                             |
| normalize     | bool / str   | False | 标准化：True/'all'、'index'、'columns' |
| dropna        | bool         | True  | 是否丢弃全 NaN 列                      |

```python
pd.crosstab(df["gender"], df["grade"])
pd.crosstab(df["gender"], df["grade"], normalize="index")   # 行比例
pd.crosstab(df["city"], df["grade"], margins=True)

```

---

## 排序

### sort\_values()

| 参数            | 类型          | 默认值         | 说明                                                   |
| ------------- | ----------- | ----------- | ---------------------------------------------------- |
| by            | str / list  | 必填          | 排序依据的列名或列名列表                                         |
| axis          | int / str   | 0           | 0 对行排序                                               |
| ascending     | bool / list | True        | 升序/降序；列表时与 by 一一对应                                   |
| inplace       | bool        | False       | 是否原地修改                                               |
| kind          | str         | 'quicksort' | 排序算法：'quicksort'、'mergesort'（稳定）、'heapsort'、'stable' |
| na\_position  | str         | 'last'      | NaN 位置：'first' 或 'last'                              |
| ignore\_index | bool        | False       | 是否重置索引                                               |
| key           | callable    | None        | 排序前对每列应用的转换函数                                        |

```python
df.sort_values("score", ascending=False)
df.sort_values(["city", "score"], ascending=[True, False])
df.sort_values("name", key=lambda s: s.str.lower())  # 忽略大小写排序
df.sort_values("score", na_position="first")

```

---

### sort\_index()

| 参数              | 类型               | 默认值         | 说明              |
| --------------- | ---------------- | ----------- | --------------- |
| axis            | int / str        | 0           | 0 对行排序，1 对列排序   |
| level           | int / str / list | None        | 指定多层索引的排序层级     |
| ascending       | bool / list      | True        | 升序/降序           |
| inplace         | bool             | False       | 是否原地修改          |
| kind            | str              | 'quicksort' | 排序算法            |
| na\_position    | str              | 'last'      | NaN 位置          |
| sort\_remaining | bool             | True        | 多层索引时是否对剩余层级也排序 |
| ignore\_index   | bool             | False       | 是否重置索引          |
| key             | callable         | None        | 排序前转换函数         |

```python
df.sort_index()             # 按行索引升序
df.sort_index(ascending=False)
df.sort_index(axis=1)       # 按列名排序

```

---

## 窗口函数

### rolling()

滑动窗口计算。

| 参数           | 类型                       | 默认值      | 说明                                     |
| ------------ | ------------------------ | -------- | -------------------------------------- |
| window       | int / timedelta / offset | 必填       | 窗口大小（行数或时间范围）                          |
| min\_periods | int                      | None     | 窗口内最少非 NaN 数，默认等于 window               |
| center       | bool                     | False    | True 则将当前点放在窗口中心                       |
| win\_type    | str                      | None     | 窗口类型（Gaussian 等），配合 scipy              |
| on           | str                      | None     | 对哪列计算（用于 datetime 时间窗口）                |
| axis         | int / str                | 0        | pandas 2.1 废弃                          |
| closed       | str                      | None     | 区间闭合方式：'right'、'left'、'both'、'neither' |
| step         | int                      | None     | 步长（每隔 N 步计算一次）                         |
| method       | str                      | 'single' | 'table' 启用表级优化                         |

```python
df["price"].rolling(window=5).mean()          # 5 日移动平均
df["price"].rolling(window=5, min_periods=1).mean()  # 不足 5 个也计算
df["price"].rolling(window=5, center=True).mean()    # 居中窗口

# 时间窗口（需 datetime 索引）
df["price"].rolling("7D").mean()   # 过去 7 天均值

# 多列同时计算
df"price", "volume".rolling(5).sum()

```

---

### expanding()

扩张窗口（从起点到当前行，窗口不断增大）。

```python
df["price"].expanding().mean()    # 累计均值
df["price"].expanding(min_periods=5).std()  # 至少 5 个点才开始计算

```

| 参数           | 类型  | 默认值      | 说明             |
| ------------ | --- | -------- | -------------- |
| min\_periods | int | 1        | 最少行数           |
| method       | str | 'single' | 'table' 启用表级优化 |

---

### ewm()

指数加权移动（指数衰减权重）。

| 参数           | 类型                | 默认值   | 说明                        |
| ------------ | ----------------- | ----- | ------------------------- |
| com          | float             | None  | 质心，alpha = 1/(1+com)      |
| span         | float             | None  | 跨度，alpha = 2/(span+1)     |
| halflife     | float / timedelta | None  | 半衰期                       |
| alpha        | float             | None  | 平滑因子，0 < alpha <= 1；以上四选一 |
| min\_periods | int               | 0     | 最少行数                      |
| adjust       | bool              | True  | 是否使用调整型加权（处理初始值）          |
| ignore\_na   | bool              | False | 是否在计算权重时忽略 NaN            |

```python
df["price"].ewm(span=20).mean()     # EMA-20（指数移动平均）
df["price"].ewm(halflife=5).std()

```

---

### 窗口函数常用聚合

```python
roll = df["price"].rolling(20)
roll.mean()    # 移动平均
roll.sum()     # 移动求和
roll.std()     # 移动标准差
roll.min()     # 移动最小值
roll.max()     # 移动最大值
roll.var()     # 移动方差
roll.corr(df["volume"])  # 移动相关系数
roll.cov(df["volume"])   # 移动协方差
roll.apply(lambda x: x.iloc[-1] / x.iloc[0] - 1)  # 自定义（N 日涨幅）

```

---

## 性能优化

### 选择合适的数据类型

不合理的类型是内存浪费的主要来源。

```python
# 查看内存占用
df.info(memory_usage="deep")
df.memory_usage(deep=True).sum() / 1024**2  # MB

# 整数降级
df["age"] = df["age"].astype("int8")         # -128 ~ 127
df["count"] = df["count"].astype("uint16")   # 0 ~ 65535

# 浮点降级
df["ratio"] = df["ratio"].astype("float32")  # 精度约 7 位

# 字符串列转 category（低基数列效果显著）
df["city"] = df["city"].astype("category")
df["status"] = df["status"].astype("category")

```

---

### 使用 category 类型节省内存

适用于基数（唯一值数量）远小于行数的列。

```python
# 转为 category
df["region"] = df["region"].astype("category")

# 内存对比
import sys
s_str = pd.Series(["北京", "上海", "广州"] * 100000)
s_cat = s_str.astype("category")
print(s_str.memory_usage(deep=True))   # ~2.4 MB
print(s_cat.memory_usage(deep=True))   # ~0.3 MB

# category 排序
from pandas.api.types import CategoricalDtype
cat_type = CategoricalDtype(categories=["低", "中", "高"], ordered=True)
df["level"] = df["level"].astype(cat_type)
df.sort_values("level")  # 按自定义顺序排序

```

---

### eval() / query() 替代向量运算

使用 `numexpr` 引擎，对大 DataFrame 可节省内存并提速。

```python
# 普通写法（会创建中间数组）
result = df["a"] + df["b"] * df["c"]

# eval 写法（原地计算，节省内存）
result = df.eval("a + b * c")
df.eval("total = a + b * c", inplace=True)

# 引用外部变量
factor = 1.1
df.eval("score_adj = score * @factor", inplace=True)

```

适用场景：行数 > 10 万、表达式涉及多列运算时效果明显；简单操作反而因解析开销更慢。

---

### chunksize 处理大文件

```python
def process_large_csv(filepath, chunksize=50000):
    results = []
    for chunk in pd.read_csv(filepath, chunksize=chunksize):
        # 每块独立处理
        filtered = chunk[chunk["amount"] > 100]
        agg = filtered.groupby("city")["amount"].sum()
        results.append(agg)
    return pd.concat(results).groupby(level=0).sum()

```

---

### 避免 iterrows()

`iterrows()` 逐行迭代，性能极差（每行创建 Series，开销大）。

```python
# 错误示范（慢）
for idx, row in df.iterrows():
    df.at[idx, "total"] = row["price"] * row["qty"]

# 正确做法：向量化
df["total"] = df["price"] * df["qty"]

# 如必须遍历，用 itertuples（比 iterrows 快 10x 以上）
for row in df.itertuples(index=True, name="Row"):
    print(row.price, row.qty)

# 或用 apply（次优）
df["total"] = df.apply(lambda r: r["price"] * r["qty"], axis=1)

```

---

### inplace=True 的真实效果

`inplace=True` 并不总是节省内存，也不总是更快。

```python
# inplace=True 实际上仍会在内部创建新对象再赋值回去
df.drop(columns=["col"], inplace=True)
# 等效于
df = df.drop(columns=["col"])

# pandas 2.0+ CoW（写时复制）机制下，inplace 行为更一致
# 建议：明确赋值，避免依赖 inplace 的副作用
df = df.dropna().reset_index(drop=True)

```

---

### 使用 PyArrow backend（pandas 2.0+）

```python
# 创建时指定 ArrowDtype
import pyarrow as pa
df = pd.read_csv("data.csv", dtype_backend="pyarrow")

# 手动转换
df = df.convert_dtypes(dtype_backend="pyarrow")

# PyArrow 类型的优势：更低内存占用、更快 IO、原生支持 null（非 NaN）

```

---

### Copy-on-Write（pandas 2.0+ CoW 机制）

pandas 2.0 引入 CoW，2.0 起可选，3.0 起强制启用。

```python
# 启用 CoW（pandas 2.0/2.1 中需手动开启）
pd.options.mode.copy_on_write = True

# CoW 下，所有切片都是写时复制视图
subset = df"a", "b"   # 不立即复制
subset["a"] = 0            # 触发复制，不影响原 df

# 明确需要独立副本时
copy = df.copy()

# CoW 的主要影响：彻底消除 SettingWithCopyWarning
# 不再需要 df.loc[mask, "col"] = val 来避免链式赋值警告

```

---

## 常用配合库

### NumPy

```python
import numpy as np

# DataFrame -> ndarray
arr = df.to_numpy()
arr = df.values        # 旧写法，不推荐
arr = df["score"].to_numpy()

# ndarray -> DataFrame
df = pd.DataFrame(arr, columns=["a", "b", "c"])

# NumPy ufunc 直接应用于 Series
np.sqrt(df["score"])
np.log(df["price"])
np.clip(df["age"], a_min=18, a_max=65)

# NumPy 函数与 pandas 互操作
np.where(df["score"] >= 60, "pass", "fail")
np.select([df["score"] >= 90, df["score"] >= 60], ["A", "B"], default="C")

```

---

### Matplotlib / Seaborn

`df.plot()` 基于 Matplotlib，是快速可视化的便捷方式。

| 参数              | 类型              | 默认值    | 说明                                                                          |
| --------------- | --------------- | ------ | --------------------------------------------------------------------------- |
| kind            | str             | 'line' | 图表类型：'line'、'bar'、'barh'、'hist'、'box'、'kde'、'area'、'pie'、'scatter'、'hexbin' |
| x               | str             | None   | x 轴列名（scatter 等需要）                                                          |
| y               | str / list      | None   | y 轴列名                                                                       |
| figsize         | tuple           | None   | 图表尺寸 (width, height)                                                        |
| title           | str             | None   | 标题                                                                          |
| xlabel / ylabel | str             | None   | 轴标签                                                                         |
| legend          | bool            | True   | 是否显示图例                                                                      |
| grid            | bool            | False  | 是否显示网格                                                                      |
| ax              | matplotlib Axes | None   | 指定 Axes 对象                                                                  |
| subplots        | bool            | False  | 每列一个子图                                                                      |
| rot             | int             | None   | x 轴标签旋转角度                                                                   |

```python
import matplotlib.pyplot as plt

df["score"].plot(kind="hist", bins=20, title="Score Distribution")
plt.show()

df.plot(kind="scatter", x="age", y="score", figsize=(8, 5))
plt.show()

# 多列折线图
df"price", "cost".plot(figsize=(12, 4), grid=True)
plt.tight_layout()
plt.show()

# Seaborn
import seaborn as sns
sns.heatmap(df.corr(), annot=True, fmt=".2f", cmap="coolwarm")
sns.boxplot(data=df, x="city", y="score")

```

---

### SQLAlchemy

见 [SQLAlchemy完全指南](https://blog.vercanti.com/sqlalchemy-wan-quan-zhi-nan/)。

```python
from sqlalchemy import create_engine

engine = create_engine("mysql+pymysql://user:pass@localhost/db")

# 读取
df = pd.read_sql("SELECT * FROM orders WHERE status = 'paid'", con=engine)

# 写入
df.to_sql(
    name="orders_backup",
    con=engine,
    if_exists="replace",   # 'fail' / 'replace' / 'append'
    index=False,
    chunksize=1000,
    method="multi",        # 批量插入，更快
)

```

`to_sql()` 重点参数：

| 参数           | 类型                | 默认值    | 说明                                 |
| ------------ | ----------------- | ------ | ---------------------------------- |
| name         | str               | 必填     | 目标表名                               |
| con          | SQLAlchemy engine | 必填     | 数据库连接                              |
| schema       | str               | None   | 数据库 schema                         |
| if\_exists   | str               | 'fail' | 'fail'、'replace'（删表重建）、'append'    |
| index        | bool              | True   | 是否写出 df 索引                         |
| index\_label | str / list        | None   | 索引的列名                              |
| chunksize    | int               | None   | 分批写入行数                             |
| dtype        | dict              | None   | 指定各列的 SQLAlchemy 类型                |
| method       | str / callable    | None   | None（逐行）、'multi'（批量 INSERT）、或自定义函数 |

---

### PyArrow

```python
import pyarrow as pa
import pyarrow.parquet as pq

# pandas -> Arrow Table
table = pa.Table.from_pandas(df)

# Arrow Table -> pandas
df = table.to_pandas()

# 直接读写 Parquet
pq.write_table(table, "data.parquet", compression="snappy")
table = pq.read_table("data.parquet", columns=["id", "amount"])
df = table.to_pandas()

# 使用 pandas 接口（底层走 pyarrow）
df.to_parquet("data.parquet", engine="pyarrow", compression="snappy")
df = pd.read_parquet("data.parquet", engine="pyarrow")

```

---

### Dask

Dask 提供与 pandas 几乎相同的 API，支持并行和大于内存的数据集。

```python
import dask.dataframe as dd

# 读取（懒执行）
ddf = dd.read_csv("large_*.csv")
ddf = dd.from_pandas(df, npartitions=4)

# 操作（与 pandas 语法基本相同）
result = ddf[ddf["score"] > 80].groupby("city")["score"].mean()

# 触发计算
result_df = result.compute()

# 转回 pandas
df = ddf.compute()

```

---

### Polars

Polars 是 Rust 编写的高性能 DataFrame 库，与 pandas 互转方便。

```python
import polars as pl

# pandas -> Polars
pl_df = pl.from_pandas(df)

# Polars -> pandas
df = pl_df.to_pandas()

# 通过 Arrow 互转（零拷贝，更高效）
arrow_table = df_pandas.to_arrow()  # pandas 2.0+ 方法（实验性）
pl_df = pl.from_arrow(arrow_table)

```

详见 [Polars完全指南](https://blog.vercanti.com/polars-wan-quan-zhi-nan/)。

---

## 最佳实践

### 链式操作（Method Chaining）

链式写法让数据处理流程更清晰，避免中间变量。

```python
result = (
    pd.read_csv("sales.csv", parse_dates=["date"])
    .rename(columns=str.lower)
    .dropna(subset=["amount", "product"])
    .assign(
        month=lambda df: df["date"].dt.to_period("M"),
        amount_rmb=lambda df: df["amount"] * 7.2,
    )
    .query("amount > 100")
    .groupby(["month", "product"])
    .agg(total=("amount_rmb", "sum"), cnt=("amount_rmb", "count"))
    .reset_index()
    .sort_values("total", ascending=False)
)

```

链式操作中，`pipe()` 可插入自定义函数：

```python
def add_rank(df, col):
    df = df.copy()
    df["rank"] = df[col].rank(ascending=False, method="dense").astype(int)
    return df

result = df.query("score > 60").pipe(add_rank, "score")

```

---

### 避免 SettingWithCopyWarning

该警告提示你正在对切片的副本赋值，修改可能不会反映到原 df。

```python
# 错误示范（链式赋值，触发警告）
df[df["score"] > 60]["grade"] = "pass"  # 不生效

# 正确做法：用 loc 赋值
df.loc[df["score"] > 60, "grade"] = "pass"

# 需要修改子集时，显式 copy
subset = df[df["city"] == "北京"].copy()
subset["discount"] = 0.9

# pandas 2.0+ 启用 CoW 后，上述问题从根本上消除
pd.options.mode.copy_on_write = True

```

---

### 使用 pd.api.types 做类型检查

```python
from pandas.api.types import (
    is_numeric_dtype,
    is_string_dtype,
    is_datetime64_any_dtype,
    is_categorical_dtype,
    is_integer_dtype,
    is_float_dtype,
)

for col in df.columns:
    if is_numeric_dtype(df[col]):
        df[col] = df[col].fillna(0)
    elif is_string_dtype(df[col]):
        df[col] = df[col].fillna("")

```

---

### 大数据集分块处理模式

```python
import pandas as pd

def etl_pipeline(input_path, output_path, chunksize=100_000):
    first = True
    for chunk in pd.read_csv(input_path, chunksize=chunksize):
        # 处理每块
        chunk = (
            chunk
            .dropna(subset=["id", "amount"])
            .assign(amount=pd.to_numeric(chunk["amount"], errors="coerce"))
            .dropna(subset=["amount"])
        )
        # 追加写出
        chunk.to_csv(
            output_path,
            mode="w" if first else "a",
            header=first,
            index=False,
        )
        first = False

```

---

### 明确使用 copy() 避免隐式引用

```python
# 切片是视图，修改会影响原 df（pandas CoW 前的行为）
subset = df"a", "b"
subset["a"] = 0   # 可能触发警告，且行为不确定

# 明确复制
subset = df"a", "b".copy()
subset["a"] = 0   # 安全，不影响原 df

```

---

## 应用场景

### 数据清洗流水线示例

```python
import pandas as pd

def clean_user_data(filepath):
    df = (
        pd.read_csv(filepath, dtype={"phone": str})
        .rename(columns=str.strip)              # 去除列名空白
        .rename(columns=str.lower)              # 列名小写
        .drop_duplicates(subset=["user_id"])    # 去重
        .dropna(subset=["user_id", "email"])    # 删除关键字段为空的行
        .assign(
            email=lambda df: df["email"].str.strip().str.lower(),
            phone=lambda df: df["phone"].str.replace(r"\D", "", regex=True),
            age=lambda df: pd.to_numeric(df["age"], errors="coerce"),
            created_at=lambda df: pd.to_datetime(df["created_at"], errors="coerce"),
        )
        .loc[lambda df: df["age"].between(0, 150)]   # 过滤异常年龄
        .reset_index(drop=True)
    )
    return df

```

---

### 日志分析示例

```python
import pandas as pd

# 读取 Nginx 日志
df = pd.read_csv(
    "access.log",
    sep=r"\s+",
    header=None,
    names=["ip", "dash1", "user", "time", "request", "status", "size"],
    usecols=["ip", "time", "request", "status", "size"],
    engine="python",
)

df["time"] = pd.to_datetime(df["time"], format="[%d/%b/%Y:%H:%M:%S")
df["status"] = pd.to_numeric(df["status"], errors="coerce")
df["size"] = pd.to_numeric(df["size"].replace("-", "0"), errors="coerce")

# 按小时统计请求量
hourly = df.set_index("time").resample("H").agg(
    requests=("status", "count"),
    errors=("status", lambda x: (x >= 500).sum()),
    traffic_mb=("size", lambda x: x.sum() / 1024**2),
)

# 找出访问量最多的 IP
top_ips = df["ip"].value_counts().head(10)

```

---

### 时序数据处理示例

```python
import pandas as pd

df = pd.read_csv("stock.csv", parse_dates=["date"])
df = df.set_index("date").sort_index()

# 计算技术指标
df["ma5"]  = df["close"].rolling(5).mean()
df["ma20"] = df["close"].rolling(20).mean()
df["ema12"] = df["close"].ewm(span=12).mean()
df["ema26"] = df["close"].ewm(span=26).mean()
df["macd"] = df["ema12"] - df["ema26"]
df["signal"] = df["macd"].ewm(span=9).mean()

# 月度统计
monthly = df["close"].resample("M").agg(
    open="first",
    high="max",
    low="min",
    close="last",
)

# 计算月涨跌幅
monthly["return"] = monthly["close"].pct_change()

```

---

### 报表生成示例

```python
import pandas as pd

df = pd.read_excel("sales.xlsx", parse_dates=["order_date"])

# 销售汇总透视表
pivot = pd.pivot_table(
    df,
    values=["amount", "qty"],
    index=pd.Grouper(key="order_date", freq="Q"),
    columns="product_category",
    aggfunc={"amount": "sum", "qty": "sum"},
    fill_value=0,
    margins=True,
    margins_name="合计",
)

# 格式化并导出
with pd.ExcelWriter("report.xlsx", engine="openpyxl") as writer:
    pivot.to_excel(writer, sheet_name="季度销售", float_format="%.2f")
    df.groupby("region")["amount"].describe().to_excel(writer, sheet_name="区域统计")

```

---

## 常见陷阱与注意事项

### SettingWithCopyWarning

```python
# 触发警告的典型写法（链式赋值）
df[df["score"] > 60]["grade"] = "pass"  # 无效赋值，且触发警告

# 正确写法
df.loc[df["score"] > 60, "grade"] = "pass"

```

原因：`df[df["score"] > 60]` 可能返回视图也可能返回副本，pandas 无法确定，因此发出警告。

---

### Chained Indexing 问题

```python
# 危险：连续两次 []，行为不确定
df["city"]["Beijing"]   # 不推荐

# 安全：一次性用 loc 完成
df.loc["Beijing", "city"]

```

---

### inplace 不总是节省内存

```python
# 错误认知：inplace=True 会在原地修改，节省内存
df.sort_values("score", inplace=True)

# 实际：pandas 内部仍创建新对象再替换，内存峰值不变
# 另外 inplace=True 阻断 method chaining，建议放弃使用
df = df.sort_values("score")

```

---

### groupby 后 index 变化

```python
# 默认 as_index=True，分组键成为索引
g = df.groupby("city")["score"].mean()
print(g.index)   # Index(['北京', '上海', ...], name='city')

# 解决方法一：reset_index()
result = df.groupby("city")["score"].mean().reset_index()

# 解决方法二：as_index=False
result = df.groupby("city", as_index=False)["score"].mean()

# 多级索引时
result = df.groupby(["city", "year"])["score"].mean()
result.xs("北京", level="city")     # 取某级索引

```

---

### merge 后列名冲突

```python
# 两表都有 name 列，合并后自动加后缀
result = pd.merge(df_left, df_right, on="id")
# 产生 name_x, name_y

# 指定自定义后缀
result = pd.merge(df_left, df_right, on="id", suffixes=("_customer", "_order"))

# 提前重命名，避免冲突
df_right = df_right.rename(columns={"name": "order_name"})

```

---

### datetime 时区问题

```python
# 无时区 datetime（naive）
dt = pd.Timestamp("2024-01-01 08:00:00")

# 附加时区（localize）
dt_tz = dt.tz_localize("Asia/Shanghai")

# 转换时区
dt_utc = dt_tz.tz_convert("UTC")

# 时区比较：naive 与 tz-aware 不能直接比较
# 统一策略：所有 datetime 都存储为 UTC，展示时转为本地时区

# 读取时指定 UTC
df["ts"] = pd.to_datetime(df["ts"], utc=True)

# 混合时区列会被存为 object 类型，需统一处理

```

---

### 浮点数精度

```python
# 浮点数比较不可靠
0.1 + 0.2 == 0.3   # False

# 安全比较方式
import numpy as np
np.isclose(df["a"], df["b"])                    # 元素级
np.allclose(df["a"].values, df["b"].values)     # 全部

# 金融场景建议用 Decimal 或存整数（分）
# 或转为 string 再比较哈希

# pandas 浮点显示精度
pd.set_option("display.float_format", "{:.4f}".format)

```

---

## 参见

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