如何以最规范高效的方式筛选含非空值的Spark DataFrame列?
在Spark 1.6+中高效筛选含至少一个非空值的列
嘿,这个需求其实很常见,咱们可以用一次全量聚合统计的方式来实现,既规范又高效,不用反复扫描数据集。
核心思路
要筛选出至少有一个非空值的列,关键是统计每列的非空行数:count()函数会自动忽略null值,所以如果某列的count结果大于0,就说明该列存在非空值。我们只需要一次聚合操作就能拿到所有列的统计结果,再筛选出符合条件的列即可。
完整实现代码
结合你提供的初始化代码,完整的解决方案如下:
// 你的数据初始化代码(保留) case class C(int1: Integer, int2: Integer, str1: String, str2: String, dt1: String, dt2: String) val cc = Seq( C(1, null, "strin1", null, null, null), C(null, null, null, null, "2000-01-03 12:12:12", null) ) val t = sc.parallelize(cc, 2).toDF() val df = t.withColumn("dt1", $"dt1".cast("timestamp")).withColumn("dt2", $"dt2".cast("timestamp")) // 1. 一次性统计所有列的非空值数量 val columnNonEmptyCounts = df.agg( df.columns.map(colName => count(col(colName)).alias(colName)): _* ).head() // 取出聚合后的唯一一行结果 // 2. 筛选出至少有一个非空值的列名 val columnsToKeep = df.columns.filter(colName => { columnNonEmptyCounts.getAs[Long](colName) > 0 }) // 3. 选择目标列得到最终结果 val resultDF = df.select(columnsToKeep.map(col): _*) // 查看结果 resultDF.show(false)
结果验证
执行后会输出你期望的结果:
+----+-------+-----------------------+ |int1|str1 |dt1 | +----+-------+-----------------------+ |1 |strin1 |null | |null|null |2000-01-03 12:12:12.0 | +----+-------+-----------------------+
为什么这个方案高效?
- 只需要一次Action操作(
head()触发计算),Spark会对数据集做一次全量扫描完成所有列的统计,避免了多次扫描带来的性能损耗。 - 代码简洁直观,完全符合Spark的API规范,在1.6及以上版本都能稳定运行。
关于你之前尝试的写法
你提到的df.columns.filter(c => when(count(col(c))>0,c))无法成功,是因为when是用于生成列表达式的函数,不能直接在Scala集合的filter里使用——集合filter需要的是布尔值判断,而不是列表达式。咱们上面的方案先拿到实际的统计数值,再做判断,就解决了这个问题。
DataSet的适配
如果是处理DataSet,操作逻辑完全一致:只需要先把DataSet转成DataFrame(dataset.toDF()),执行上述操作后,再转回DataSet即可(resultDF.as[YourCaseClass])。
内容的提问来源于stack exchange,提问作者MaxU - stand with Ukraine




