PySpark 处理 Null 和 NA:实用清洗秘籍
Updated on
Null 如果不处理,会悄悄破坏指标计算、关联(join)以及导出结果;而随手调用 dropna 又可能悄悄删掉很多有价值的行。团队需要一套明确的模式,在 PySpark 中有意识地检查、填充、删除或安全比较 null。
PySpark 自带的空值处理工具——fillna、dropna、na.replace、coalesce 以及空安全比较——可以让数据流水线更可控、更透明。
想在不写代码的前提下,从 Python Pandas Dataframe 快速创建数据可视化?
PyGWalker 是一个基于可视化的 Python 探索式数据分析库。PyGWalker (opens in a new tab) 能够简化你在 Jupyter Notebook 中的数据分析和可视化流程,把 pandas dataframe(以及 polars dataframe)变成类似 tableau 的可视分析界面。
快速决策指南
| 目标 | API | 说明 |
|---|---|---|
| 填充默认值 | fillna | 按列传 dict;注意类型匹配 |
| 丢弃稀疏行 | dropna | 控制 how、thresh、subset |
| 替换哨兵值 | na.replace | 替换占位字符串或数字 |
| 取第一个非 null | coalesce | 按优先级合并多列 |
| 空安全相等比较 | <=> 或 eqNullSafe | 将 null 与 null 视为相等 |
初始化
from pyspark.sql import SparkSession, functions as F
spark = SparkSession.builder.appName("null-handling").getOrCreate()
df = spark.createDataFrame(
[
(1, "Alice", None, None),
(2, "Bob", "basic", 0),
(3, None, "premium", None),
],
"id INT, name STRING, tier STRING, score INT",
)检查 null 分布模式
null_stats = df.select(
*[
F.sum(F.col(c).isNull().cast("int")).alias(f"{c}_nulls")
for c in df.columns
]
)- 在决定填充还是删除之前,先统计每列的 null 数量。
填充缺失值
filled = df.fillna({"name": "unknown", "tier": "basic", "score": 0})- 传入 dict 以按列指定,保持类型一致;避免用字符串填充数值列。
- 日期/时间戳列,最好用领域内约定的默认值(例如
to_date('1970-01-01'))。
有选择地删除行
drop_sparse = df.dropna(thresh=3) # 保留非 null 值数量 >=3 的行
drop_missing_tier = df.dropna(subset=["tier"])- 使用
thresh防止过度删除。 - 优先使用列子集而不是裸
dropna(),尽可能保留有用记录。
替换哨兵值(占位符)
clean_sent = df.na.replace({"N/A": None, "": None}, subset=["tier", "name"])- 先把各种占位字符串统一转换成真正的 null,再进行填充或删除。
使用 Coalesce 选择第一个非 null
with_fallback = df.withColumn("primary_tier", F.coalesce("tier", F.lit("basic")))coalesce返回参数列表中第一个非 null 的值。
空安全比较(Null-safe comparisons)
matches = df.where(F.expr("tier <=> 'premium'"))<=>(或eqNullSafe)会把两个 null 视为相等;普通的=在参与 null 时结果为 null,而不是 true/false。
保护下游 Join
- 只有在业务规则允许时才对关联键进行填充;否则应显式过滤掉 null 键。
- 对事实表关联维度表时,要事先决定:是在 join 前就删除 null 键,还是保留并在下游逻辑中再处理。
极简清洗流水线示例
clean = (
df
.na.replace({"N/A": None, "": None})
.dropna(subset=["name"]) # 要求 name 不为空
.fillna({"tier": "basic", "score": 0})
.withColumn("tier_clean", F.coalesce("tier", F.lit("basic")))
)这个顺序先统一占位符,再保留必要字段,接着用业务默认值填充,最后增加一个带回退逻辑的列,便于报表和分析。
