[英]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_date
和duration
,並且還將示例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|
+-----------+-------------------------+----+-----+-----+-----+-----+-----+-----+
啊! 這部分不優雅
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.