4. 使用Spark SQL进行探索性数据分析
在Delta Lake中完成数据接入后,真正决定后续建模质量的是对数据的理解程度。探索性数据分析(EDA)不是“随便看看数据”,而是用一组可复现的SQL查询,把数据的结构、完整度、分布形态、字段间关系和异常点量化出来。本节以一张销售明细Delta表为例,演示如何用Spark SQL完成数据剖析,并最终形成一份可交付的销售数据质量评估报告。
一、数据剖析:先建立字段级画像
假设Delta表名为 sales_delta,主要字段包括:order_id(订单号)、customer_id(客户号)、product_id(商品号)、quantity(数量)、unit_price(单价)、discount(折扣率)、order_ts(下单时间)、region(区域)、channel(渠道)。数据剖析的目标是回答三个问题:有多少行、每列有多少非空值、每列有多少唯一值。
-- 1. 表级概览
SELECT COUNT(*) AS total_rows,
COUNT(DISTINCT order_id) AS distinct_orders,
MIN(order_ts) AS min_ts,
MAX(order_ts) AS max_ts
FROM sales_delta;
-- 2. 字段级剖析:非空率与基数
SELECT
COUNT(*) AS total,
COUNT(customer_id) AS customer_not_null,
COUNT(DISTINCT customer_id) AS customer_cardinality,
COUNT(product_id) AS product_not_null,
COUNT(DISTINCT product_id) AS product_cardinality,
COUNT(region) AS region_not_null,
COUNT(DISTINCT region) AS region_cardinality
FROM sales_delta;
示例结果(教学假设):总行数 1,200,000;customer_id 非空 1,198,400,基数 48,200;product_id 非空 1,200,000,基数 3,600;region 非空 1,176,000,基数 6。由此可判断:customer_id 存在约 0.13% 缺失,region 缺失约 2%,而 product_id 完整。基数信息还能帮助判断字段是否适合作为维度键。
二、缺失值:从计数到影响评估
缺失值不能只统计“有多少”,还要看“缺在哪些维度上”。下面按区域和渠道统计缺失分布,判断缺失是否随机。
SELECT region,
COUNT(*) AS rows_cnt,
SUM(CASE WHEN customer_id IS NULL THEN 1 ELSE 0 END) AS cust_null,
ROUND(100.0 * SUM(CASE WHEN customer_id IS NULL THEN 1 ELSE 0 END) / COUNT(*), 3) AS cust_null_pct
FROM sales_delta
GROUP BY region
ORDER BY cust_null_pct DESC;
若某区域缺失率显著高于其他区域(例如某区域达到 8%,其余低于 0.5%),说明缺失与区域相关,不能简单用全局均值填充,而应结合区域做分层处理或回溯上游采集链路。对于 quantity、unit_price 这类数值字段,缺失会直接影响销售额计算,建议在质量报告中单独列出“影响金额”的估算。
三、分布:用分位数和直方图看形态
Spark SQL 提供 percentile_approx 快速查看数值分布,避免全表排序开销。
SELECT
percentile_approx(quantity, array(0.01,0.25,0.5,0.75,0.99)) AS qty_pct,
percentile_approx(unit_price, array(0.01,0.25,0.5,0.75,0.99)) AS price_pct,
percentile_approx(discount, array(0.5,0.9,0.99)) AS disc_pct
FROM sales_delta;
示例结果:quantity 中位数 3,P99 为 40;unit_price 中位数 59.9,P99 为 899;discount 中位数 0,P99 为 0.35。若 quantity 出现 P99=40 而最大值达到 9999,则说明存在极端值,需要进入异常检测环节。对类别字段则用 GROUP BY 统计频次,观察是否存在长尾或单一值占比过高。
四、相关性:判断字段间是否冗余或驱动
Spark SQL 内置 corr() 函数可计算皮尔逊相关系数。重点观察 quantity 与 unit_price、discount 与销售额之间的关系。
SELECT
corr(quantity, unit_price) AS corr_qty_price,
corr(discount, quantity) AS corr_disc_qty,
corr(quantity * unit_price * (1 - discount), discount) AS corr_amt_disc
FROM sales_delta
WHERE quantity IS NOT NULL AND unit_price IS NOT NULL;
示例结果:corr_qty_price ≈ -0.02(几乎无关),corr_disc_qty ≈ 0.31(弱正相关),corr_amt_disc ≈ -0.18。这说明折扣对数量有一定拉动,但对单笔金额是负向影响,符合业务直觉。若两个特征相关性超过 0.85,建模时应考虑剔除其一,避免多重共线性。
五、异常检测:规则与统计双管齐下
销售数据常见异常包括:数量为负、单价为负、折扣超出 [0,1]、时间戳越界、金额与数量单价严重不符。先用规则过滤,再用 IQR 或 Z-Score 找统计离群点。
-- 规则型异常
SELECT 'negative_qty' AS rule, COUNT(*) AS cnt FROM sales_delta WHERE quantity < 0
UNION ALL
SELECT 'invalid_discount', COUNT(*) FROM sales_delta WHERE discount < 0 OR discount > 1
UNION ALL
SELECT 'future_ts', COUNT(*) FROM sales_delta WHERE order_ts > current_timestamp()
UNION ALL
SELECT 'amount_mismatch', COUNT(*) FROM sales_delta
WHERE ABS(quantity * unit_price * (1 - discount) - amount) > 0.01;
-- IQR 离群点
WITH q AS (
SELECT percentile_approx(quantity, array(0.25,0.75)) AS p FROM sales_delta
)
SELECT COUNT(*) AS iqr_outliers
FROM sales_delta, q
WHERE quantity < p[0] - 1.5*(p[1]-p[0]) OR quantity > p[1] + 1.5*(p[1]-p[0]);
示例结果:负数量 320 条,非法折扣 1,150 条,未来时间戳 0 条,金额不一致 2,400 条,IQR 离群点 18,600 条。规则型异常通常直接判定为脏数据;IQR 离群点则需结合业务判断,例如大客户批量采购属于合理极值,不应一律删除。
六、形成质量评估报告
把上述查询结果整理成一份结构化报告,建议包含五个维度:完整性(缺失率)、一致性(金额校验、时间越界)、准确性(负值、非法折扣)、唯一性(主键重复)、分布合理性(分位数与离群比例)。每条结论都应附带“影响范围”和“处置建议”,例如:
| 维度 | 指标 | 示例值 | 处置建议 |
|---|---|---|---|
| 完整性 | customer_id 缺失率 | 0.13% | 按区域分层填充或回溯采集 |
| 完整性 | region 缺失率 | 2.00% | 用渠道+客户画像推断补全 |
| 一致性 | 金额不符行数 | 2,400 | 重算 amount 并覆盖 |
| 准确性 | 负数量 | 320 | 隔离到 quarantine 表 |
| 准确性 | 非法折扣 | 1,150 | 截断到 [0,1] 或置空 |
| 分布 | quantity IQR 离群 | 18,600 | 标记 is_outlier,不删除 |
DESCRIBE HISTORY sales_delta 确认数据版本,再对同一版本执行所有查询,保证报告可复现。若数据量很大,可先 CACHE TABLE 或抽样 10% 做快速剖析,再对关键指标全量校验。完成这一步后,你得到的不仅是一组查询结果,而是一份可追溯、可复现、可指导清洗与特征工程的销售数据质量评估报告。它直接决定后续特征工程和模型训练的数据基础是否可靠。