You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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)

解决方案

可以通过以下步骤实现需求:

  1. 转换日期类型:将两个DataFrame的Date列转为日期类型,保证日期比较逻辑准确。
  2. 对DF2分组排序:按Id分组后将日期降序排列,让最新日期排在前面。
  3. 关联并筛选:将DF1与处理后的DF2按Id关联,筛选出DF2中日期早于当前DF1记录的行。
  4. 收集前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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 23:50:28