我有一个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)
如何为更多列解决此问题?
答案 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
个节点的数量限制。