老板:白茶,你说咱这行业咋就留不住人呢? 白茶:(黑人问号??)老板,你说的是没有回头客么? 老板:对对对,就是这个意思,能不能搞一个报表,让我知道顾客是在哪个阶段流失的? 白茶:唔...问题不大!
对于很多新兴行业来说,用户留存都是一个比较突出的问题。因为用户就代表着市场的占有率,也代表商业的大盘,盘子越大,能产生的价值也越高,因此越来越多的企业开始重视用户留存的问题。
本期咱们来聊一聊用户留存的问题,这个问题对于零售快销、电商行业、以及游戏行业都具有很高的参考价值。
先来看看本期的案例数据:
数据非常的简单,只有基础的日期列、用户ID、产品以及售卖金额。
解释一下什么叫留存:
简而言之,顾客今天在我这里买东西了,明天依然来我这里买,那么这两天对于此用户就产生了留存的概念;
玩家今天登录客户端选择了打游戏,明天依然登录打游戏,这个也是用户留存;
浏览者今天观看我直播,明天依然选择观看,这个还是留存。
对于大部分企业来说,这个概念习惯称之为用户留存,但是从互联网的角度来说,称之为流量也可以。
用户留存,我们需要对用户的数量进行统计,统计每个周期内的新客数量以及新客持续购买的数量。
编写基础的DAX函数:
代码语言:txt复制C.CustomerNumber =
DISTINCTCOUNTNOBLANK ( Fact_Sale[用户ID] )
有了基础的计算指标,我们的思路可以扩展一下,用户留存我们观测的不是某一个具体的时间点,而是一个阶段,比如我选择2021年8月1日,我更希望看到的是8月1日之前一段时间的数据。
因此我们的模型关系如下:
建立两张日期表,一张建立关系,一张不建立关系。
编写如下度量值:
阶段结束时间:
代码语言:txt复制A.EndNode =
SELECTEDVALUE ( Dim_Date_II[Date] )
建立参数:
注:也可以不建立参数, 这里是为了扩展性考虑,可以自由的筛选展示阶段。
阶段开始时间:
代码语言:txt复制B.InitialNode =
TOPN (
1,
FILTER ( ALL ( Dim_Date[Date] ), [Date] <= [A.EndNode] - [Period] ),
'Dim_Date'[Date], DESC
)
阶段内用户数量:
代码语言:txt复制D.CurrentCustomerNumber =
VAR CurrentCustomerNumber =
SUMMARIZE (
'Fact_Sale',
'Fact_Sale'[用户ID],
Dim_Date[Date],
"@CUSTOMER", [C.CustomerNumber]
)
RETURN
SUMX (
FILTER (
CurrentCustomerNumber,
[Date] >= [B.InitialNode]
&& [Date] <= [A.EndNode]
),
[@CUSTOMER]
)
效果如下:
这样一个可以展示阶段的度量值就准备完毕了。
那么,我们现在需要考虑用户留存的问题,我们需要知道在当日购买的用户,次日或者第三日依然选择购买的用户有多少。
编写如下度量值:
代码语言:txt复制E.FirstDateCustomerNumber =
VAR FirstDateCustomerNumberTable =
ADDCOLUMNS (
SUMMARIZE ( 'Fact_Sale', 'Fact_Sale'[用户ID], Dim_Date[Date] ),
"@FirstDateCustomerNumber",
VAR FirstDateKey = [Date] 1
RETURN
CALCULATE ( [C.CustomerNumber], 'Dim_Date'[Date] = FirstDateKey )
)
VAR FirstDateCustomerNumber =
SUMX (
FirstDateCustomerNumberTable,
IF (
[Date] >= [B.InitialNode]
&& [Date] <= [A.EndNode] - 1,
[@FirstDateCustomerNumber]
)
)
RETURN
IF (
[Period] >= 1,
FirstDateCustomerNumber ,
BLANK ()
)
解释一下代码含义:
通过定义一张虚拟表,来减少性能的损耗;
添加“@FirstDateCustomerNumber”虚拟列,来计算次日依然购买的新客数量;
“@FirstDateCustomerNumber”虚拟列中使用VAR定义了一个来自虚拟表上下文的变量,通过内部上下文覆盖外部上下文的方式,实现次日人数的计算;
DAX中的部分数字是为了计算间隔天数,部分是为了阶梯式呈现;
最后结果输出,参照上面的逻辑,我们继续构建其他度量值。
代码语言:txt复制F.SecondDateCustomerNumber =
VAR SecondDateCustomerNumberTable =
ADDCOLUMNS (
SUMMARIZE ( 'Fact_Sale', 'Fact_Sale'[用户ID], Dim_Date[Date] ),
"@SecondDateCustomerNumber",
VAR SecondDateKey = [Date] 2
RETURN
CALCULATE ( [C.CustomerNumber], 'Dim_Date'[Date] = SecondDateKey )
)
VAR SecondDateCustomerNumber =
SUMX (
SecondDateCustomerNumberTable,
IF (
[Date] >= [B.InitialNode]
&& [Date] <= [A.EndNode] - 2,
[@SecondDateCustomerNumber]
)
)
RETURN
IF (
[Period] >= 2,
SecondDateCustomerNumber ,
BLANK ()
)
与上面度量值的差异就是数字参数不同。
这样的度量值,白茶一共构建了7个,假定留存周期7日为一个周期。
我们来看一下效果:
有一点小瑕疵,当某一天没有值的时候,该度量值白茶希望它消失。
添加一张展示使用的维度表:
编写如下度量值:
代码语言:txt复制L.DisplayNumber =
VAR DisplayIndex =
SELECTEDVALUE ( Dim_Display[Index] )
RETURN
SWITCH (
TRUE (),
DisplayIndex = 1, [D.CurrentCustomerNumber],
DisplayIndex = 2, [E.FirstDateCustomerNumber],
DisplayIndex = 3, [F.SecondDateCustomerNumber],
DisplayIndex = 4, [G.ThirdDateCustomerNumber],
DisplayIndex = 5, [H.FourthDateCustomerNumber],
DisplayIndex = 6, [I.FifthDateCustomerNumber],
DisplayIndex = 7, [J.SixthDateCustomerNumber],
DisplayIndex = 8, [K.SeventhDateCustomerNumber],
BLANK ()
)
我们再来看一下效果:
这样的话我们就解决了度量值空值呈现的问题。
我们不光想知道每日留存的客户数量,我们还想知道留存率,继续编写度量值。
代码语言:txt复制M.DisplayRetention =
IF (
[L.DisplayNumber] <> BLANK (),
IF (
SELECTEDVALUE ( Dim_Display[Index] ) = 1,
FORMAT ( [L.DisplayNumber], "0" ),
FORMAT ( [L.DisplayNumber] / [D.CurrentCustomerNumber], "0.00%" )
),
BLANK ()
)
效果如下:
这样用户留存的核心模型就已经搭建出来了。
将这个故事完善一下,我们需要知道当前完整周期的时间段、完整周期的新客数量、周期结束用户留存的数量。
编写如下度量值:
完整周期的时间段:
代码语言:txt复制O.FirstCycle =
FORMAT (
IF (
MINX ( ALL ( Dim_Date[Date] ), [Date] ) >= [A.EndNode] - [Period],
MINX ( ALL ( Dim_Date[Date] ), [Date] ),
[A.EndNode] - [Period]
),
"YYYY-MM-DD"
) & "~"
& FORMAT ( [A.EndNode], "YYYY-MM-DD" )
完整周期的新客数量:
代码语言:txt复制P.BeginCycleNumber =
CALCULATE (
[D.CurrentCustomerNumber],
FILTER (
ALLSELECTED ( 'Dim_Date' ),
'Dim_Date'[Date]
= IF (
MINX ( ALL ( Dim_Date[Date] ), [Date] ) >= [A.EndNode] - [Period],
MINX ( ALL ( Dim_Date[Date] ), [Date] ),
[A.EndNode] - [Period]
)
)
)
周期结束用户留存的数量:
代码语言:txt复制Q.EndCycleNumber =
LASTNONBLANKVALUE ( ALLSELECTED ( Dim_Display[Index] ), [L.DisplayNumber] )
效果如下:
为了美观,白茶添加了一个配色的度量值:
代码语言:txt复制N.DisplayRetentionColor =
VAR CurrentValue =
IF (
SELECTEDVALUE ( Dim_Display[Index] ) = 1,
[L.DisplayNumber],
[L.DisplayNumber] / [D.CurrentCustomerNumber]
)
VAR Color =
IF (
SELECTEDVALUE ( Dim_Display[Index] ) = 1,
"#0D2248",
IF (
[M.DisplayRetention] <> BLANK (),
SWITCH (
TRUE (),
CurrentValue >= 0.8, "#9C0A0D",
CurrentValue >= 0.6, "#C91014",
CurrentValue >= 0.4, "#E64B47",
CurrentValue >= 0.2, "#FE8664",
CurrentValue >= 0, "#FFD2A0"
)
)
)
RETURN
Color
对报表的整体进行调整,最终效果如下:
闲聊几句:
其实这个分析模型还是可以继续扩展的,如果我们可以拿到回访信息,那么搭配每个阶段的留存率,是可以直接分析出流失的主要原因;
针对游戏行业,此模型还可以计算连续活跃天数以及最终活跃,扩展度非常的高。
由于时间关系,本期就到这里了,喜欢的小伙伴可以自行模拟扩展。
小伙伴们❤GET了么?
(BOSS:哎,还是好难受。)
这里是白茶,一个PowerBI的初学者。