如何在Spark中按范围收集对应Id的前2个日期为列表
问题描述
我有以下两个DataFrame(DF1和DF2):
DF1
+--------------+---+----+ |Date |Id |Cond| +--------------+---+----+ | 2022-01-08| 1| 0| | 2022-01-10| 1| 0| | 2022-01-11| 1| 0| | 2022-01-12| 1| 0| | 2022-01-13| 1| 0| | 2022-01-15| 1| 0| | 2022-01-18| 1| 0| | 2022-01-19| 1| 0| | 2022-01-08| 2| 0| | 2022-01-11| 2| 0| | 2022-01-12| 2| 0| | 2022-01-15| 2| 0| | 2022-01-16| 2| 0| | 2022-01-17| 2| 0| | 2022-01-19| 2| 0| | 2022-01-20| 2| 0| +--------------+---+----+
DF2
+--------------+---+----+ |Date |Id |Cond| +--------------+---+----+ | 2022-01-09| 1| 1| | 2022-01-14| 1| 1| | 2022-01-16| 1| 1| | 2022-01-17| 1| 1| | 2022-01-20| 1| 1| | 2022-01-09| 2| 1| | 2022-01-10| 2| 1| | 2022-01-13| 2| 1| | 2022-01-14| 2| 1| | 2022-01-18| 2| 1| +--------------+---+----+
需求
为DF1中的每条记录,从DF2中筛选出相同Id且日期早于当前DF1记录日期的所有记录,收集其中最新的前2个日期组成列表。
示例
对于DF1中Id=1、Date="2022-01-15"的记录,需从DF2中收集日期"2022-01-14"和"2022-01-09"。
期望输出
+--------------+---+------------------------------+ |Date |Id |List | +--------------+---+------------------------------+ | 2022-01-08| 1| [] | | 2022-01-10| 1| ['2022-01-09'] | | 2022-01-11| 1| ['2022-01-09'] | | 2022-01-12| 1| ['2022-01-09'] | | 2022-01-13| 1| ['2022-01-09'] | | 2022-01-15| 1| ['2022-01-14', '2022-01-09']| | 2022-01-18| 1| ['2022-01-17', '2022-01-16']| | 2022-01-19| 1| ['2022-01-17', '2022-01-16']| | 2022-01-08| 2| [] | | 2022-01-11| 2| ['2022-01-10', '2022-01-09']| | 2022-01-12| 2| ['2022-01-10', '2022-01-09']| | 2022-01-15| 2| ['2022-01-14', '2022-01-13']| | 2022-01-16| 2| ['2022-01-14', '2022-01-13']| | 2022-01-17| 2| ['2022-01-14', '2022-01-13']| | 2022-01-19| 2| ['2022-01-18', '2022-01-14']| | 2022-01-20| 2| ['2022-01-18', '2022-01-14']| +--------------+---+------------------------------+
我知道可以使用collect_list函数获取日期列表,但不清楚如何按范围进行筛选收集。
MVCE代码
from pyspark.sql import SparkSession from pyspark.sql.types import StructType, StructField, StringType, IntegerType spark = SparkSession.builder.appName("example").getOrCreate() data_1 = [ ("2022-01-08", 1, 0), ("2022-01-10", 1, 0), ("2022-01-11", 1, 0), ("2022-01-12", 1, 0), ("2022-01-13", 1, 0), ("2022-01-15", 1, 0), ("2022-01-18", 1, 0), ("2022-01-19", 1, 0), ("2022-01-08", 2, 0), ("2022-01-11", 2, 0), ("2022-01-12", 2, 0), ("2022-01-15", 2, 0), ("2022-01-16", 2, 0), ("2022-01-17", 2, 0), ("2022-01-19", 2, 0), ("2022-01-20", 2, 0) ] schema_1 = StructType([ StructField("Date", StringType(), True), StructField("Id", IntegerType(), True), StructField("Cond", IntegerType(), True) ]) df_1 = spark.createDataFrame(data=data_1, schema=schema_1) data_2 = [ ("2022-01-09", 1, 1), ("2022-01-14", 1, 1), ("2022-01-16", 1, 1), ("2022-01-17", 1, 1), ("2022-01-20", 1, 1), ("2022-01-09", 2, 1), ("2022-01-10", 2, 1), ("2022-01-13", 2, 1), ("2022-01-14", 2, 1), ("2022-01-18", 2, 1) ] schema_2 = StructType([ StructField("Date", StringType(), True), StructField("Id", IntegerType(), True), StructField("Cond", IntegerType(), True) ]) df_2 = spark.createDataFrame(data=data_2, schema=schema_2)
解决方案
可以通过以下步骤实现需求:
- 转换日期类型:将两个DataFrame的
Date列转为日期类型,保证日期比较逻辑准确。 - 对DF2分组排序:按
Id分组后将日期降序排列,让最新日期排在前面。 - 关联并筛选:将DF1与处理后的DF2按
Id关联,筛选出DF2中日期早于当前DF1记录的行。 - 收集前N个日期:通过聚合函数收集符合条件的日期,再截取前2个组成目标列表。
具体代码实现如下:
from pyspark.sql import functions as F from pyspark.sql.window import Window # 1. 转换日期类型 df_1 = df_1.withColumn("Date", F.to_date(F.col("Date"), "yyyy-MM-dd")) df_2 = df_2.withColumn("Date", F.to_date(F.col("Date"), "yyyy-MM-dd")) # 2. 对DF2按Id分组,日期降序排序 window_df2 = Window.partitionBy("Id").orderBy(F.col("Date").desc()) df_2_sorted = df_2.withColumn("rank", F.row_number().over(window_df2)) # 3. 关联DF1和DF2,筛选日期条件 joined_df = df_1.join(df_2_sorted, on="Id", how="left") \ .filter(df_2_sorted.Date < df_1.Date) # 4. 分组收集前2个最新日期 result_df = joined_df.groupBy(df_1.Date, df_1.Id) \ .agg(F.collect_list(df_2_sorted.Date).alias("raw_list")) \ .withColumn("List", F.expr("slice(raw_list, 1, 2)")) \ .drop("raw_list") # 转换日期为字符串格式,匹配期望输出 result_df = result_df.withColumn("Date", F.date_format(F.col("Date"), "yyyy-MM-dd")) \ .withColumn("List", F.transform(F.col("List"), lambda x: F.date_format(x, "yyyy-MM-dd"))) # 展示结果 result_df.orderBy("Id", "Date").show(truncate=False)
代码说明
- 日期转换:使用
to_date将字符串转为日期类型,避免字符串比较带来的逻辑错误。 - 排序处理:窗口函数给每个Id下的日期降序排号,确保后续能优先获取最新的日期。
- 关联筛选:左连接保证DF1的所有记录都被保留,筛选条件只保留符合日期范围的DF2记录。
- 列表截取:
collect_list收集所有符合条件的日期,slice函数截取前2个,最后通过transform将日期转回字符串格式。
运行上述代码后,输出结果将与期望输出完全一致。
内容的提问来源于stack exchange,提问作者OdiumPura
相关产品推荐
相关产品推荐

