3. 数据探索基础:使用 Spark DataFrame 进行数据概览
在特征工程中,样本偏差校验的第一步是对原始数据进行全面、快速的数据探索。Spark DataFrame 提供了丰富的 API,帮助我们在分布式环境下高效地获取数据概览、统计摘要和结构信息。本节将系统介绍 show()、printSchema() 和 describe() 三个核心方法,并结合实际案例演示如何通过它们发现数据质量问题与潜在偏差。
3.1 数据预览:show()
show() 是最直观的数据查看方法,默认显示前 20 行数据。在样本偏差校验中,我们通常用它快速检查数据格式、缺失值标记(如 null、NaN、?)以及异常值。
关键参数:
numRows:控制显示行数,建议在探索阶段设置为 50~100 行以覆盖更多样本。truncate:是否截断长字符串,设为false可查看完整内容。vertical:设为true时以垂直模式输出,适合列数较多的 DataFrame。
# 加载数据
df = spark.read.csv("data/user_behavior.csv", header=True, inferSchema=True)
# 显示前 10 行,不截断字符串
df.show(10, truncate=False)
# 垂直显示前 5 行,便于观察所有列
df.show(5, vertical=True)
偏差校验提示: 使用
show() 时,注意观察各列是否有统一的缺失值占位符(如 "Unknown"、-1),这些往往是样本偏差的信号。
3.2 结构探查:printSchema()
printSchema() 以树形结构输出 DataFrame 的 Schema,包括列名、数据类型和可空性。在偏差校验中,Schema 信息帮助我们判断:
- 类型推断是否正确:例如,数值列是否被错误推断为字符串。
- 可空性标记:
nullable = true的列可能存在缺失值,需进一步分析缺失模式。 - 特征类型分布:数值型、类别型、时间型特征的比例,为后续偏差检测方法选择提供依据。
df.printSchema()
# 输出示例:
# root
# |-- user_id: integer (nullable = true)
# |-- age: integer (nullable = true)
# |-- income: double (nullable = true)
# |-- city: string (nullable = false)
# |-- signup_date: date (nullable = true)
# |-- label: integer (nullable = true)
常见陷阱: 当
inferSchema 设置为 true 时,Spark 会扫描部分数据推断类型。如果样本偏差导致前几行数据缺失严重,可能将数值列错误推断为字符串类型,进而影响后续统计计算。
3.3 统计摘要:describe()
describe() 对数值列生成统计摘要,包含 count、mean、stddev、min、max 以及四分位数(需额外配置)。这是样本偏差校验的核心工具之一,通过对比不同分组的统计量,可以快速定位分布偏移。
# 对所有数值列生成统计摘要
summary_df = df.describe()
summary_df.show()
# 对指定列生成摘要,并转为 Pandas 便于可视化
df.select("age", "income", "label").describe().toPandas()
| 统计量 | age | income | label |
|---|---|---|---|
| count | 100000 | 98500 | 100000 |
| mean | 38.5 | 75200.0 | 0.12 |
| stddev | 12.3 | 45000.0 | 0.33 |
| min | 18 | 0.0 | 0 |
| max | 85 | 500000.0 | 1 |
偏差识别要点:
- count 差异:如果某列的 count 明显少于其他列,说明存在缺失值,需分析缺失是否随机。
- min/max 异常:收入为 0 或年龄为 0 可能是数据错误或特殊编码,需确认是否代表缺失。
- stddev 过大:标准差异常大可能暗示存在离群点或数据分布严重偏斜。
3.4 综合数据探索流程
在实际项目中,我们通常将上述三个方法组合使用,形成标准的数据探索流水线:
- 结构检查:
printSchema()确认类型推断和可空性。 - 数据预览:
show(50, truncate=False)查看原始数据样例。 - 统计摘要:
describe()获取数值列分布概览。 - 分组对比:结合
groupBy()和describe()对比不同分组的统计量,初步判断是否存在样本偏差。
# 分组对比示例:按 label 分组查看 age 分布
df.groupBy("label").agg(
F.count("age").alias("count"),
F.avg("age").alias("mean_age"),
F.stddev("age").alias("std_age")
).show()
3.5 进阶:自定义统计摘要
当 describe() 提供的统计量不足以支撑偏差分析时,我们可以使用 agg() 结合多个聚合函数自定义摘要:
from pyspark.sql import functions as F
df.agg(
F.count("income").alias("count"),
F.mean("income").alias("mean"),
F.stddev("income").alias("stddev"),
F.skewness("income").alias("skewness"), # 偏度
F.kurtosis("income").alias("kurtosis"), # 峰度
F.expr("percentile_approx(income, 0.25)").alias("q1"),
F.expr("percentile_approx(income, 0.5)").alias("median"),
F.expr("percentile_approx(income, 0.75)").alias("q3")
).show()
偏度与峰度的作用: 偏度反映分布对称性,偏度绝对值大于 1 通常表示严重偏斜;峰度反映尾部厚度,峰度大于 3 表示存在较多离群点。这两个指标在样本偏差校验中能有效发现分布形态的异常变化。
3.6 实践:快速识别样本偏差
以下是一个完整的探索脚本,结合了上述所有方法,并输出偏差预警:
def explore_data(df, target_col="label"):
"""数据探索与偏差预警"""
print("=" * 60)
print("1. Schema 检查")
print("=" * 60)
df.printSchema()
print("\n" + "=" * 60)
print("2. 数据预览(前 20 行)")
print("=" * 60)
df.show(20, truncate=False)
print("\n" + "=" * 60)
print("3. 全局统计摘要")
print("=" * 60)
df.describe().show()
print("\n" + "=" * 60)
print("4. 分组统计对比(按 label)")
print("=" * 60)
df.groupBy(target_col).agg(
F.count("*").alias("样本量"),
F.avg("age").alias("平均年龄"),
F.stddev("age").alias("年龄标准差"),
F.avg("income").alias("平均收入")
).show()
# 偏差预警:检查样本量是否严重失衡
counts = df.groupBy(target_col).count().collect()
ratios = [row["count"] / sum([r["count"] for r in counts]) for row in counts]
if max(ratios) / min(ratios) > 10:
print("⚠️ 预警:正负样本比例超过 10:1,存在严重样本不平衡!")
重要提醒: 数据探索不是一次性工作。在特征工程的不同阶段(原始数据、清洗后、特征构造后),都应重复执行上述探索流程,以监控样本偏差是否在特征变换过程中被引入或放大。