我在spark中有一个操作,应该对数据框中的几列执行。通常,指定此类操作有两种可能性
handleBias("bar", df)
.join(handleBias("baz", df), df.columns)
.drop(columnsToDrop: _*).show
var isFirst = true
var res = df
for (col <- columnsToDrop ++ columnsToCode) {
if (isFirst) {
res = handleBias(col, res)
isFirst = false
} else {
res = handleBias(col, res)
}
}
res.drop(columnsToDrop: _*).show
问题是动态生成的DAG是不同的,当使用更多列而不是硬编码操作时,动态解决方案的运行时间会增加得多。
我很好奇如何将动态构造的优雅与快速执行时间结合起来。
对于大约80列,这导致硬编码变体的相当好的图形 对于动态构造的查询而言,这是一个非常大的,可能较少可并行化且速度较慢的DAG。
当前版本的spark(2.0.2)与DataFrames
和spark-sql
完成最小例子的代码:
def handleBias(col: String, df: DataFrame, target: String = "FOO"): DataFrame = {
val pre1_1 = df
.filter(df(target) === 1)
.groupBy(col, target)
.agg((count("*") / df.filter(df(target) === 1).count).alias("pre_" + col))
.drop(target)
val pre2_1 = df
.groupBy(col)
.agg(mean(target).alias("pre2_" + col))
df
.join(pre1_1, Seq(col), "left")
.join(pre2_1, Seq(col), "left")
.na.fill(0)
}
使用foldleft
运行任务会生成线性DAG
并且硬编码所有列的函数导致
两者都比我原来的DAG好很多但是,硬编码的变体对我来说看起来更好。连接spark中的SQL语句的字符串可以允许我动态生成硬编码的执行图,但这看起来相当丑陋。你还有别的选择吗?
答案 0 :(得分:2)
编辑1:从handleBias中删除了一个窗口函数,并将其转换为广播连接。
编辑2:更改了空值的替换策略。
我有一些可以改善您的代码的建议。首先,对于“handleBias”函数,我会使用窗口函数和“withColumn”调用来做,避免连接:
import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window
def handleBias(df: DataFrame, colName: String, target: String = "foo") = {
val w1 = Window.partitionBy(colName)
val w2 = Window.partitionBy(colName, target)
val result = df
.withColumn("cnt_group", count("*").over(w2))
.withColumn("pre2_" + colName, mean(target).over(w1))
.withColumn("pre_" + colName, coalesce(min(col("cnt_group") / col("cnt_foo_eq_1")).over(w1), lit(0D)))
.drop("cnt_group")
result
}
然后,为了调用多列,我建议使用foldLeft
这是解决此类问题的“功能”方法:
val df = Seq((1, "first", "A"), (1, "second", "A"),(2, "noValidFormat", "B"),(1, "lastAssumingSameDate", "C")).toDF("foo", "bar", "baz")
val columnsToDrop = Seq("baz")
val columnsToCode = Seq("bar", "baz")
val target = "foo"
val targetCounts = df.filter(df(target) === 1).groupBy(target)
.agg(count(target).as("cnt_foo_eq_1"))
val newDF = df.join(broadcast(targetCounts), Seq(target), "left")
val result = (columnsToDrop ++ columnsToCode).toSet.foldLeft(df) {
(currentDF, colName) => handleBias(currentDF, colName)
}
result.drop(columnsToDrop:_*).show()
+---+--------------------+------------------+--------+------------------+--------+
|foo| bar| pre_baz|pre2_baz| pre_bar|pre2_bar|
+---+--------------------+------------------+--------+------------------+--------+
| 2| noValidFormat| 0.0| 2.0| 0.0| 2.0|
| 1|lastAssumingSameDate|0.3333333333333333| 1.0|0.3333333333333333| 1.0|
| 1| second|0.6666666666666666| 1.0|0.3333333333333333| 1.0|
| 1| first|0.6666666666666666| 1.0|0.3333333333333333| 1.0|
+---+--------------------+------------------+--------+------------------+--------+
我不确定它会改进你的DAG,但至少它会使代码更清晰,更具可读性。
<强>参考:强>