如何计算作为列值的表达式?

时间:2018-04-24 10:16:20

标签: scala apache-spark apache-spark-sql

我有一个包含数百万行的大数据框,如下所示:

A    B    C    Eqn
12   3    4    A+B
32   8    9    B*C
56   12   2    A+B*C

如何评估Eqn列中的表达式?

3 个答案:

答案 0 :(得分:4)

您可以创建一个自定义UDF来评估这些算术函数

def evalUDF = udf((a:Int, b:Int, c:Int, eqn:String) => {
 val eqnParts = eqn
    .replace("A", a.toString)
    .replace("B", b.toString)
    .replace("C", c.toString)
    .split("""\b""")
    .toList

  val (sum, _) = eqnParts.tail.foldLeft((eqnParts.head.toInt, "")){
    case ((runningTotal, "+"), num) => (runningTotal + num.toInt, "") 
    case ((runningTotal, "-"), num) => (runningTotal - num.toInt, "") 
    case ((runningTotal, "*"), num) => (runningTotal * num.toInt, "") 
    case ((runningTotal, _), op) => (runningTotal, op)
  }

  sum
})

evalDf
  .withColumn("eval", evalUDF('A, 'B, 'C, 'Eqn))
  .show()

输出:

+---+---+---+-----+----+
|  A|  B|  C|  Eqn|eval|
+---+---+---+-----+----+
| 12|  3|  4|  A+B|  15|
| 32|  8|  9|  B*C|  72|
| 56| 12|  2|A+B*C| 136|
+---+---+---+-----+----+

正如你所看到的那样有效,但是非常脆弱(空间,未知的操作符等会破坏代码)并且不遵守操作顺序(否则最后应该是92)

所以你可以自己编写所有这些或者找一些已经做过的文库(比如https://gist.github.com/daixque/1610753)?

也许性能开销会非常大(特别是你开始使用递归解析器),但至少你可以在数据帧上执行它而不是先收集它

答案 1 :(得分:2)

我认为执行DataFrame内部SQL的唯一方法是先select("Eqn").collect然后在源数据集上迭代执行SQL。

由于SQL位于DataFrame中,除了对将在Spark执行程序上执行的分布式计算的描述之外,您无法在处理执行程序上的SQL时提交Spark作业。在执行管道中太迟了。您应该回到驱动程序上以便能够提交新的Spark作业,比如执行SQL。

使用驱动程序上的SQL,然后每个SQL获取相应的行,只需withColumn执行SQL(及其行)。

我认为编写它比开发一个有效的Spark应用程序更容易,但这就是我如何去做。

答案 2 :(得分:0)

我迟到了,以防万一有人在找

  • 使用变量的通用数学表达式解释器
  • 无法将其硬编码到UDF中的复杂/未知表达式(可接受的答案)

然后您可以使用javax.script.ScriptEngineManager

import javax.script.SimpleBindings;
import javax.script.ScriptEngineManager
import java.util.Map
import java.util.HashMap


def calculateFunction = (mathExpression: String, A : Double, B : Double, C : Double ) => {
    val vars: Map[String, Object] = new HashMap[String, Object]();
    vars.put("A",A.asInstanceOf[Object])
    vars.put("B",B.asInstanceOf[Object])
    vars.put("C",C.asInstanceOf[Object])
    val engine = new ScriptEngineManager().getEngineByExtension("js");
    val result = engine.eval(mathExpression, new SimpleBindings(vars));
    result.asInstanceOf[Double]
}

val calculateUDF = spark.udf.register("calculateFunction",calculateFunction)

注意::它将处理通用表达式,并且功能强大,但性能却比接受的答案差很多,并且占用大量内存