如何在Spark中将maptype转换为SparkML稀疏向量?

时间:2018-02-01 11:09:38

标签: scala apache-spark apache-spark-mllib apache-spark-ml

我的原始模式包含许多我想在ML模型中使用的maptypes,所以我需要将它们转换为SparkML稀疏向量。

root
 |-- colA: map (nullable = true)
 |    |-- key: string
 |    |-- value: double (valueContainsNull = true)
 |-- colB: map (nullable = true)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)
 |-- colC: map (nullable = true)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)

上下文: SparkML模型要求将数据形成为特征向量。有 一些实用程序来生成特征向量但没有一个支持maptype类型。 例如 SparkML VectorAssembler允许组合多个列(所有数字类型,布尔类型或矢量类型)。

修改

到目前为止,我的解决方案是将地图分别分解为列,然后使用VectorAssembler

val listkeysColA = df.select(explode($"colA"))
  .select($"key").as[Int].distinct.collect.sorted

val exploded= df.select(listkeysColA.map(x => 
  $"colA".getItem(x).alias(x.toString)): _*).na.fill(0) 

val columnNames = exploded.columns

val assembler = new VectorAssembler().setInputCols(columnNames).setOutputCol("features")

EDIT2

我应该补充一点,我的地图中的数据非常稀疏,并且事先没有已知的密钥集。这就是为什么在我目前的解决方案中,我首先将数据传递给数据以收集和排序密钥。然后我使用getItem(keyName)访问值。

1 个答案:

答案 0 :(得分:0)

据我所知,Spark中没有内置的方法,因此UDF在这种情况下是一个合适的解决方案。这是一个带有Map[String, Double]的列并返回ML向量的列:

val toVector = udf((m: Map[String, Double]) => Vectors.dense(m.values.toArray).toSparse)

由于Map没有订单,因此不保证结果向量具有特定顺序。

示例输入(df):

+---------------------------------+---------------------------------+
|colA                             |colB                             |
+---------------------------------+---------------------------------+
|Map(a -> 1.0, b -> 2.0, c -> 3.0)|Map(a -> 1.0, b -> 2.0, c -> 3.0)|
+---------------------------------+---------------------------------+

并使用UDF

val df2 = df.withColumn("colA", toVector($"colA")).withColumn("colB", toVector($"colB"))

给出以下输出:

+-------------+-------------+
|colA         |colB         |
+-------------+-------------+
|[1.0,2.0,3.0]|[1.0,2.0,3.0]|
+-------------+-------------+

其中两列都是矢量类型。

root
 |-- colA: vector (nullable = true)
 |-- colB: vector (nullable = true)

如果您想将所有列合并为一个向量,请在此处使用VectorAssembler,如问题编辑中那样。

修改

如果您需要保持值的某个顺序,那么您需要先完成所有键的收集。但是,您可以避免使用explode

val keys = df.select($"colA")
  .flatMap(_.getAs[Map[String, Int]]("colA").keys)
  .distinct
  .collect
  .sorted

然后相应地更改UDF以将keys的顺序考虑在内,默认值为0.0:

val toVector = udf((m: Map[String, Double]) => 
  Vectors.dense(keys.map(key => m.getOrElse(key, 0.0))).toSparse
)