在数据科学和日常的数据分析工作中,处理缺失数据是我们最常面临的挑战之一。真实世界的数据很少是完美的,就像生活一样,总会有缺失。当我们使用 Python 的 Pandas 库进行数据预处理时,如何优雅且高效地填补这些空白(NaN 值),往往决定了后续分析的准确性。
在这篇文章中,我们将深入探讨如何使用 Pandas 的 fillna() 方法,特别是在多列数据中实现“原地”操作。你可能会问,什么是“原地”?为什么它很重要?我们将通过实战代码和最佳实践,带你从原理到应用全面掌握这一技能。我们将一起探索如何用固定值、统计量(如均值、众数)来填充数据,并在此过程中优化你的代码性能,同时结合 2026 年最新的 AI 辅助开发理念,看看这些基础操作如何融入现代化的数据工程管道。
为什么原地(In Place)操作很重要?
在开始写代码之前,让我们先理解一个概念:原地操作。
通常情况下,Pandas 的许多操作都会返回对象的副本。这意味着如果你执行 INLINECODE663de125,Pandas 实际上是在内存中创建了一个全新的 DataFrame,填充了数据,然后你需要把它重新赋值给 INLINECODEe2e10d36 变量。如果你的数据集非常庞大(比如数百万行),这种频繁的内存复制会显著降低性能并消耗宝贵的内存资源。
而“原地”操作,即设置参数 inplace=True,会直接在现有的对象上修改数据,而不创建新的副本。这对于处理大规模数据集时的性能优化至关重要。然而,在 2026 年的云原生架构下,这个选择也变得更加微妙,我们稍后会深入讨论这一点。
准备工作:构建我们的实验数据
为了演示不同场景下的填充策略,让我们首先创建一个包含缺失值(NaN)的示例 DataFrame。这将帮助我们直观地看到代码修改前后的对比。
我们使用 INLINECODE5042900e 来生成缺失值,并用 INLINECODE2fcdc916 来构建数据结构。
# 导入所需的库
import pandas as pd
import numpy as np
# 设置随机种子以保证结果可复现
np.random.seed(42)
# 创建一个包含 NaN 值的示例 DataFrame
# 这里我们模拟了一个包含计数、名称和类别的数据集
dataframe = pd.DataFrame({
‘Count‘: [1, np.nan, np.nan, 4, 2, np.nan, np.nan, 5, 6],
‘Name‘: [‘Geeks‘, ‘for‘, ‘Geeks‘, ‘a‘, ‘portal‘, ‘for‘, ‘computer‘, ‘Science‘, ‘Geeks‘],
‘Category‘: list(‘ppqqrrsss‘)
})
# 打印原始数据框
print("原始数据集:")
display(dataframe)
在这个数据集中,Count 列包含了一些我们需要处理的“空洞”。接下来,我们将尝试不同的方法来填补它们。
场景一:使用字典为不同列指定特定的填充值
有时候,我们不想用同一个值填充所有列。例如,你可能想把数值列的缺失值填为 0,而把文本列的缺失值填为 "Unknown"。虽然本例主要关注数值列,但掌握字典映射是处理复杂数据清理的关键技巧。
我们可以通过定义一个字典,将列名映射到想要填充的常量值,然后传递给 value 参数。
# 重建示例数据框
dataframe = pd.DataFrame({
‘Count‘: [1, np.nan, np.nan, 4, 2, np.nan, np.nan, 5, 6],
‘Name‘: [‘Geeks‘, ‘for‘, ‘Geeks‘, ‘a‘, ‘portal‘, ‘for‘, ‘computer‘, ‘Science‘, ‘Geeks‘],
‘Category‘: list(‘ppqqrrsss‘)
})
# 定义一个填充字典:键为列名,值为填充内容
# 这里我们计划将 Count 列的缺失值统一填充为 10
constant_values = {‘Count‘: 10}
# 执行填充操作
dataframe.fillna(value=constant_values, inplace=True)
# 打印填充后的数据框
print("使用固定值 10 填充后:")
display(dataframe)
应用场景分析:这种方法非常适合当你有明确的业务规则时。例如,“如果销售额缺失,默认为 0”。这种方法的局限性在于,它是静态的,不会根据数据的分布情况自动调整。
场景二:使用均值填充——让数据更平滑
在数据统计分析中,如果数据缺失,用该列的平均值来填充是一个非常经典的方法。这样做可以保持数据的整体统计特征(如总和)不会发生剧烈波动。
让我们看看如何结合 INLINECODEf7cb34ab 方法和 INLINECODEd7564794 来实现原地填充。
# 重建示例数据框,确保我们是从原始状态开始
dataframe = pd.DataFrame({
‘Count‘: [1, np.nan, np.nan, 4, 2, np.nan, np.nan, 5, 6],
‘Name‘: [‘Geeks‘, ‘for‘, ‘Geeks‘, ‘a‘, ‘portal‘, ‘for‘, ‘computer‘, ‘Science‘, ‘Geeks‘],
‘Category‘: list(‘ppqqrrsss‘)
})
# 计算平均值并直接进行原地填充
# dataframe[‘Count‘].mean() 会自动忽略 NaN 值计算均值
mean_val = dataframe[‘Count‘].mean()
dataframe.fillna(mean_val, inplace=True)
print(f"计算得到的平均值为: {mean_val:.2f}")
print("使用平均值填充后:")
display(dataframe)
深入理解:为什么这很有效?假设 Count 代表用户的每日登录次数。如果某天的数据丢失了,用该用户的历史平均水平来代替,通常比直接填 0 或直接删除该行数据对模型的影响更小。这是一种平滑处理,能够防止数据分布产生畸变。
场景三:使用众数填充——处理离散数据
除了平均值,众数也是一种强有力的填充策略,特别是在处理分类数据或离散数值时。众数是指数据集中出现频率最高的值。
想象一下,如果我们的数据是关于“鞋码”或“颜色”,平均值就没有意义了(你不可能有 5.3 号的鞋子)。这时,众数是最佳选择。
下面的代码展示了如何计算众数并填充。注意,INLINECODEef752029 可能会返回多个值(如果并列第一),所以我们通常取索引 INLINECODE775ac378。
# 重建示例数据框,调整 Count 数据以使其有明显的众数
# 这里 1 出现了多次,我们期望众数为 1
dataframe = pd.DataFrame({
‘Count‘: [1, np.nan, np.nan, 1, 2, np.nan, np.nan, 5, 1],
‘Name‘: [‘Geeks‘, ‘for‘, ‘Geeks‘, ‘a‘, ‘portal‘, ‘for‘, ‘computer‘, ‘Science‘, ‘Geeks‘],
‘Category‘: list(‘ppqqrrsss‘)
})
# 获取众数,mode() 返回的是 DataFrame,所以我们取第一行第一列的值
mode_val = dataframe[‘Count‘].mode()[0]
# 使用 Mode() 函数结合 fillna 来填充值
dataframe.fillna(mode_val, inplace=True)
print(f"计算得到的众数值为: {mode_val}")
print("使用众数填充后:")
display(dataframe)
2026 视角:AI 原生开发中的数据清洗
作为一名在这个领域深耕多年的开发者,我必须向你分享我们在 2026 年如何看待这些基础操作。虽然 INLINECODE91e5ac83 的语法没有变,但我们编写和维护这些代码的方式已经发生了革命性的变化。现在,当我们面对一个新的数据集时,我们不再是打开文档查阅 INLINECODE308351fa 的参数,而是直接与我们的“结对编程伙伴”——AI 编程助手(如 GitHub Copilot、Cursor 或 Windsurf)对话。
AI 辅助的代码生成与审查
你可能会这样问你的 AI 助手:“嘿,帮我看看这个 DataFrame,对于所有的数值列,如果缺失率超过 5%,就用中位数填充,否则用 0 填充,并且我希望代码运行得尽可能快。”
AI 不仅能生成代码,还能帮我们检查边界情况。在我们最近的一个金融风控项目中,AI 提醒我:对数正态分布的金额数据不应该用均值填充,而应该用中位数,否则会人为放大资产规模。这就是 Vibe Coding(氛围编程) 的魅力——我们专注于描述业务逻辑和意图,让 AI 处理底层实现的繁琐细节。
进阶技巧:处理多列的复杂策略与性能优化
在实际工作中,你经常需要一次性处理多列,且每列的策略可能不同。让我们把前面的知识结合起来,看一个更复杂的例子。
假设我们有一个新的数据集,其中 INLINECODEc8def348(年龄)缺失用均值填充,而 INLINECODEe60374a6(薪资)缺失用中位数填充。我们可以构建一个包含计算结果的字典来实现这一点。
# 创建一个更复杂的模拟数据集
df_complex = pd.DataFrame({
‘Name‘: [‘Alice‘, ‘Bob‘, ‘Charlie‘, ‘David‘, ‘Eva‘],
‘Age‘: [25, np.nan, 30, np.nan, 22],
‘Salary‘: [50000, 60000, np.nan, 45000, np.nan],
‘Department‘: [‘HR‘, ‘IT‘, ‘IT‘, ‘Sales‘, ‘HR‘]
})
print("处理前的复杂数据集:")
display(df_complex)
# 定义填充策略字典
# 键是列名,值是要填充的具体数值或计算结果
fill_values = {
‘Age‘: df_complex[‘Age‘].mean(), # 年龄用平均值
‘Salary‘: df_complex[‘Salary‘].median(), # 薪资用中位数(抗噪能力更强)
‘Department‘: ‘Unknown‘ # 部门用默认值
}
# 执行原地填充
df_complex.fillna(value=fill_values, inplace=True)
print("
应用多策略填充后:")
display(df_complex)
从脚本到管道:云原生与不可变性
虽然 INLINECODE91e39fd4 在单机脚本中很有用,但在 2026 年的现代云原生数据架构中(例如使用 Polars 或 Pandas on Ray),我们实际上开始减少对 INLINECODEb1a0591a 的依赖。为什么?因为现代数据流更倾向于不可变的数据管道。
在一个 Serverless 的数据清洗函数中,输入数据通常是不可变的流。我们更倾向于写出 INLINECODE51a677e5 这样的链式操作,因为它天然支持并行处理,且更容易在分布式系统中进行错误回滚和调试。但这并不意味着 INLINECODE66bd994f 没用了,在内存受限的边缘计算设备上进行本地数据预处理时,它依然是我们的首选。
让我们看一个结合了现代类型提示和链式调用的“2026 风格”代码示例,这在我们的 CI/CD 管道中非常常见:
import pandas as pd
import numpy as np
from typing import Dict, Any, Callable
def smart_fillna_pipeline(df: pd.DataFrame, strategies: Dict[str, Any]) -> pd.DataFrame:
"""
2026年风格的数据清洗管道:类型安全、可组合、无副作用。
参数:
df: 原始数据框
strategies: 字典,定义每列的填充策略,支持 lambda 表达式
返回:
清洗后的新数据框
"""
# 我们不修改原数据,而是返回一个新的副本,便于并发处理
# 使用字典推导式动态计算填充值
fill_values = {}
for col, strategy in strategies.items():
if callable(strategy):
# 如果策略是函数(如 mean, median),则执行它
fill_values[col] = strategy(df[col])
else:
# 否则直接使用常量
fill_values[col] = strategy
return df.fillna(value=fill_values)
# 模拟实时数据流
data_stream = pd.DataFrame({
‘sensor_temp‘: [22.5, np.nan, 23.1, np.nan, 21.0],
‘status‘: [‘ok‘, ‘ok‘, ‘error‘, np.nan, ‘ok‘]
})
# 定义策略:温度用中位数(抗噪),状态用前向填充的模拟(这里简化为常量演示)
cleaning_strategies = {
‘sensor_temp‘: lambda x: x.median(),
‘status‘: ‘unknown‘ # 简化处理,实际 ffill 需要在 method 参数中设置
}
# 执行管道
cleaned_data = smart_fillna_pipeline(data_stream, cleaning_strategies)
print("现代化处理结果:")
print(cleaned_data)
常见陷阱与生产级建议
在我们结束之前,我想分享几个在生产环境中使用 fillna 时容易遇到的坑,以及我们如何利用现代工具来解决它们。
- 链式赋值警告:你可能会尝试写像 INLINECODE2f903554 这样的代码。但请注意,如果 INLINECODE353db0c9 返回的是一个视图的副本,这可能会导致 INLINECODE5d7358ac。更安全的做法是直接在 DataFrame 对象上操作,如 INLINECODEb87c5a35。
- 数据泄漏风险:在使用像 INLINECODEb99e25b4 或 INLINECODEd97c6c66 这样的统计量进行填充时,如果是在交叉验证或时间序列预测中,你必须非常小心。不要在拆分数据集之前计算全局均值来填充测试集,这会导致数据泄漏。在 2026 年,我们通常会使用 INLINECODE7b07c9e4 的 INLINECODEe120e6fa 配合
SimpleImputer来确保填充操作只发生在训练集上,并严格应用到测试集上。
- 监控与可观测性:当你的代码在云端自动运行时,如何知道填充是否合理?我们建议在填充后添加简单的断言或日志记录。例如,记录填充了多少个 NaN,或者填充后的均值是否发生了剧烈偏移。
总结与后续步骤
今天我们一起深入探讨了 Pandas 中 INLINECODE22680583 方法的多种用法。从最简单的固定值填充,到利用统计量(均值、中位数、众数)进行智能填充,再到利用 INLINECODE1356d024 进行性能优化,最后展望了 AI 时代的开发范式。
掌握这些技巧后,你将能够更自信地面对脏数据。我们不仅要让代码跑通,更要让它跑得快、跑得稳,符合现代工程的标准。
下一步建议:
尝试在你自己的数据集上应用这些方法。你可以结合数据可视化工具(如 Matplotlib 或 Seaborn),在填充前后绘制数据的分布图,直观地感受不同的填充策略对数据分布的影响。这将帮助你根据具体业务场景选择最合适的填充策略。同时,不妨试着把你的数据清洗逻辑封装成一个函数,看看 AI 能否帮你优化它,或者尝试使用 Polars 库来处理百万级以上的数据,感受一下现代计算引擎的速度。