簡體   English   中英

通過評估表達式連接兩個 spark 數據幀

[英]Join two spark dataframes by evaluating an expression

我有兩個火花數據框

userMemberShipDF:

用戶 會員數組
a1 s1、s2、s3
a2 s4, s6
a3 s5、s4、s3
a4 s1,s3,s4,s5
a5 s2、s4、s6
a6 s3、s7、s1
a7 s1、s4、s6

和 categoryDF 與

類別ID 會員表達式 開始日期 期間
c1 s1 || s2 2022-05-01 30
c2 s4 && s6 && !s2 2022-06-20 50
c3 s3 && s4 2022-06-10 60

結果數據框包含user, category_id, start_date, duration

我已經編寫了一個 function ,它將從第二個數據幀中獲取membership_expression 以及第一個dataframe 中的membership_array 並評估為真或假。

例如membership_expression = s1 || s2 將匹配所有用戶 a1、a4、a5、a6 和 a7,而表達式s4 && s6 !s2將僅匹配 a2 等。

我想根據此表達式的計算結果為真或假來加入兩個數據框。 我查看了火花連接,它只會將列作為連接條件,而不是 boolean 表達式。

所以我嘗試了以下方法

val matchedUserSegments = userMemberShipDF
      .map { r =>
        {
        // categoryDF is broadcasted
          val category_items_set = categoryDF.value.flatMap { fl =>
            {
              if (CategoryEvaluator.evaluateMemberShipExpression(fl.membership_expression, r.membership_array)) {
                Some(fl.category_id)
              } else {
                None
              }
            }
          }
          (r.user_id, category_items_set)
        }
      }
      .toDF("user_id", "category_items_set")

然后在 category_items_set 上分解生成的 dataframe ,然后在 categoryDF 上加入,得到所需的 output 表。

我知道我做了兩次操作,但找不到更好的方法來通過迭代兩個數據幀來計算所有內容。

請提出一種有效的方法來做到這一點。

我有很多數據,而 spark 工作需要 24 小時以上才能完成。 謝謝

PS:為簡單起見,我沒有包含start_dateduration ,並且還將示例user行限制為a1, a2, a3, a4 此處顯示的 Output 可能與您預期的 output 不完全匹配; 但如果你使用完整的數據,我相信輸出會匹配。

import pyspark.sql.functions as F

userMemberShipDF = spark.createDataFrame([
    ("a1",["s1","s2","s3"]),
    ("a2",["s4","s6"]),
    ("a3",["s5","s4","s3"]),
    ("a4",["s1","s3","s4","s5"]),
], ["user","membership_array"])

如果用戶具有該成員資格,則將每個成員資格 s1、s2、s3 等轉換為單獨的列並標記為true

userMemberShipDF = userMemberShipDF.withColumn("membership_individual", F.explode("membership_array"))
+----+----------------+---------------------+
|user|membership_array|membership_individual|
+----+----------------+---------------------+
|  a1|    [s1, s2, s3]|                   s1|
|  a1|    [s1, s2, s3]|                   s2|
|  a1|    [s1, s2, s3]|                   s3|
|  a2|        [s4, s6]|                   s4|
|  a2|        [s4, s6]|                   s6|
|  a3|    [s5, s4, s3]|                   s5|
|  a3|    [s5, s4, s3]|                   s4|
|  a3|    [s5, s4, s3]|                   s3|
|  a4|[s1, s3, s4, s5]|                   s1|
|  a4|[s1, s3, s4, s5]|                   s3|
|  a4|[s1, s3, s4, s5]|                   s4|
|  a4|[s1, s3, s4, s5]|                   s5|
+----+----------------+---------------------+


userMemberShipDF = userMemberShipDF.groupBy("user").pivot("membership_individual").agg(F.count("*").isNotNull()).na.fill(False)
+----+-----+-----+-----+-----+-----+-----+
|user|   s1|   s2|   s3|   s4|   s5|   s6|
+----+-----+-----+-----+-----+-----+-----+
|  a3|false|false| true| true| true|false|
|  a4| true|false| true| true| true|false|
|  a2|false|false|false| true|false| true|
|  a1| true| true| true|false|false|false|
+----+-----+-----+-----+-----+-----+-----+

在類別數據中,替換|| , && , ! or , and , not :

categoryDF = spark.createDataFrame([
    ("c1", "s1 || s2"),
    ("c2", "s4 && s6 && !s2"),
    ("c3", "s3 && s4"),
], ["category_id", "membership_expression"])

