10个内置在 Pandas 中却常被忽略的向量化操作

简介: 本文精选10个高效Pandas写法,助你告别低效`apply()`和冗余循环:用`np.select()`多条件赋值、`.assign()`链式构建列、`pd.cut()`/`qcut()`智能分箱、`.melt()`/`.pivot_table()`一键重塑、`.query()`可读筛选、`.transform()`免merge聚合、`to_datetime(coerce=True)`鲁棒解析、`.explode()`展开列表、向量化`.str`方法等,全面提升代码性能与可维护性。

Pandas 代码写得越多,越容易陷入一种惯性:用 apply() 逐行处理,用循环拼接结果,用 groupbymerge 绕一大圈完成本可以一行解决的操作。代码能跑结果正确,但行数膨胀、性能也大打折扣,审查时也让人读得费力。

Pandas 本身内置了大量面向列操作的方法,覆盖条件赋值、数据分箱、格式转换、字符串处理等常见场景,只是在日常使用中很容易被忽略。翻阅 Kaggle 高分方案和生产级数据管道的源码后会发现,那些看起来简洁的一行代码并非技巧,而是对库本身设计意图的理解。

本文整理了10个这样的写法,每个都附带常见的冗长版本作为对照。

1、 用 np.select() 替代嵌套的 if/else来创建多条件列

创建条件列最常见的写法是 apply() 加自定义函数。能跑,但在大型 DataFrame 上慢得肉眼可见。

常见写法:

def categorize(row):
    if row['score'] >= 90:
        return 'A'
    elif row['score'] >= 80:
        return 'B'
    elif row['score'] >= 70:
        return 'C'
    else:
        return 'F'

df['grade'] = df.apply(categorize, axis=1)

换一种方式:

import numpy as np

conditions = [df['score'] >= 90, df['score'] >= 80, df['score'] >= 70]
df['grade'] = np.select(conditions, ['A', 'B', 'C'], default='F')

np.select() 是向量化操作,一次性处理整个数组而非逐行循环。在百万行的 DataFrame 上,比 apply() 快50到100倍并不罕见。可读性更好只是附带的好处。

2、 链式调用 .assign()构建列,不打断代码流

写过一堆 df['new_col'] = ... 赋值语句堆在一起的人,换用 .assign() 之后代码结构会清晰很多。

# 用assign替代三行独立的赋值语句...
df = (
    df.assign(
        full_name=lambda x: x['first'] + ' ' + x['last'],
        email_domain=lambda x: x['email'].str.split('@').str[1],
        is_active=lambda x: x['last_login'] > '2025-01-01'
    )
)

.assign() 返回新的 DataFrame,天然适合方法链。每个 lambda 接收的是链中当前状态的 DataFrame,同一次调用里后面的列可以引用前面的列。特征工程管道中一次性构建五六个派生列时尤其实用。

3、 用 pd.cut() 和 pd.qcut()对连续数据分箱

对连续数据做分箱,过去的做法是手写条件判断。Pandas 内置了两个专用函数:

# 等宽分箱(固定区间)
df['age_group'] = pd.cut(df['age'], bins=[0, 18, 35, 50, 65, 100],
                         labels=['Teen', 'Young Adult', 'Mid', 'Senior', 'Elder'])

# 等频分箱(每个箱中的记录数相同)
df['income_quartile'] = pd.qcut(df['income'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])

两者的差异比多数人意识到的更关键。pd.cut() 按固定宽度划分区间,适合年龄段这类有明确范围的场景;pd.qcut() 按观测值数量均分,在机器学习或统计分析中构建均衡分组时正好对应需求。

4、 用 .melt() 和 .pivot_table()一步完成数据重塑

宽格式与长格式之间的转换,花了远比愿意承认的更久才摸清楚。

宽转长:

df_long = df.melt(
    id_vars=['student'],
    value_vars=['math', 'science', 'english'],
    var_name='subject',
    value_name='score'
)

长转宽:

df_wide = df_long.pivot_table(
    index='student',
    columns='subject',
    values='score',
    aggfunc='mean'
)

两者组合大约覆盖了80%的数据重塑场景。.melt() 将宽 DataFrame 拆成长格式,.pivot_table() 反向操作的同时允许在透视过程中做聚合。给 Seaborn 喂数据或搭报表仪表板时,熟练掌握它们能省下不少时间。

5、 用增强版 .describe() 一行完成 DataFrame 概要分析

