Pandas 完全指南

最后更新: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

分享

最后更新:2026-03-27

官方文档:https://pandas.pydata.org/docs/
适用版本:pandas 2.x(2026-05-08 核实)

相关文档:SQLAlchemy完全指南 MySQL基础完全指南


目录

  1. Pandas完全指南 · 安装
  2. Pandas完全指南 · 核心数据结构
  3. Pandas完全指南 · 数据读写
  4. Pandas完全指南 · 数据查看
  5. Pandas完全指南 · 数据选取
  6. Pandas完全指南 · 数据清洗
  7. Pandas完全指南 · 数据变换
  8. Pandas完全指南 · 字符串操作
  9. Pandas完全指南 · 日期时间操作
  10. Pandas完全指南 · 分组聚合
  11. Pandas完全指南 · 合并与连接
  12. Pandas完全指南 · 透视与重塑
  13. Pandas完全指南 · 排序
  14. Pandas完全指南 · 窗口函数
  15. Pandas完全指南 · 性能优化
  16. Pandas完全指南 · 常用配合库
  17. Pandas完全指南 · 最佳实践
  18. Pandas完全指南 · 应用场景
  19. Pandas完全指南 · 常见陷阱与注意事项

安装

# 基础安装
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]"

版本确认:

import pandas as pd
print(pd.__version__)   # 例:2.2.0

核心数据结构

Series

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

创建 Series

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 是否所有值唯一
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

# 从字典(最常用)
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() 每列内存占用(字节)
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 或文本文件。

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'
# 分块读取大文件
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)。

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)
# 读取多个 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 文件或字符串。

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' 压缩格式
# 读取 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)。

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 等)
# 只读取部分列(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)。

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 分块写出
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) 冻结首行
# 多 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' 压缩格式
# 输出 JSONL 格式(常用于流式处理)
df.to_json("output.jsonl", orient="records", lines=True, force_ascii=False)

数据查看

head() / tail() / sample()

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()

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

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 使用最频繁的核心章节。

[] 基础选取

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

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

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

注意:[] 用于行切片时是基于位置(整数)或标签,行为依索引类型而定,容易混淆,建议优先使用 loc / iloc


loc[]

基于标签(行索引名和列名)选取数据。

# 单行
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,不含末端)。

# 单行
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 更快(无需额外开销)。

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] 行位置, 列位置 基于整数位置的单值访问

布尔索引

# 单条件
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()

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

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 的额外参数

多条件过滤总结

# 方法一:布尔索引(最通用)
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()

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 个缺失值
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 是否原地修改
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:标记所有重复
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 是否重置索引
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':出错时返回原数据
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' 降级类型
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 是否优先解析为日在前的格式
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' 在键不存在时报错
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 多层列时,将索引插入的层级
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 是否检查索引唯一性
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 的关键字参数
# 对每列应用
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()

# 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 加速,避免使用循环。

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(不修改原数据),适合链式操作。

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 作为参数,可引用刚新建的列

列的增删改

# 新增列
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 则作为字面字符串匹配
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 的填充值
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 是否使用正则
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 去除空白
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 是否使用正则
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: 正则 返回所有匹配列表
# 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()

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完全指南 · 类型转换 章节中的参数表。

dt 访问器属性

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

# 先转换为 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()

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

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 起始偏移
# 设置时间索引
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' 端点包含
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 的组
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 - - 传递给函数的关键字参数
# 单函数
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 - - 关键字参数
# 组内标准化(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 行
# 保留组内记录数 >= 10 的组
df.groupby("city").filter(lambda g: len(g) >= 10)

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

apply() 在 GroupBy 上的用法

# 每组取 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 是否按连接键排序结果
# 基本用法
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
# 找出左表独有的行(反连接)
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 基数校验
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)
# 纵向拼接(行堆叠)
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 是否排序
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 使用其余所有列
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 是否重置索引
pd.melt(
    df,
    id_vars=["id", "name"],
    value_vars=["2022_sales", "2023_sales", "2024_sales"],
    var_name="year",
    value_name="sales",
)

stack() / unstack()

# 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 列
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 排序前对每列应用的转换函数
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 排序前转换函数
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' 启用表级优化
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()

扩张窗口(从起点到当前行,窗口不断增大)。

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
df["price"].ewm(span=20).mean()     # EMA-20(指数移动平均)
df["price"].ewm(halflife=5).std()

窗口函数常用聚合

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 日涨幅)

性能优化

选择合适的数据类型

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

# 查看内存占用
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 类型节省内存

适用于基数(唯一值数量)远小于行数的列。

# 转为 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 可节省内存并提速。

# 普通写法(会创建中间数组)
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 处理大文件

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,开销大)。

# 错误示范(慢)
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 并不总是节省内存,也不总是更快。

# 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+)

# 创建时指定 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 起强制启用。

# 启用 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

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 轴标签旋转角度
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完全指南

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

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,支持并行和大于内存的数据集。

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 互转方便。

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完全指南


最佳实践

链式操作(Method Chaining)

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

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() 可插入自定义函数:

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。

# 错误示范(链式赋值,触发警告)
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 做类型检查

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("")

大数据集分块处理模式

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() 避免隐式引用

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

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

应用场景

数据清洗流水线示例

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

日志分析示例

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)

时序数据处理示例

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()

报表生成示例

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

# 触发警告的典型写法(链式赋值)
df[df["score"] > 60]["grade"] = "pass"  # 无效赋值,且触发警告

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

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


Chained Indexing 问题

# 危险:连续两次 [],行为不确定
df["city"]["Beijing"]   # 不推荐

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

inplace 不总是节省内存

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

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

groupby 后 index 变化

# 默认 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 后列名冲突

# 两表都有 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 时区问题

# 无时区 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 类型,需统一处理

浮点数精度

# 浮点数比较不可靠
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完全指南
SQLAlchemy完全指南
FastAPI完全指南

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog