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

Pyspark实现交易行按日展开并计算客户每日净余额

PySpark实现客户每日净余额计算方案

核心逻辑为预处理交易数据后生成客户维度的连续日期序列,关联交易后通过窗口函数累加计算余额,比全量复制交易行的方案性能更高。


步骤1:交易数据预处理

  • 统一客户ID大小写,将字符串格式的交易日期转换为日期类型
  • 按交易类型生成带符号的交易值:Deposit计为正,withdraw计为负
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
from pyspark.sql.window import Window

# 初始化SparkSession
spark = SparkSession.builder.appName("customer_daily_balance").getOrCreate()

# 样例原始交易数据,实际使用可替换为读取你的数据源逻辑
raw_data = [
    ("c1", "T1", "01-10-2021", "Deposit", 500),
    ("C1", "T2", "01-15-2021", "withdraw", 100),
    ("C1", "T3", "01-30-2021", "withdraw", 300),
    ("C1", "T4", "02-20-2021", "Deposit", 200),
    ("c2", "T1", "01-01-2021", "Deposit", 500),
    ("C2", "T2", "01-15-2021", "withdraw", 100),
    ("C3", "T1", "01-30-2021", "Deposit", 300),
    ("C3", "T2", "02-20-2021", "withdraw", 200)
]
df = spark.createDataFrame(raw_data, schema=["customerID", "TransactionID", "TransactionDate", "TransactionType", "TransactionAmount"])

# 预处理交易数据
trans_df = df \
    .withColumn("customerID", F.upper(F.col("customerID"))) \
    .withColumn("TransactionDate", F.to_date(F.col("TransactionDate"), "MM-dd-yyyy")) \
    .withColumn("trans_val", F.when(F.col("TransactionType") == "Deposit", F.col("TransactionAmount"))
                .otherwise(-F.col("TransactionAmount")))

步骤2:生成每个客户的连续自然日序列

  • 统计每个客户的首笔交易日期,结合当前日期生成每个客户需要计算的日期范围
  • 生成连续日期基准表,和客户ID关联得到全量「客户+日期」的基础计算表
# 获取每个客户的首交易日、当前日期
customer_date_range = trans_df.groupBy("customerID") \
    .agg(F.min("TransactionDate").alias("first_trans_date")) \
    .withColumn("current_date", F.current_date())

# 生成足够长的连续日期基准序列,max_days可根据业务时间跨度调整
max_days = 10000
date_seq = spark.range(max_days) \
    .withColumn("CalendarDay", F.date_add(F.to_date(F.lit("2020-01-01")), F.col("id").cast("int")))

# 关联得到每个客户从首交易日到当前日的所有自然日
customer_calendar = customer_date_range.join(date_seq, 
    (F.col("CalendarDay") >= F.col("first_trans_date")) & 
    (F.col("CalendarDay") <= F.col("current_date")),
    "inner"
).select("customerID", "CalendarDay")

步骤3:关联交易计算每日净余额

  • 连续日期表左关联单日交易汇总数据,无交易的日期交易值记为0
  • 用窗口函数按客户维度按日期累加,得到每日净余额
# 先聚合计算每个客户每日的总交易值
daily_trans_sum = trans_df.groupBy("customerID", "TransactionDate") \
    .agg(F.sum("trans_val").alias("daily_trans"))

# 关联连续日期表
daily_trans = customer_calendar.join(
    daily_trans_sum,
    (customer_calendar.customerID == daily_trans_sum.customerID) & 
    (customer_calendar.CalendarDay == daily_trans_sum.TransactionDate),
    "left"
).select(customer_calendar["customerID"], "CalendarDay", "daily_trans")

# 计算累计净余额,无交易的日期自动继承前一日余额
window_spec = Window.partitionBy("customerID").orderBy("CalendarDay").rowsBetween(Window.unboundedPreceding, 0)
result_df = daily_trans.withColumn("Netbalance", F.sum(F.coalesce("daily_trans", F.lit(0))).over(window_spec))

# 可选:将日期转换为样例中的字符串格式
result_df = result_df.withColumn("CalendarDay", F.date_format("CalendarDay", "MM-dd-yyyy"))

# 输出结果
result_df.orderBy("customerID", "CalendarDay").show()

补充说明

如果需要输出样例中带计算过程的余额说明(如400(500-100)),可以额外加字段拼接计算逻辑,根据业务需求自行扩展即可。

内容的提问来源于stack exchange,提问作者PysparkSan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 11:36:02