![](/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.