Spark DataFrame中的SumProduct

时间:2015-11-10 19:56:13

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

我想在Spark DataFrame中跨列创建一个sumproduct。我有一个看起来像这样的DataFrame:

id    val1   val2   val3   val4
123   10     5      7      5

我也有一张看起来像的地图:

val coefficents = Map("val1" -> 1, "val2" -> 2, "val3" -> 3, "val4" -> 4)

我想获取DataFrame的每一列中的值,将其乘以地图中的相应值,并将结果返回到新列中,以便实质上:

(10*1) + (5*2) + (7*3) + (5*4) = 61

我试过了:

val myDF1 = myDF.withColumn("mySum", {var a:Double = 0.0; for ((k,v) <- coefficients) a + (col(k).cast(DoubleType)*coefficients(k));a})

但是出现“+”方法过载的错误。即使我解决了这个问题,我也不确定这会有效。有任何想法吗?我总是可以动态地将SQL查询构建为文本字符串并按照这种方式进行操作,但我希望能够更有说服力。

任何想法都表示赞赏。

3 个答案:

答案 0 :(得分:2)

看起来问题是你实际上并没有a

做任何事情
for((k, v) <- coefficients) a + ...

你可能意味着a += ...

此外,还有一些建议可以清除withColumn调用中的代码块:

您不需要致电coefficients(k),因为您已经v for((k,v) <- coefficients)已经从fold获得了价值

Scala非常善于制作单行,但如果你必须在这一行中加上分号,那就有点作弊:P我建议将和计算部分分成每行一行

总和表达式可以重写为var,避免使用var(惯用的Scala通常会避免使用import org.apache.spark.sql.functions.lit coefficients.foldLeft(lit(0.0)){ case (sumSoFar, (k,v)) => col(k).cast(DoubleType) * v + sumSoFar } ),例如

<table class="table table-striped table-bordered table-advance table-hover">
<tbody>
<td>
    <div class="btn-group">
    <div class="btn dropdown-toggle" style=" background-color:#EF4836;color:white; margin:2.5px 0;" data-toggle="dropdown" data-hover="dropdown" data-delay="0" data-close-others="true">
    </div>
    <ul class="dropdown-menu" role="menu" style="width:300px;">
    <span style="background-color: rgb(0, 0, 255);"><font color="#ffffff">Hi!<br></font></span>
      <li>Hello Stack Overflow</li>
        <li>
          Google vs Amazon</li>
    </ul>
    </div>
</td>
...

答案 1 :(得分:2)

您的代码存在问题,即您尝试将Column添加到Doublecast(DoubleType)仅影响一种存储值,而不影响列本身的类型。由于Double没有提供*(x: org.apache.spark.sql.Column): org.apache.spark.sql.Column方法,因此一切都失败了。

为了使其有效,你可以做一些这样的事情:

import org.apache.spark.sql.Column
import org.apache.spark.sql.functions.{col, lit}

val df = sc.parallelize(Seq(
    (123, 10, 5, 7, 5), (456,  1, 1, 1, 1)
)).toDF("k", "val1", "val2", "val3", "val4")

val coefficients = Map("val1" -> 1, "val2" -> 2, "val3" -> 3, "val4" -> 4)

val dotProduct: Column = coefficients
  // To be explicit you can replace
  // col(k) * v with col(k) * lit(v)
  // but it is not required here
  // since we use * f Column.* method not Int.*
  .map{ case (k, v) => col(k) * v }  // * -> Column.*
  .reduce(_ + _)  // + -> Column.+

df.withColumn("mySum", dotProduct).show
// +---+----+----+----+----+-----+
// |  k|val1|val2|val3|val4|mySum|
// +---+----+----+----+----+-----+
// |123|  10|   5|   7|   5|   61|
// |456|   1|   1|   1|   1|   10|
// +---+----+----+----+----+-----+

答案 2 :(得分:0)

我不确定这是否可以通过DataFrame API实现,因为您只能使用列而不是任何预定义的闭包(例如参数图)。

我在下面概述了使用DataFrame的基础RDD的方法:

import org.apache.spark.sql.types._
import org.apache.spark.sql.Row

// Initializing your input example.
val df1 = sc.parallelize(Seq((123, 10, 5, 7, 5))).toDF("id", "val1", "val2", "val3", "val4")

// Return column names as an array
val names = df1.columns

// Grab underlying RDD and zip elements with column names
val rdd1 = df1.rdd.map(row => (0 until row.length).map(row.getInt(_)).zip(names))

// Tack on accumulated total to the existing row
val rdd2 = rdd0.map { seq => Row.fromSeq(seq.map(_._1) :+ seq.map { case (value: Int, name: String) => value * coefficents.getOrElse(name, 0) }.sum) }

// Create output schema (with total)
val totalSchema = StructType(df1.schema.fields :+ StructField("total", IntegerType))

// Apply schema to create output dataframe
val df2 = sqlContext.createDataFrame(rdd1, totalSchema)

// Show output:
df2.show()
...
+---+----+----+----+----+-----+
| id|val1|val2|val3|val4|total|
+---+----+----+----+----+-----+
|123|  10|   5|   7|   5|   61|
+---+----+----+----+----+-----+