精讀《15 大 LOD 表示式 - 下》

黃子毅發表於2021-11-15

接著上一篇 精讀《15 大 LOD 表示式 - 上》 ,這次繼續總結 Top 15 LOD Expressions 這篇文章的 9~15 場景。

9. 某時間段內最後一天的值

如何實現股票平均每日收盤價與當月最後一天收盤價的對比趨勢圖?

如圖所示,要對比的並非是某個時間段,而是當月最後一天的收盤價,因此必須要藉助 LOD 表示式。

設想原表如下:

DateTickerAdj Close
29/08/2013SYMC$1
28/08/2013SYMC$2
27/08/2013SYMC$3

我們按照月進行聚合作為橫軸,求 avg([Adj Close]) 作為縱軸即可。但計算對比我們需要一個 Max Date 欄位如下:

DateTickerAdj CloseMax, Date
29/08/2013SYMC$129/08/2013
28/08/2013SYMC$229/08/2013
27/08/2013SYMC$329/08/2013

如果我們使用 max(Date) 表示式,在聚合後結果是可以看到 Max Date 的:

Month of DateTickerAvg, Adj CloseMax, Date
08/2013SYMC$229/08/2013

原因是,max(Date) 是一個聚合表示式,只能在 group by 聚合 sql 下生效。但如果我們要計算最後一天的收盤價,就要執行 sum([Close value on last day],表示式如下:

[Close value on last day] = if [Max Date] = [Date] then [Adj Close] else 0 end

但問題是,這個表示式計算的明細級別是以天為粒度的,我們 max(Date) 在天粒度下是算不出來的:

DateTickerAdj CloseMax, Date
29/08/2013SYMC$1
28/08/2013SYMC$2
27/08/2013SYMC$3

原因就是上面說過的,聚合表示式不能在非聚合的明細級別中出現。因此我們利用 { include : max([Date]) } 表示式就能輕鬆實現下面的效果了:

DateTickerAdj Close{ include : max([Date]) }
29/08/2013SYMC$129/08/2013
28/08/2013SYMC$229/08/2013
27/08/2013SYMC$329/08/2013

{ include : max([Date]) } 表示式沒有給定 include 引數,意味著永遠以當前檢視的明細級別計算,因此這個欄位下推到明細表做計算時,也可以出現在明細表的每一行。接著按照上面的思路組裝表示式即可。

擴充一下,如果橫軸我們按年進行聚合,那麼對比值就是每年最後一天的收盤價。原因是 { include : max([Date]) } 會以當前年這個粒度計算 max([Date]),自然是當年的最後一天,然後下推到明細表,整整一年 365 行資料中,[Close value on last day] 大概是這樣:

DateTickerAdj Close[Close value on last day]
31/12/2013SYMC$1$1
30/12/2013SYMC$2$1
............
03/01/2013SYMC$7$1
02/01/2013SYMC$8$1
01/01/2013SYMC$9$1

接著對比值按照 sum([Close value on last day]) 聚合即可。

10. 復購陣列

如下圖所示,希望檢視客戶第一次購買到第二次購買間隔季度的復購陣列:

關鍵在於如何求第一次與第二次購買的季度時間差。首先可以通過 [1st purchase] = { fixed [customer id] : min([order date]) } 計算每位客戶首次購買時間。

如何計算第二次購買時間?這裡有個小技巧。首先利用 [repeat purchase] = iif([order date] > [1st purchase], [order date], null) 得到一個新列,首次購買的那一行值為 null,我們可以利用 min 函式計算時忽略 null 的特性,得到第二次購買時間:[2nd purchase] = { fixed [customer id] : min([repeat purchase]) }

最後利用 datediff 函式得到間隔的季度數:[quarters repeat to purchase] = datediff('quarter', [1st prechase], [2nd purchase])

11. 範圍平均值差異百分比

如下圖所示,我們希望將趨勢圖的每個點,與選定區域(圖中兩個虛線範圍內)的均值做一個差異百分比,並生成一個新的折線圖放在上方。

重點是上面折線圖 y 軸欄位,差異百分比如何表示。首先我們要生成一個只包含指定區間的收盤值:

[Close value in reference period] = IF [Date] >= [Start reference date] AND [Date] <= [End reference date] THEN [Adj close] END,這段表示式只在日期在制定區間內時,才返回 [Adj close],也就是隻包含這個區間內的值。

第二步,計算制定區間的平均值,這個用 FIX 表示式即可:[Average daily close value between ref date] = { fixed [Ticker] : AVG([Close value in reference period]) }

第三步,計算百分比差異:[percent different from ref period] = ([Adj close] - [Average daily close value between ref date]) / [Average daily close value between ref date]

最後就是用 [percent different from ref period] 這個欄位繪製上面的圖形了。

12. 相對週期過濾

如果我們想對比兩個週期資料差異,可能會遇到資料不全導致的錯誤。比如今年 3 月份資料只產出到 6 號,但卻和去年 3 月整月的資料進行對比,顯然是不合理的。我們可以利用 LOD 表示式解決這個問題:

相對週期過濾的重點是,不能直接用日期進行對比,因為今年資料總是比去年大。比如因為今年最新資料到 11.11 號,那麼去年 11.11 號之後的資料都要被過濾掉。

首先找到最新資料是哪一天,利用不包含條件的 FIX 表示式即可:[max date] = { max([date]) }

然後利用 datepart 函式計算當前日期是今年的第幾天:

[day of year of max date] = datepart('dayofyear', [max date])[day of year of order date] = datepart('dayofyear', [order date])

所以 [day of year of max date] 就是一個卡點,任何超過今年這麼多天的資料都要過濾掉。因此我們建立一個過濾條件:[period filter] = [day of year of order date] <= [day of year of max date]

[period filter] 欄位作為篩選條件即可。

13. 使用者登陸頻率

如何繪製一個使用者每個月登陸頻率?

要計算這個指標,得用使用者總活躍時間除以總登陸次數。

首先計算總活躍時間:利用 FIX 表示式計算使用者最早、最晚的登陸時間:

  • [first login] = { fixed [user id] : min([log in date]) }
  • [last login] = { fixed [user id] : max([log in date]) }

計算其中月份 diff,就是使用者活躍月數:

[total months user is active] = datediff("month", [first login], [last login])

總登入次數比較簡單,也是固定使用者 ID 後,對登陸日期計數即可:

[numbers of logins per user] = { fixed [user id] : count([login date]) }

最後,我們用兩者相除,得到使用者登陸頻率:

[login frequency] = [total months user is active] / [numbers of logins per user]

製作圖表就很簡單了,把 [login frequency] 移到橫軸,count distinct 使用者 ID 作為縱軸即可。

14. 比例筆刷

這個是 LOD 最常見的場景,比如求各品類銷量佔此品類總銷量的貢獻佔比?

sum(sales) / sum({ fixed [category] : sum(sales) }) 即可。

當前詳細級別是 category + country,我們固定品類,就可以得到各品類在所有國家的累積銷量。

15. 按客戶群劃分的年度購買頻率

如何證明老客戶忠誠度更高?

我們可以如下圖,按照客戶群(2011 年、2012 年客戶)作為圖例,觀察他們每年購買頻次分佈。

如上圖所示,我們發現顧客註冊時間越早,各購買頻次的比例都更高,所以證明了老顧客忠誠度更高這一結論。注意這裡看的是至少購買 N 次,所以每條線相比才具有說服力。如果是購買 N 次,則可能老顧客購買 1 次較少,購買 10 次較多,難以直接對比。

首先我們生成圖例欄位,即按最早照購買年份劃分顧客群:[Cohort] = { fixed [customer id] : min(Year([order date])) }

然後就和我們第一個例子類似,計算每個訂單數量下,有多少顧客。唯一的區別是,我們不僅按照顧客 ID group,還要進一步對最早購買日期做拆分,即:{ fixed [customer id], [Cohort] : count([order id]) }

上面的欄位作為 X 軸,Y 軸和第一個例子類似:count(customer id),但我們想檢視的是至少購買 N 次,也就是這個購買次數是累計值,即至少購買 9 次 = 購買 9 次 + 購買 10 次 + ... 購買 MAX 次。所以是一種 DESC 的 windowsum,整體表示式應該類似 [Running Total] = WINDOW_SUM(count(customer id)), 0, LAST())

最後,因為實際 Y 軸計算的是佔比,所以用剛才計算的至少購買 N 次指標除以各 Cohort 下總購買次數,即 [Running Total] / sum({ fixed [Cohort] : count([customer id]) })

總結

上面的幾個例子,都是基於 fixed、include、exclude 這幾個基本 LOD 用法的疊加。但從實際例子來看,我們會發現真正的難點不在與 LOD 表示式的語法,而在於我們如何精確理解需求,拆解成合理的計算步驟,並在需要執行 LOD 的計算步驟正確的使用。

LOD 表示式看上去很神奇,似乎可以和資料 “神奇” 的貼合在一起,我們要理解到 LOD 背後就是表之間的 join,而不同明細級別就表示不同的 group by 規則這一背後原理,就能比較好的理解為什麼 LOD 表示式能這麼運作了。

討論地址是:精讀《15 大 LOD 表示式 - 下》· Issue #370 · dt-fe/weekly

如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公眾號

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章