3 Pandas Tricks for Data Cleaning & Preparation
TL;DR · AI 摘要
Pandas 的三种高效数据清洗技巧可显著提升数据准备效率,包括声明式方法链、内存和速度优化、分组感知插补。
核心要点
- 使用 .assign()、.query() 和 .pipe() 实现声明式方法链,提升代码可读性和安全性。
- 通过 categoricals 和 vectorized string accessors 优化内存和速度。
- 使用 .transform() 进行分组感知插补,提高数据完整性。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- Pandas 数据清洗技巧
- 声明式方法链
- .assign()
- .query()
- .pipe()
- 内存和速度优化
- categoricals
- vectorized string accessors
- 分组感知插补
- .transform()
金句 / Highlights
值得收藏与分享的关键句。
数据清洗和准备占数据科学家日常工作的 80%,使用 Pandas 可提高效率。
使用 .assign()、.query() 和 .pipe() 实现更安全、可读性强的数据处理流程。
通过 categoricals 和 vectorized string accessors 优化内存使用和执行速度。
3 个 Pandas 技巧用于数据清洗与准备 - KDnuggets
publ: 2026年6月15日
- 博客热门文章
- 主题 人工智能 职业建议 计算机视觉 数据工程 数据科学 语言模型 机器学习 MLOps 自然语言处理 编程 Python SQL
- 数据集
- 活动
- 资源 快速参考指南 推荐 技术简报
- 广告
加入新闻通讯
#header end
/ad_wrapper
3 个 Pandas 技巧用于数据清洗与准备
在本文中,我们将介绍三个关键的 Pandas 技巧,以高效地清洗和准备数据:声明式方法链、通过分类和向量化字符串访问器进行内存和速度优化,以及使用 .transform() 进行组感知插补。
作者:
,KDnuggets 主编,2026年6月15日,在
Python
<div class="addthis_native_toolbox"></div>
# 引言
据估计,数据清洗和准备占据了数据科学家日常工作中多达80%的时间。由于 Pandas 是 Python 中的标准数据操作库,您的操作效率直接决定了您从原始、脏数据集到模型就绪特征的转换速度。而且,有充分的理由希望增加清洗和准备时间:这直接转化为可用于建模、分析和传达洞察的时间。
然而,许多开发人员编写的 Pandas 代码模仿了标准的 Python 循环结构,或者使用了命令式、状态突变的更新。这些方法存在几个问题:它们可能引发令人困惑的 SettingWithCopyWarning,通过冗余的复制增加 RAM 使用量,并且由于避免向量化而降低执行速度。
要编写生产级别的数据管道,您需要从基本语法过渡到惯用的 Pandas 设计模式。在本文中,我们将介绍三个关键的 Pandas 技巧,以高效地清洗和准备数据:
- 使用 .assign()、.query() 和 .pipe() 的声明式方法链
- 通过分类和向量化字符串访问器进行内存和速度优化
- 使用 .transform() 进行组感知插补
# 1. 使用 .assign()、.query() 和 .pipe() 的声明式方法链
在准备数据时,通常会执行一系列修改:清理字符串值、创建新的数学列、过滤异常值、重命名字段等。
一种天真的方法是按顺序编写这些操作,就地修改 DataFrame 或反复将其重新分配给相同的变量。这不仅使代码难以阅读和调试,而且修改切片后的 DataFrame 通常会频繁触发臭名昭著的 SettingWithCopyWarning。这个警告是 Pandas 告诉您它无法保证您是在修改副本还是内存中的原始数组缓冲区。
通过将数据清洗管道用括号括起来,您可以按顺序链接 Pandas 方法。使用 .assign() 声明新列,使用 .query() 进行行过滤,并使用 .pipe() 应用自定义函数,可以保持您的操作线性、可读,并且免受副作用的影响。
这种命令式风格逐步修改 DataFrame,存在警告警报的风险,并且使中间阶段难以隔离:
import pandas as pd
import numpy as np
# 示例原始销售数据
data = {
'sale_date': ['2026-01-01', '2026-01-02', 'invalid_date', '2026-01-04'],
'item_code': [' PROD_A ', ' PROD_B', 'PROD_C ', ' PROD_D '],
'price': [100.0, 250.0, -99.0, 150.0],
'quantity': [2, 1, 5, 3]
}
df = pd.DataFrame(data)
# 简单的多步骤清理
df['sale_date'] = pd.to_datetime(df['sale_date'], errors='coerce')
df['item_code'] = df['item_code'].str.strip()
df['total_revenue'] = df['price'] * df['quantity']
# 过滤掉无效日期和价格
df = df[df['sale_date'].notna()]
df = df[df['price'] > 0]
# 重命名列以保持一致性
df.rename(columns={'item_code': 'product_id'}, inplace=True)
print(df)在这里,我们将相同的逻辑重新组织为一个单一、连贯、从上到下的流程。我们使用自定义的辅助函数和 .pipe() 来处理自定义异常:
import pandas as pd
import numpy as np
data = {
'sale_date': ['2026-01-01', '2026-01-02', 'invalid_date', '2026-01-04'],
'item_code': [' PROD_A ', ' PROD_B', 'PROD_C ', ' PROD_D '],
'price': [100.0, 250.0, -99.0, 150.0],
'quantity': [2, 1, 5, 3]
}
df_raw = pd.DataFrame(data)
# 自定义模块化清理步骤
def clean_item_codes(df):
df['item_code'] = df['item_code'].str.strip()
return df
# 方法链式流程
cleaned_df = (
df_raw
.copy() # 防止修改原始数据
.assign(
sale_date=lambda d: pd.to_datetime(d['sale_date'], errors='coerce'),
total_revenue=lambda d: d['price'] * d['quantity']
)
.pipe(clean_item_codes)
.query("sale_date.notna() and price > 0")
.rename(columns={'item_code': 'product_id'})
)
print(cleaned_df)输出:
sale_date product_id price quantity total_revenue
0 2026-01-01 PROD_A 100.0 2 200.0
1 2026-01-02 PROD_B 250.0 1 250.0
3 2026-01-04 PROD_D 150.0 3 450.0通过将表达式包裹在 ( ... ) 中,Python 允许在不使用反斜杠的情况下进行多行链式操作。
- .assign() 接受关键字参数,其中 lambdas 接收 DataFrame 的当前状态 (d),使您能够按顺序创建或修改多个列。
- .pipe() 将中间 DataFrame 传递给外部函数。这将可重用的清理逻辑与主链分开。
- .query() 接受一个布尔表达式字符串。它比嵌套括号 (df[(df[a] > 0) & (df[b].notna())]) 更整洁,并且在内部使用 NumPy 的快速数值表达式评估器 NumExpr,运行速度更快。
这种函数模式避免了 SettingWithCopyWarning,因为它从未修改中间切片。
# 2. 使用分类和向量化字符串方法进行内存和速度优化
默认情况下,Pandas 会将包含文本的列分配为通用对象数据类型。对象列存储指向堆内存中字符串的 Python 指针,而不是连续的打包值。对于包含低基数字符串(如状态标志、城市名称或性别等重复类别的列)的大数据集,这会导致显而易见的内存占用。
此外,开发人员经常通过将 Python lambda 表达式传递给 .apply() 来应用自定义字符串修改。这迫使 Pandas 以缓慢的 Python 解释器速度逐行循环。
我们可以通过以下方式优化内存使用和执行时间:
- 将低基数字符串列转换为原生的类别数据类型 通过使用 .str 访问器,用优化的向量化字符串方法替换缓慢的 .apply() 循环
让我们通过保持文本作为对象列并使用 .apply() 清理空格来模拟清理一个大型数据集(1,000,000 行):
import pandas as pd
import numpy as np
import time
# 创建一个包含100万行低基数字符串数据的模拟数据集
n_rows = 1000000
categories = [' PENDING ', ' COMPLETED ', ' FAILED ', ' SHIPPED ']
df = pd.DataFrame({
'status': np.random.choice(categories, size=n_rows),
'val': np.random.rand(n_rows)
})
# 清理前的内存使用情况基准测试
mem_before = df['status'].memory_usage(deep=True) / (1024 ** 2)
start_time = time.time()
# 朴素清理:缓慢的Python apply循环
df['status'] = df['status'].apply(lambda x: x.strip().upper())
duration_apply = time.time() - start_time
mem_after = df['status'].memory_usage(deep=True) / (1024 ** 2)
print(f"Apply清理完成时间: {duration_apply:.4f} 秒")
print(f"状态列内存使用情况: {mem_after:.2f} MB(原始为 {mem_before:.2f} MB)")通过首先将状态列转换为类别类型,并使用向量化的 .str 访问器,我们实现了即时的速度提升并节省了大量内存:
import pandas as pd
import numpy as np
import time
n_rows = 1000000
categories = [' PENDING ', ' COMPLETED ', ' FAILED ', ' SHIPPED ']
df = pd.DataFrame({
'status': np.random.choice(categories, size=n_rows),
'val': np.random.rand(n_rows)
})
# 转换为类别类型
df['status'] = df['status'].astype('category')
# 内存使用情况基准测试
mem_category = df['status'].memory_usage(deep=True) / (1024 ** 2)
start_time = time.time()
# 直接在类别上进行向量化字符串清理
df['status'] = df['status'].cat.rename_categories(lambda x: x.strip().upper())
duration_vectorized = time.time() - start_time
print(f"向量化类别清理完成时间: {duration_vectorized:.4f} 秒")
print(f"类别状态列内存使用情况: {mem_category:.2f} MB")
print(f"加速比: {duration_apply / duration_vectorized:.2f} 倍更快")综合输出:
Apply清理完成时间: 0.1213 秒
状态列内存使用情况: 53.64 MB(原始为 55.55 MB)
向量化类别清理完成时间: 0.0003 秒
类别状态列内存使用情况: 0.95 MB
加速比: 407.83 倍更快我们将这些性能提升视为胜利。
当一个列被转换为类别类型时,Pandas 在内部将字符串编码为整数键(例如,PENDING -> 0,COMPLETED -> 1)。
- 与其存储1,000,000个字符串,Pandas存储1,000,000个小整数和一个很小的映射表,其中包含4个实际字符串类别。这将内存占用从约56 MB减少到不到1 MB。通过直接使用 .cat.rename_categories() 清理标签,Pandas只在4个唯一类别上执行字符串操作,而不是遍历1,000,000行。执行时间几乎降为零。
注意:如果你处理的是高基数文本(其中值很少重复),将其保留为类别类型不会节省内存。在这种情况下,你仍然应避免使用 .apply(),并直接在对象列上使用向量化字符串方法:df['status'].str.strip().str.upper(),这在编译的C代码中执行,而不是在Python中。
# 3. 使用 groupby() 和 .transform() 进行组感知的插值和插值
处理缺失数据是数据清洗的基本步骤。在许多情况下,用全局平均值或常数替换缺失值会引入统计偏差。例如,如果你要填补缺失的产品价格,使用所有商店产品的全局平均价格是不准确的。使用该特定产品类别的平均价格进行填补会更加精确。
一种简单的方法是遍历产品类别,计算组平均值,过滤 DataFrame,填充缺失值,然后将各组重新拼接在一起。另一种方法是使用自定义函数在 groupby().apply() 中进行操作,这会触发效率较低的 split-apply-combine 循环,扩展性较差。
优化的解决方案是将 groupby() 与 .transform() 方法结合使用。
在这里,我们使用循环或传递给 .apply() 的自定义函数来模拟填补缺失的数值价格(用 NaN 表示):
import pandas as pd
import numpy as np
import time
# 创建一个包含 100,000 个物品的模拟目录,按类别分组
n_items = 100000
categories = [f"CAT_{i}" for i in range(100)]
df = pd.DataFrame({
'category': np.random.choice(categories, size=n_items),
'price': np.random.uniform(10.0, 500.0, size=n_items)
})
# 引入 10% 的缺失价格(NaN)
nan_mask = np.random.rand(n_items) < 0.1
df.loc[nan_mask, 'price'] = np.nan
df_clunky = df.copy()
start_time = time.time()
# 使用 apply() 和自定义 lambda 进行 split-apply-combine
df_clunky['price'] = df_clunky.groupby('category')['price'].apply(lambda x: x.fillna(x.mean())).reset_index(level=0, drop=True)
duration_clunky = time.time() - start_time
print(f"基于 apply 的组填补耗时: {duration_clunky:.4f} 秒")通过利用 .transform(),我们绕过了自定义 lambda 循环,允许 Pandas 本地处理索引对齐和矢量化操作:
import pandas as pd
import numpy as np
import time
# 使用相同的设置
df_optimized = df.copy()
start_time = time.time()
# 使用 transform 的优化方法
group_means = df_optimized.groupby('category')['price'].transform('mean')
df_optimized['price'] = df_optimized['price'].fillna(group_means)
duration_opt = time.time() - start_time
print(f"基于 transform 的组填补耗时: {duration_opt:.4f} 秒")
print(f"加速比: {duration_clunky / duration_opt:.2f} 倍更快")基于 apply 的组填补耗时: 0.0224 秒
基于 transform 的组填补耗时: 0.0032 秒
加速比: 7.04 倍更快理解 .transform() 的工作原理对于编写高性能的 Pandas 代码至关重要:
- 当你运行 df.groupby('category')['price'].transform('mean') 时,Pandas 会计算每个类别的平均价格。与返回一个较小的分组汇总表不同,.transform() 会将计算的值广播回原始 DataFrame 的大小和对齐方式。它输出一个与原始数据集长度相同的序列,其中索引 i 包含该行所属组的平均值。然后我们可以使用 df['price'].fillna(group_means)。这通过干净、矢量化、索引对齐的赋值来填补缺失值。
这种模式非常灵活。你可以使用它来进行组级别的标准化(例如,减去组平均值),或者使用 df.groupby('group')['val'].transform('ffill') 按组进行前向填充缺失值。
# 总结
通过超越基础的、简单的循环结构,采用符合 Pandas 风格的设计模式,你可以构建出能够无缝从本地原型扩展到生产环境的数据准备管道。
让我们回顾一下:
- 方法链式调用用可读的、声明式的处理序列取代了脆弱的、多行的命令式修改,完全避免了 SettingWithCopyWarning。分类转换和向量化字符串方法优化了内存布局,并将字符串转换操作卸载到 C 语言速度的执行中,在低基数数据上可将 RAM 使用量减少高达 98%。使用 .transform() 的分组感知插补方法计算分组级别的统计信息,并原生地将它们重新对齐到原始索引形状,避免了缓慢的自定义分组循环。
将这些模式融入到你的日常工作中,将使你的特征工程和数据清洗过程变得快速、整洁且高度可维护。
Matthew Mayo(@mattmayo13)拥有计算机科学硕士学位和数据挖掘研究生文凭。作为 KDnuggets & Statology 的总编辑,以及 Machine Learning Mastery 的特约编辑,Matthew 致力于使复杂的数据科学概念变得易于理解。他的专业兴趣包括自然语言处理、语言模型、机器学习算法以及探索新兴的人工智能。他致力于推动数据科学社区知识的民主化。Matthew 从六岁起就开始编程了。
更多相关内容
- 365 Data Science 提供的免费 AI 驱动的面试准备工具
- 7 个 Pandas 技巧,节省你的时间
- 掌握使用 Python 和 Pandas 进行数据清洗的 7 个步骤
- 使用 Pandas 进行 NLP 任务的文本数据清洗和预处理
- 使用 Python 和 Pandas 创建自动化的数据清洗管道
- 10 个 Pandas 一行代码实现的数据清洗技巧
<hr class="grey-line"><br> <div><h3>我们推荐的 5 门免费课程</h3><br> </div>
Mailchimp for WordPress v4.13.0 - https://wordpress.org/plugins/mailchimp-for-wp/
/ Mailchimp for WordPress 插件
你可以从这里开始编辑。
如果评论已关闭。
<= 上一篇
下一篇 =>
#content end
<script type="text/javascript">kda_sid_write(kda_sid_n);</script>
最新文章
- 2026 年成为 LLM 工程师的路线图 停止在 Pandas 中编写循环:尝试 7 个更快的替代方法 使用 sktime 在 Python 中构建时间序列机器学习模型 Pandas 数据清洗与准备的 3 个技巧 将 Claude Code 与本地模型配对 3 个 NumPy 技巧提升数值性能
热门文章
- 低成本实现本地代理编程:Claude Code + Ollama + Gemma4
- 将 Claude Code 与本地模型配对
- Anthropic 的 Claude 技能构建完整指南
- 5 个有用的 Python 脚本,自动化无聊的 PDF 任务
- 7 个获取创业点子资金的最佳方式
- 10 个用于 Python Web 开发的 GitHub 仓库
- AI 工程师必须知道的 5 个 Python 概念
- 使用 sktime 在 Python 中构建时间序列机器学习模型
- 5 篇有趣论文清晰解释 LLMs
- 数据清洗与准备的 3 个 Pandas 技巧
#content_wrapper end
© 2026
Guiding Tech Media
|
关于
联系我们
广告合作
隐私政策
服务条款
发布于 2026 年 6 月 15 日
blank
不,谢谢!
/.main_wrapper
<script defer type="text/javascript" src="https://s7.addthis.com/js/300/addthis_widget.js#pubid=gpsaddthis"></script>
noptimize
/noptimize