.describe() 人人都知道,但通过自定义百分位数可以将覆盖范围扩展到所有列类型:

df.describe(include='all', percentiles=[.01, .05, .25, .5, .75, .95, .99])

几乎每个新数据集上还会运行另一个操作——一次性获取所有字符串列中频率最高的前3个值:

df.select_dtypes(include='object').apply(lambda col: col.value_counts().head(3))

初始数据探索时在 .info().describe() 之后紧接着跑一次,异常的分类值很快就会暴露出来。需要更系统的分析工作流时,ydata-profiling(原 pandas-profiling)一个函数调用就能生成完整的 HTML 报告。

6、 用 .query() 做可读的类 SQL 筛选

过去写筛选条件一直是下面这种风格:

result = df[(df['age'] > 25) & (df['city'] == 'Delhi') & (df['salary'] > 50000)]

满屏的括号和 &,容易写错,code review 时读起来也累。.query() 的写法接近原生 SQL:

result = df.query("age > 25 and city == 'Delhi' and salary > 50000")

外部变量用 @ 前缀引用:

min_salary = 50000
result = df.query("salary > @min_salary")

.query() 在 numexpr 可用时会在底层调用它,对大数据集上的复杂条件有加速效果。不过真正让人坚持用它的原因是,三个月后回来读代码时条件表达一目了然。

7、 用 .transform() 替代 groupby + merge

生产代码里经常出现下面的模式,写法总是比必要的复杂:

# 三行代码加一次merge来获取部门平均值
avg_salary = df.groupby('department')['salary'].mean().reset_index()
avg_salary.columns = ['department', 'dept_avg_salary']
df = df.merge(avg_salary, on='department')

.transform() 一行就够:

df['dept_avg_salary'] = df.groupby('department')['salary'].transform('mean')

.transform() 把组级别的计算结果广播回原始索引,每行直接拿到所属组的聚合值,不需要任何 merge。sumstdcountrank、自定义 lambda 都适用。

8、 用 pd.to_datetime()配合 errors='coerce' 自动解析混乱日期

现实数据里日期格式几乎不会是干净的——同一列混着 "March 5, 2024""2024-03-05""05/03/24" 是常态。手动逐条处理并不现实。

df['date'] = pd.to_datetime(df['date_string'], errors='coerce', infer_datetime_format=True)

errors='coerce' 把无法解析的日期转为 NaT(Not a Time)而不是抛出异常,随后可以检查失败的记录:

failed = df[df['date'].isna() & df['date_string'].notna()]
print(f"{len(failed)} dates couldn't be parsed")

拿到干净的 datetime 列之后,用 .dt 访问器批量提取时间特征:

df = df.assign(
    year=df['date'].dt.year,
    month=df['date'].dt.month,
    day_of_week=df['date'].dt.day_name(),
    is_weekend=df['date'].dt.dayofweek.ge(5)
)

pd.to_datetime() 覆盖了90%的日期清洗场景。格式极端混乱时可能需要 dateutil 或手写解析逻辑,但那种情况远比想象中少见。

9、 用 .explode() 展开单元格中的列表

JSON API 或 NoSQL 导出的数据中,列表值嵌在 DataFrame 单元格里是常见现象。以前要把它们拆开,得写一段不短的循环。

df = pd.DataFrame({
   
    'user': ['Alice', 'Bob'],
    'skills': [['Python', 'SQL', 'Spark'], ['Java', 'Scala']]
})

df_exploded = df.explode('skills')

.explode() 从 Pandas 0.25 开始引入,列表中的每个元素单独成行,其余列自动复制。反向操作——将行折叠回列表——用 .groupby().agg(list) 即可。

10、 用向量化字符串方法替代字符串 apply()

字符串操作是 Pandas 中最容易踩的性能坑。遇到字符串处理就条件反射地写 apply(),但 .str 下有一整套向量化方法,执行速度快得多。

慢速写法:

df['clean_name'] = df['name'].apply(lambda x: x.strip().lower().replace(' ', '_'))

快速写法:

df['clean_name'] = df['name'].str.strip().str.lower().str.replace(' ', '_', regex=False)

.str 访问器覆盖了 .contains().extract().findall().split().pad().zfill() 等数十种方法。百万行数据集上全面弃用 apply() 可以换来5到10倍的速度差距。

总结

以上10个写法的共同点在于:放弃逐行处理的思路,转向列级别的向量化操作。这不只是代码风格的偏好,而是 Pandas 底层基于 NumPy 数组设计的必然结果——顺着库的设计方向写,代码自然会更短、更快、更易维护。

