![](/img/trans.png)
[英]aggregation with lambda function for last 30 days with python
[英]OOM using Spark window function with 30 days interval
我有这个数据框:
df = (
spark
.createDataFrame([
[20210101, 'A', 103, "abc"],
[20210101, 'A', 102, "def"],
[20210101, 'A', 101, "def"],
[20210102, 'A', 34, "ghu"],
[20210101, 'B', 180, "xyz"],
[20210102, 'B', 123, "kqt"]
]
).toDF("txn_date", "txn_type", "txn_amount", "other_attributes")
)
每个日期都有多个不同类型的交易。 我的任务是计算每条记录的金额标准差(对于相同的类型并返回 30 天)。
最明显的方法(我尝试过)是根据类型创建一个 window 并包括可以追溯到过去 30 天的记录。
days = lambda i: i * 86400
win = Window.partitionBy("txn_type").orderBy(F.col("txn_date").cast(LongType())).rangeBetween(-days(30), 0)
df = df.withColumn("stddev_last_30days", F.stddev(F.col("txn_amount")).over(win))
由于某些交易类型每天有数百万笔交易,这会导致 OOM。
我尝试分部分进行(一次只为每个日期记录少量记录),但这会导致计算容易出错,因为标准偏差不是相加的。
我还为交易类型和日期的所有记录尝试了“collect_set”(因此所有金额都以数组形式出现在一个列中),但这也会遇到 OOM。
我尝试一次处理一个月(我需要至少 2 个月的数据,因为我需要 go 回到 1 个月)但即使这样也让我的执行者不知所措。
解决此问题的可扩展方法是什么?
笔记:
在原始数据中,列txn_date
以“yyyyMMdd”格式存储。
数据框中的其他列对于每个日期和类型可能相同也可能不同。 为简单起见,我没有将它们包含在示例代码中。
过滤
删除不需要的数据总是好的。 你说你只需要最后 60 天,所以你可以filter
掉不需要的东西。
此行将仅保留日期不早于最后几天(直到今天)的行:
df = df.filter(F.to_date('txn_date', 'yyyyMMdd').between(F.current_date()-61, F.current_date()))
我现在不会使用它来说明其他问题。
Window
第一个简单的事情,如果它已经是长格式,你不需要再次转换为 long,所以我们可以删除.cast(LongType())
。
另一件大事是你的窗口的下限是错误的。 看,让我们在输入中再添加一行:
[19990101, 'B', 9999999, "xxxxxxx"],
该行表示 1999 年的日期。添加该行后,运行代码,我们得到:
# +--------+--------+----------+----------------+------------------+
# |txn_date|txn_type|txn_amount|other_attributes|stddev_last_30days|
# +--------+--------+----------+----------------+------------------+
# |20210101| A| 103| abc| 1.0|
# |20210101| A| 102| def| 1.0|
# |20210101| A| 101| def| 1.0|
# |20210102| A| 34| ghu|34.009802508492555|
# |19990101| B| 9999999| xxxxxxx| null|
# |20210101| B| 180| xyz| 7070939.82553808|
# |20210102| B| 123| kqt| 5773414.64605055|
# +--------+--------+----------+----------------+------------------+
您可以看到 2021 年行的 stddev 也受到了影响,因此 30 天 window 不起作用,您的 window 实际上可以获取所有数据。 我们可以检查日期20210101
的下限是多少:
print(20210101-days(30)) # Returns 17618101 - I doubt you wanted this date as lower bound
可能这是你最大的问题。 你永远不应该试图超越日期和时间。 始终使用专门用于日期和时间的函数。
您可以使用这个 window:
days = lambda i: i * 86400
w = Window.partitionBy('txn_type').orderBy(F.unix_timestamp(F.col('txn_date').cast('string'), 'yyyyMMdd')).rangeBetween(-days(30), 0)
df = df.withColumn('stddev_last_30days', F.stddev('txn_amount').over(w))
df.show()
# +--------+--------+----------+----------------+------------------+
# |txn_date|txn_type|txn_amount|other_attributes|stddev_last_30days|
# +--------+--------+----------+----------------+------------------+
# |20210101| A| 103| abc| 1.0|
# |20210101| A| 102| def| 1.0|
# |20210101| A| 101| def| 1.0|
# |20210102| A| 34| ghu|34.009802508492555|
# |19990101| B| 9999999| xxxxxxx| null|
# |20210101| B| 180| xyz| null|
# |20210102| B| 123| kqt| 40.30508652763321|
# +--------+--------+----------+----------------+------------------+
unix_timestamp
可以将您的 'yyyyMMdd' 格式转换为适当的长格式数字(UNIX 时间以秒为单位)。 从此,现在您可以减去秒数(相当于 30 天的秒数)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.