先准备构造一个DataFrame,其中scores
字段是一个序列,里面的每一个元素是一个元组:
import spark.implicits._
val df: DataFrame = Seq(
("A", Seq(("1", 5.0), ("3", 2.0))),
("B", Seq(("1", 4.0), ("2", 5.0))),
("C", Seq(("2", 5.0), ("3", 2.0)))
).toDF("userId", "scores")
.cache()
df.printSchema()
df.show(false)
可以看到这个DataFrame的scores
字段是ArrayType
,而里面的每一个元素是StructType
:
root
|-- userId: string (nullable = true)
|-- scores: array (nullable = true)
| |-- element: struct (containsNull = true)
| | |-- _1: string (nullable = true)
| | |-- _2: double (nullable = false)
+------+------------------+
|userId|scores |
+------+------------------+
|A |[[1,5.0], [3,2.0]]|
|B |[[1,4.0], [2,5.0]]|
|C |[[2,5.0], [3,2.0]]|
+------+------------------+
需求:构建一个UDF函数作用于这个DataFrame的scores
字段,以便计算每一条scores
的模:
// 计算向量的模
val vecLen: UserDefinedFunction = udf { vector: Seq[(String, Double)] =>
var length = 0.0
for (score <- vector) {
length += (score._2 * score._2)
}
Math.sqrt(length)
}
df.withColumn("vecLen", vecLen($"scores")).show()
此时报错:
java.lang.ClassCastException: org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema cannot be cast to scala.Tuple2
Spark认为scores
中的每一个元素是GenericRowWithSchema
类型的,不能转换成Tuple2
事实上,scores
中的每个元素是Struct
类型的,如果想要取出来,它是一个Row
类型的。
据了解,像String
,Seq[Double]
这种类型就可以直接取出来,但是像 Seq[(Double,Double)]
这种类型直接取的话就会丢失schema信息,Spark认为(Double,Double)
是Row
类型的。
可以这样更改UDF函数:
// 计算向量的模
val vecLen: UserDefinedFunction = udf { vector: Seq[Row] =>
var length = 0.0
for (score <- vector) {
length += (score.getDouble(1) * score.getDouble(1))
}
Math.sqrt(length)
}
df.withColumn("vecLen", vecLen($"scores")).show()
这样就能得到正确的结果:
+------+------------------+------------------+
|userId| scores| vecLen|
+------+------------------+------------------+
| A|[[1,5.0], [3,2.0]]| 5.385164807134504|
| B|[[1,4.0], [2,5.0]]|6.4031242374328485|
| C|[[2,5.0], [3,2.0]]| 5.385164807134504|
+------+------------------+------------------+