Skip to content

PySpark 处理 Null 和 NA:实用清洗秘籍

Updated on

Null 如果不处理,会悄悄破坏指标计算、关联(join)以及导出结果;而随手调用 dropna 又可能悄悄删掉很多有价值的行。团队需要一套明确的模式,在 PySpark 中有意识地检查、填充、删除或安全比较 null。

PySpark 自带的空值处理工具——fillnadropnana.replacecoalesce 以及空安全比较——可以让数据流水线更可控、更透明。

想在不写代码的前提下,从 Python Pandas Dataframe 快速创建数据可视化?

PyGWalker 是一个基于可视化的 Python 探索式数据分析库。PyGWalker (opens in a new tab) 能够简化你在 Jupyter Notebook 中的数据分析和可视化流程,把 pandas dataframe(以及 polars dataframe)变成类似 tableau 的可视分析界面。

PyGWalker for Data visualization (opens in a new tab)

快速决策指南

目标API说明
填充默认值fillna按列传 dict;注意类型匹配
丢弃稀疏行dropna控制 howthreshsubset
替换哨兵值na.replace替换占位字符串或数字
取第一个非 nullcoalesce按优先级合并多列
空安全相等比较<=>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")))
)

这个顺序先统一占位符,再保留必要字段,接着用业务默认值填充,最后增加一个带回退逻辑的列,便于报表和分析。