categoryDF = categoryDF.withColumn("membership_expression", F.regexp_replace("membership_expression", "\|\|", " or "))
categoryDF = categoryDF.withColumn("membership_expression", F.regexp_replace("membership_expression", "\&\&", " and "))
categoryDF = categoryDF.withColumn("membership_expression", F.regexp_replace("membership_expression", "\!", " not "))

+-----------+-------------------------+
|category_id|membership_expression    |
+-----------+-------------------------+
|c1         |s1  or  s2               |
|c2         |s4  and  s6  and   not s2|
|c3         |s3  and  s4              |
+-----------+-------------------------+

交叉連接用戶和類別數據以針對每個類別評估每個用戶:

resultDF_sp = categoryDF.crossJoin(userMemberShipDF)
+-----------+-------------------------+----+-----+-----+-----+-----+-----+-----+
|category_id|membership_expression    |user|s1   |s2   |s3   |s4   |s5   |s6   |
+-----------+-------------------------+----+-----+-----+-----+-----+-----+-----+
|c1         |s1  or  s2               |a3  |false|false|true |true |true |false|
|c1         |s1  or  s2               |a4  |true |false|true |true |true |false|
|c1         |s1  or  s2               |a2  |false|false|false|true |false|true |
|c1         |s1  or  s2               |a1  |true |true |true |false|false|false|
|c2         |s4  and  s6  and   not s2|a3  |false|false|true |true |true |false|
|c2         |s4  and  s6  and   not s2|a4  |true |false|true |true |true |false|
|c2         |s4  and  s6  and   not s2|a2  |false|false|false|true |false|true |
|c2         |s4  and  s6  and   not s2|a1  |true |true |true |false|false|false|
|c3         |s3  and  s4              |a3  |false|false|true |true |true |false|
|c3         |s3  and  s4              |a4  |true |false|true |true |true |false|
|c3         |s3  and  s4              |a2  |false|false|false|true |false|true |
|c3         |s3  and  s4              |a1  |true |true |true |false|false|false|
+-----------+-------------------------+----+-----+-----+-----+-----+-----+-----+

評估membership_expression

啊! 這部分不優雅

Spark 提供expr function來使用列值評估 SQL 表達式; 但這僅在表達式是 static 字符串時才有效:

resultDF_sp.select(F.expr("s1 or s2"))

但如果表達式"s1 or s2"是一個列值(如上面的membership_expression列),則無法對其進行評估。 這導致錯誤Column is not iterable

resultDF_sp.select(F.expr(F.col("membership_expression")))

關於這個,stackoverflow 有幾個問題; 但他們都建議解析表達式,並編寫一個評估器來手動評估解析的表達式:

幸運的是,可以使用其他列的參數值將表達式計算為列值。

所以,我不喜歡的部分; 但別無選擇是將 dataframe 轉換為 pandas,評估表達式並轉換回火花(如果有人可以建議如何在火花中實現這一點,我很樂意將其包含在編輯中):

resultDF_pd = resultDF_sp.toPandas()

def evaluate_expr(row_series):
    df = row_series.to_frame().transpose().infer_objects()
    return df.eval(df["membership_expression"].values[0]).values[0]

resultDF_pd["is_matching_user"] = resultDF_pd.apply(lambda row: evaluate_expr(row), axis=1)
resultDF_sp = spark.createDataFrame(resultDF_pd[["category_id", "user", "is_matching_user"]])
+-----------+----+----------------+
|category_id|user|is_matching_user|
+-----------+----+----------------+
|         c1|  a3|           false|
|         c1|  a4|            true|
|         c1|  a2|           false|
|         c1|  a1|            true|
|         c2|  a3|           false|
|         c2|  a4|           false|
|         c2|  a2|            true|
|         c2|  a1|           false|
|         c3|  a3|            true|
|         c3|  a4|            true|
|         c3|  a2|           false|
|         c3|  a1|           false|
+-----------+----+----------------+

最后,過濾匹配的用戶:

resultDF_sp = resultDF_sp.filter("is_matching_user")
+-----------+----+----------------+
|category_id|user|is_matching_user|
+-----------+----+----------------+
|         c1|  a4|            true|
|         c1|  a1|            true|
|         c2|  a2|            true|
|         c3|  a3|            true|
|         c3|  a4|            true|
+-----------+----+----------------+

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM