【问题标题】:Create a new spark dataframe that contains pairwise combinations of another dataframe?创建一个包含另一个数据帧的成对组合的新火花数据帧?
【发布时间】:2025-12-20 18:50:06
【问题描述】:

考虑下面的代码

question = spark.createDataFrame([{'A':1,'B':5},{'A':2,'B':5},
                             {'A':3,'B':5},{'A':3,'B':6}])
#+---+---+
#|  A|  B|
#+---+---+
#|  1|  5|
#|  2|  5|
#|  3|  5|
#|  3|  6|
#+---+---+

如何创建如下所示的 spark 数据框:

solution = spark.createDataFrame([{'C':1,'D':2},{'C':1,'D':3},
                             {'C':2,'D':3},{'C':5,'D':6}])
#+---+---+
#|  C|  D|
#+---+---+
#|  1|  2|
#|  1|  3|
#|  2|  3|
#|  5|  6|
#+---+---+

这是三元闭包的概念,我根据已经连接的边连接三角形的第三条边。

我必须有 (1,2),因为 (1,5) 和 (2,5) 存在,我必须有 (1,3),因为 (1,5) 和 (3,5) 存在,并且我必须有 (2,3),因为 (2,5) 和 (3,5) 存在。我必须有(5,6),因为(3,5)和(3,6)存在(两个方向的边缘)。 (5,6) 不应该有额外的条目,因为没有两对从 A 映射到 6。由于 A 中没有第二个实例映射到 6,因此不会添加 (5,6)。

【问题讨论】:

  • 那么 C=5, D=6 是否必须有另一个条目?
  • 如果我得到这个问题是正确的,你能不能:1)将原始数据框附加到自身,但 B 和 A 切换.... 2)按 A... 3)平面图分组到所有成对组合(我认为这有 scala 函数).... 4)将新列映射到单独的 C 和 D 列.... 5)如果需要,过滤重复项
  • 不,C=5, D=6 不应该有额外的条目,因为没有两对从 A 映射到 6。我必须有 (1,2),因为 (1,5) 和 ( 2,5) 存在,我必须有 (1,3),因为 (1,5) 和 (3,5) 存在,我必须有 (2,3),因为 (2,5) 和 (3,5) ) 存在。由于 A 中没有第二个实例映射到 6,因此不会添加它。这有助于澄清吗?
  • 所以它只有一种方式,从 A 列到 B? 5 和 6 都与上例中的 3 反向相关。另外,您能否将 cmets 中的说明添加到问题本身?
  • 是的,非常好。它确实需要双向。我将在原始帖子中包含编辑。感谢您的反馈

标签: apache-spark pyspark spark-dataframe


【解决方案1】:

试试这个,

import pyspark.sql.functions as F
from pyspark.sql.types import *
from itertools import combinations

df = spark.createDataFrame([{'A':1,'B':5},{'A':2,'B':5},
                         {'A':3,'B':5},{'A':3,'B':6}])

def pairs(list_):
    if len(set(list_)) > 1:
        return [[int(x[0]),int(x[1])] for x in combinations(set(list_), r=2)]
    else:
        return None

triadic_udf = F.udf(pairs, ArrayType(ArrayType(IntegerType())))
cols = ['C','D']
splits = [F.udf(lambda val:val[0],IntegerType())\
         ,F.udf(lambda val:val[1],IntegerType())]

df1 = df.groupby('B').agg(F.collect_list('A').alias('A'))\
                 .withColumn('pairs',F.explode(triadic_udf(F.col('A'))))\
                 .dropna().select('pairs')

df2 = df.groupby('A').agg(F.collect_list('B').alias('B'))\
                 .withColumn('pairs',F.explode(triadic_udf(F.col('B'))))\
                 .dropna().select('pairs')

solution = df1.union(df2).select([s('pairs').alias(c) for s,c in zip(splits,cols)])

solution.show()

【讨论】:

  • 如果答案正确,您可以检查它是否正确并点赞以帮助其他人轻松找到它;)
  • 我刚开了一个账户,我的信誉分数低于15,所以它不会改变公开显示的分数。会做一次,但声望分数会上升!
  • 我认为这个解决方案非常好,除了想象而不是 (3,6) 对,它实际上是 (6,3)。我认为它不会找到 (5,6) 对。
  • @SiddSingal 我认为如果是这种情况,那么 (5,6) 对将根本不存在,因为在任何给定列中两者之间不会有任何共同元素。根据问题,我认为我们正在查看具有映射到另一列的相同元素的常见对。
  • @mayankagrawal 似乎在三元闭包中,三个不同的对组成了闭包......所以如果我们有 (1,5) 和 (2,5),那么我们需要一对 (1 ,2), 三对 (1,5), (2,5) 和 (1,2) 组成一个三元闭包..这也意味着如果我们只有 (2,5) 和 (1, 2),我们应该能够想出(1,5)。
【解决方案2】:
val df = sc.parallelize(Array((1,5),(2,5),(3,5),(3,6),(1,7),(2,7))).toDF("A","B")
df.union(df.select("B","A"))
  .groupByKey(r => r.getInt(0))
  .flatMapGroups({
    (K,Vs) => Vs.map(_.getInt(1)).toArray.combinations(2).map(a => (a(0), a(1)))
  })
  .dropDuplicates
  .show

这是在 Scala 中,而不是 Python,但应该很容易转换。我包含了一些额外的数据点来说明为什么需要dropDuplicates。我基本上只是完全按照我上面在评论中写的步骤进行操作:
1) 将原始数据帧附加到自身,但 B 和 A 已切换
2) 按 A 分组
3) 平面图组到所有成对组合(我认为有 scala 函数)
4) 将新列映射到单独的 C 和 D 列(我实际上并没有这样做)
5) 如果需要,过滤重复项

【讨论】:

    最近更新 更多