如果在数据规模上遇到 Pandas 的性能天花板,Polars 是目前最值得评估的替代方案——基于 Rust 实现,在不少工作负载下有明显的速度优势。

https://avoid.overfit.cn/post/b6224e5b1351454695bad537b59edb26

by Aashish Kumar

目录
相关文章
|
小程序
云开发(微信-小程序)笔记(十四)---- 收藏,点赞(上)
云开发(微信-小程序)笔记(十四)---- 收藏,点赞(上)
388 0
|
机器学习/深度学习 Python
pandas将dataframe列中的list转换为多列
在应用机器学习的过程中,很大一部分工作都是在做数据的处理,一个非常常见的场景就是将一个list序列的特征数据拆成多个单独的特征数据。
609 0
|
4月前
|
编解码 数据可视化 BI
Python 实现 Excel 数据可视化:柱状图制作教程
本文以运营人员小张的痛点切入,介绍如何用Python(pandas+matplotlib+openpyxl)自动化生成Excel销售柱状图。涵盖读取数据、绘制普通/堆叠/横向柱状图、美化样式、批量处理多文件、嵌入图表到Excel等实用技巧,助你将半小时手工操作压缩至十秒,实现高效、统一、可复用的数据可视化。(239字)
631 1
|
BI 索引 Python
python XlsxWriter模块execl文档生成详解(2)
python XlsxWriter模块execl文档生成详解(2)
python XlsxWriter模块execl文档生成详解(2)
|
2月前
|
消息中间件 监控 Java
【Java并发编程】Java虚拟线程与平台线程的区别、虚拟线程调度、适用/不适用场景、在Spring Boot中的集成(2026高频)(附《思维导图》+《面试高频考点清单》)
Java虚拟线程是JDK 21正式推出的轻量级并发方案,由JVM用户态调度,单线程仅占几百字节内存,支持百万级并发。它通过“M:N”调度模型与自动挂载/卸载机制,彻底解决传统平台线程在IO密集型场景下的资源瓶颈与阻塞浪费问题,让同步编程轻松承载高并发。
|
4月前
|
人工智能 JavaScript 中间件
复刻字节 AI 开发流:实践 Node.js 通用脚手架
揭秘字节内部 AI 开发流:不是用 AI 写代码,而是“训练 AI 写代码”。从混乱到高效,只靠一套可复用的规则与反馈机制,让 AI 越用越聪明、代码越写越稳。附完整 Node.js 实战脚手架,教你打造真正可进化的 AI 开发体系。
412 7
|
4月前
|
JavaScript 前端开发 Java
事件与回调的协奏:写给开发者的Node.js运行机理漫谈
Node.js是基于V8引擎的JavaScript运行时,实现JS从浏览器沙箱到服务器端的“越狱”。它通过libuv异步I/O、事件循环、多线程/多进程等机制,支撑高并发;融合Buffer、Stream、C++扩展、AsyncLocalStorage等能力,构建高性能、全栈可用的现代运行环境。(239字)
392 1
|
4月前
|
人工智能 编解码 数据可视化
豆包怎么导出成图片
本文分析2025–2026年豆包“导出图片难”这一高频痛点,揭示其在流程图、聊天记录、学习笔记等场景中因缺乏原生导出功能导致的模糊、截断、失真等问题;通过与ChatGPT、Claude等竞品对比,指出豆包在分辨率、格式保留、易用性上的短板;并提供基于浏览器插件的轻量级结构化解决方案——支持一键导出高清PNG/JPG,兼容复杂内容(LaTeX、表格、流程图),本地处理、安全高效。(239字)
|
4月前
|
人工智能 IDE 测试技术
AI Agent下半场:比模型更卷的是Skill生态
2026年,大模型正从“技术壁垒”变为“基础设施”,竞争焦点转向Agent落地能力。MCP协议已成事实标准,月下载9700万次;Skill生态则将测试、开发等经验工程化封装,实现能力复用与可持续演进——真正的分水岭,不在模型,而在如何让AI把事干成。
|
4月前
|
人工智能 物联网 API
以一杯奶茶为例,带你读懂这35个大模型核心概念
用“奶茶制作”类比大模型核心概念,生动解析数据集、预训练、Token、Prompt、RLHF、RAG、LoRA、多模态、AI Agent等40+术语,轻松理解技术本质,告别名词焦虑!
570 3