如何将DataFrame映射到EdgeRDD

时间:2016-03-24 12:20:59

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

我有一个DataFrame,如:

val data = sc.parallelize(Array((1,10,10,7,7),(2,7,7,7,8),(3, 5,5,6,8))).toDF("id","col1","col2","col3","col4")

我想要做的是创建一个EdgeRDD,如果两个id共享一个链接,如果它们在至少一个列中共享相同的值

id col1 col2 col3 col4
 1   10   10    7    7
 2    7    7    7    8
 3    5    5    6    8

然后节点1和2有一个无向链接1--2,因为它们在col3中共享一个公共值。

出于同样的原因,节点2和3共享一个无向链接,因为它们在col4中共享一个公共值

我知道如何以一种丑陋的方式解决这个问题(但在我的实际案例中,我有太多的专栏来采用这种策略)

val data2 = data.withColumnRenamed("id", "idd").withColumnRenamed("col1", "col1d").withColumnRenamed("col2", "col2d").withColumnRenamed("col3", "col3d").withColumnRenamed("col4", "col4d")
val res = data.join(data2, data("id") < data2("idd")
                    && (data("col1") === data2("col1d")
                    || data("col2") === data2("col2d")
                    || data("col3") === data2("col3d")
                    || data("col4") === data2("col4d")))
                                              //> res  : org.apache.spark.sql.DataFrame = [id: int, col1: int, col2: int, col
                                              //| 3: int, col4: int, idd: int, col1d: int, col2d: int, col3d: int, col4d: int
                                              //| ]
res.show                                      //> +---+----+----+----+----+---+-----+-----+-----+-----+
                                              //| | id|col1|col2|col3|col4|idd|col1d|col2d|col3d|col4d|
                                              //| +---+----+----+----+----+---+-----+-----+-----+-----+
                                              //| |  1|  10|  10|   7|   7|  2|    7|    7|    7|    8|
                                              //| |  2|   7|   7|   7|   8|  3|    5|    5|    6|    8|
                                              //| +---+----+----+----+----+---+-----+-----+-----+-----+
                                              //| 
val links = EdgeRDD.fromEdges(res.map(row => Edge(row.getAs[Int]("id").toLong, row.getAs[Int]("idd").toLong, "indirect")))
                                              //> links  : org.apache.spark.graphx.impl.EdgeRDDImpl[String,Nothing] = EdgeRDD
                                              //| Impl[27] at RDD at EdgeRDD.scala:42
links.foreach(println)                        //> Edge(1,2,indirect)
                                              //| Edge(2,3,indirect)

如何为更多列解决此问题?

1 个答案:

答案 0 :(得分:2)

你的意思是这样吗?

val expr = data.columns.diff(Seq("id"))
  .map(c => data(c) === data2(s"${c}d"))
  .reduce(_ || _)

data.join(data2, data("id") < data2("idd") && expr)

您也可以使用别名

import org.apache.spark.sql.functions.col

val expr = data.columns.diff(Seq("id"))
  .map(c => col(s"d1.$c") === col(s"d2.$c"))
  .reduce(_ || _)

data.alias("d1").join(data.alias("d2"), col("d1.id") < col("d2.id") && expr)

您可以轻松地按照select轻松完成每项操作($相当于col,但需要导入sqlContext.implicits.StringToColumn

.select($"id".cast("long"), $"idd".cast("long"))

.select($"d1.id".cast("long"), $"d2.id".cast("long"))

和模式匹配:

.rdd.map { case Row(src: Long, dst: Long) => Edge(src, dst, "indirect") }

请注意,像这样的逻辑分离无法优化,扩展为笛卡尔积,后跟filter。如果你想避免,你可以尝试以不同的方式解决这个问题。

让我们从将数据从宽到长重塑:

val expr = explode(array(data.columns.tail.map(
  c => struct(lit(c).alias("column"), col(c).alias("value"))
): _*))

val long = data.withColumn("tmp", expr)
  .select($"id", $"tmp.column", $"tmp.value")

这将为我们提供一个DataFrame,其中包含以下架构:

long.printSchema

// root
//  |-- id: integer (nullable = false)
//  |-- column: string (nullable = false)
//  |-- value: integer (nullable = false)

对于这样的数据,您有多种选择,包括优化的join

val pairs = long.as("long1")
  .join(long.as("long2"),
    $"long1.column" === $"long2.column" &&  // Optimized
    $"long1.value" === $"long2.value" &&  // Optimized
    $"long1.id" < $"long2.id" // Not optimized - filtered after sort-merge join
  )
  // Select only ids
  .select($"long1.id".alias("src"), $"long2.id".alias("dst"))
  // And keep distict
  .distinct

pairs.show
// +---+---+
// |src|dst|
// +---+---+
// |  1|  2|
// |  2|  3|
// +---+---+

这可以通过使用不同的散列技术来进一步改进,以避免爆炸产生的大量记录。

您还可以将此问题视为二分图,其中观察属于节点类别,属性 - 值对属于另一个。

sealed trait CustomNode
case class Record(id: Long) extends CustomNode
case class Property(name: String, value: Int) extends CustomNode

以此为起点,您可以使用long生成以下类型的边:

Record -> Property

通过搜索

之类的路径直接使用GraphX解决这个问题
Record -> Property <- Record

提示:为每个属性收集邻居并传播回来。

与之前相同,您应该考虑使用散列或存储桶来减少生成的Property个节点的数量限制。