Spark Scala中按列唯一值占比删除列的方案合理性及优化咨询
问题解答
现有思路合理性评估
- 你的实现思路逻辑是通顺的,可以正确得到待删除列列表,但存在冗余操作和性能优化空间:
- 冗余点1:
count(col(c))会对每列单独计算非空行数,重复执行了N次(N为列数)全局计数操作,不必要地增加了计算开销 - 冗余点2:拿到仅1行的占比结果表
df2后,又做了一次selectExpr取列名的操作,属于完全多余的步骤,直接从first()的结果里就能过滤得到待删除列名 - 性能瓶颈:
countDistinct本身是全局聚合操作,会触发shuffle,列数较多时开销会明显上升
- 冗余点1:
更高效的实现方案
优化思路核心是减少重复计算和shuffle次数:
- 提前只计算1次全表总行数,避免每列重复计数
- 单次聚合直接拿到所有列的唯一值计数,没有多余的中间DataFrame操作
- 大数据量场景下可使用
approx_count_distinct替代countDistinct,允许少量精度误差的前提下,性能提升可达数倍
优化后代码示例:
val df = spark.read .format("csv") .option("delimiter", ";") .option("header", "true") .load("work/youtube_videos.csv") // 提前计算一次总行数,若统计占比的分母是当前列非空行数,可保留原来的count(col(c))逻辑 val totalRows = df.count() val threshold = 1.0 // 百分比阈值 // 单次聚合得到所有列的唯一值占比 val distinctRatioMap = df.select( df.columns.map(c => (countDistinct(col(c)) * 100.0 / lit(totalRows)).alias(c)): _* ).first().getValuesMap[Double](df.columns) // 直接过滤得到待删除列 val colsToDrop = distinctRatioMap.filter(_._2 < threshold).keys.toSeq colsToDrop.foreach(println) // 执行删除列操作 val cleanedDf = df.drop(colsToDrop: _*)
如果可以接受近似精度,把countDistinct替换为approx_count_distinct即可,还可以传入第二个参数指定误差上限,默认值为0.05(即5%误差)。
内容的提问来源于stack exchange,提问作者joesan
相关产品推荐
相关产品推荐

