【问题标题】:Joining two pyspark dataframes by unique values in a column通过列中的唯一值连接两个 pyspark 数据帧
【发布时间】:2020-02-13 01:43:43
【问题描述】:

假设我有两个 pyspark 数据框,users 和 shops。两个数据框的一些示例行如下所示。

用户数据框:

+---------+-------------+---------+
| idvalue | day-of-week | geohash |
+---------+-------------+---------+
| id-1    |           2 | gcutjjn |
| id-1    |           3 | gcutjjn |
| id-1    |           5 | gcutjht |
+---------+-------------+---------+

商店数据框

+---------+-----------+---------+
| shop-id | shop-name | geohash |
+---------+-----------+---------+
| sid-1   | kfc       | gcutjjn |
| sid-2   | mcd       | gcutjhq |
| sid-3   | starbucks | gcutjht |
+---------+-----------+---------+

我需要在 geohash 列上加入这两个数据框。我可以肯定地做一个简单的 equi-join,但是 users 数据框很大,包含数十亿行,并且 geohashes 很可能在 idvalue 内和跨 idvalue 重复。所以,我想知道是否有办法对 users 数据框中的唯一地理哈希和 shops 数据框中的地理哈希执行连接。如果我们能做到这一点,那么在结果数据框中复制商店条目以匹配地理哈希就很容易。

可能它可以通过 pandas udf 来实现,我将在 users.idvalue 上执行 groupby,通过仅获取 udf 中的 shops组中的第一行(因为组中的所有 id 无论如何都是相同的),并创建一个单行数据框。从逻辑上讲,这应该可以工作,但不确定性能方面,因为 udf(s) 通常比 spark 本机转换慢。欢迎任何想法。

【问题讨论】:

  • 您显示的示例用户 df 具有重复的行。如果您的实际数据是这种情况,那么您可以删除用户 df 中的重复行,然后执行联接
  • 其实只有 geohash 列有重复的条目,其余的都不同,所以我不能删除行。我更新了表格以消除混乱。
  • 因此您必须保留用户 df 中的所有行,并且您的商店数据没有重复的 geohashes。执行联接的方式是联接。问题已经简化为基本形式。

标签: dataframe pyspark


【解决方案1】:

您说您的用户数据框很大,并且“geohashes 可能会在 idvalue 内部和跨 idvalue 重复”。但是,如果您的商店数据框中可能存在重复的地理哈希,您没有提及。

如果后者没有重复的哈希,我认为一个简单的连接可以解决你的问题:

val userDf = Seq(("id-1",2,"gcutjjn"),("id-2",2,"gcutjjn"),("id-1",3,"gcutjjn"),("id-1",5,"gcutjht")).toDF("idvalue","day_of_week","geohash")
val shopDf = Seq(("sid-1","kfc","gcutjjn"),("sid-2","mcd","gcutjhq"),("sid-3","starbucks","gcutjht")).toDF("shop_id","shop_name","geohash")

userDf.show
+-------+-----------+-------+
|idvalue|day_of_week|geohash|
+-------+-----------+-------+
|   id-1|          2|gcutjjn|
|   id-2|          2|gcutjjn|
|   id-1|          3|gcutjjn|
|   id-1|          5|gcutjht|
+-------+-----------+-------+

shopDf.show
+-------+---------+-------+
|shop_id|shop_name|geohash|
+-------+---------+-------+
|  sid-1|      kfc|gcutjjn|
|  sid-2|      mcd|gcutjhq|
|  sid-3|starbucks|gcutjht|
+-------+---------+-------+

shopDf
    .join(userDf,Seq("geohash"),"inner")
    .groupBy($"geohash",$"shop_id",$"idvalue")
    .agg(collect_list($"day_of_week").alias("days"))
    .show
+-------+-------+-------+------+
|geohash|shop_id|idvalue|  days|
+-------+-------+-------+------+
|gcutjjn|  sid-1|   id-1|[2, 3]|
|gcutjht|  sid-3|   id-1|   [5]|
|gcutjjn|  sid-1|   id-2|   [2]|
+-------+-------+-------+------+

如果您的商店数据框中有重复的哈希值,一种可能的方法是从您的商店数据框中删除这些重复的哈希值(如果您的要求允许的话),然后执行相同的连接操作。

val userDf = Seq(("id-1",2,"gcutjjn"),("id-2",2,"gcutjjn"),("id-1",3,"gcutjjn"),("id-1",5,"gcutjht")).toDF("idvalue","day_of_week","geohash")
val shopDf = Seq(("sid-1","kfc","gcutjjn"),("sid-2","mcd","gcutjhq"),("sid-3","starbucks","gcutjht"),("sid-4","burguer king","gcutjjn")).toDF("shop_id","shop_name","geohash")

userDf.show
+-------+-----------+-------+
|idvalue|day_of_week|geohash|
+-------+-----------+-------+
|   id-1|          2|gcutjjn|
|   id-2|          2|gcutjjn|
|   id-1|          3|gcutjjn|
|   id-1|          5|gcutjht|
+-------+-----------+-------+

shopDf.show
+-------+------------+-------+
|shop_id|   shop_name|geohash|
+-------+------------+-------+
|  sid-1|         kfc|gcutjjn|  <<  Duplicated geohash
|  sid-2|         mcd|gcutjhq|
|  sid-3|   starbucks|gcutjht|
|  sid-4|burguer king|gcutjjn|  <<  Duplicated geohash
+-------+------------+-------+

//Dataframe with hashes to exclude:
val excludedHashes = shopDf.groupBy("geohash").count.filter("count > 1")
excludedHashes.show
+-------+-----+
|geohash|count|
+-------+-----+
|gcutjjn|    2|
+-------+-----+

//Create a dataframe of shops without the ones with duplicated hashes
val cleanShopDf = shopDf.join(excludedHashes,Seq("geohash"),"left_anti")
cleanShopDf.show
+-------+-------+---------+
|geohash|shop_id|shop_name|
+-------+-------+---------+
|gcutjhq|  sid-2|      mcd|
|gcutjht|  sid-3|starbucks|
+-------+-------+---------+

//Perform the same join operation
cleanShopDf.join(userDf,Seq("geohash"),"inner")
    .groupBy($"geohash",$"shop_id",$"idvalue")
    .agg(collect_list($"day_of_week").alias("days"))
    .show
+-------+-------+-------+----+
|geohash|shop_id|idvalue|days|
+-------+-------+-------+----+
|gcutjht|  sid-3|   id-1| [5]|
+-------+-------+-------+----+

提供的代码是用 Scala 编写的,但可以轻松转换为 Python。

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    如果您可以使用 pyspark SQL 选择不同的 geohash 并创建到临时表,这是一个想法。然后从这个表而不是数据框加入。

    【讨论】:

      猜你喜欢
      • 2018-03-08
      • 2020-03-31
      • 2017-11-02
      • 2016-09-16
      • 2021-11-06
      • 1970-01-01
      • 2018-03-16
      • 2021-07-25
      • 1970-01-01
      相关资源
      最近更新 更多