如何高效计算用户留存率

断了今生、忘了曾经 提交于 2019-12-02 06:11:43

如何高效计算用户留存率

简单介绍留存率的概念,说明数仓建设中对留存率计算的优化思路

什么是留存率

在互联网行业中,用户在某段时间内开始使用应用,经过一段时间后,仍然继续使用该应用的用户,被认作是留存用户。

留存率就是留存用户与全部用户的比值,计算公式

留存率 = 留存用户数 / 用户数 * 100%

比如昨天来了100个人,今天这100个人里面的60个人又来了,那么留存率就是60%。

留存率反应了一个产品的用户黏性,留存越高说明用户在使用这个产品之后,继续使用的概率越大。
在用户运营越来越重要的今天,留存率作为公司的重要指标,也越来越被重视起来。

留存率的口径

留存率有很多计算口径,适用于不同的分析场景。但都是要确定两个时间窗口,第一个时间段用来圈人,第二个时间段用来观察被圈的人有没有再次访问。一般来说看的比较多的有如下几种口径

口径名称 前一时间段 下一时间段
次日留存 1天 1天
次三日留存 1天 3天
次七日留存 1天 7天
次30日留存 1天 30天
周留存 7天 7天
月留存 30天 30天
自然月留存 上个自然月 下个自然月

此外游戏产品还很看重用户注册之后的第一日留存、第二日留存…一直到第7日留存,含义是第0天来的人,在第1天、第2天…第7天的留存,是一个不断下降的曲线,游戏策划的一大目标就是让这条曲线下降变慢一点。

可以看到留存率的计算口径众多,时间跨度广,如果再摊上用户数量巨大,计算起来就是一件费时费力的工作了。

留存率计算方式零: 在用到的地方计算

简单来说就是没有统一计算的底层留存表,数据开发者在需要计算留存的时候,自己把两块用户数据left join起来,然后计算留存。
这样好处是简单快速;缺点是有数据口径不统一的隐患,逻辑很难复用。只适用于初级的数仓报表建设。因为这种没有任何设计思路,这里就不再展开。

留存率计算方式一: 全量用户访问情况表

思路是很简单的,如果我们知道了每一个用户,在前一段时间的访问天数M,在后一段时间的访问天数N,那么留存率就是 sum(M>0 and N>0) / sum(M > 0) * 100%
而我们一般用到的时间段是可以枚举的:1、2、3、4、5、6、7、14、28、29、30、31。那么,如果我们有一张表包含了12 * 2 = 24个字段(前后要*2),就能方便计算留存率了。

但是,这种方法会遇到一个问题,就是未来是不知道的,在可以计算次日留存的时候,月留存还无法得到结果,总不能等到一个月之后才拿到次日留存吧。所以就遇到需要回刷的问题。在每一个时间点都需要回刷一次,假设关注12个时间点,一张表就相当于12张表的计算量。虽然看起来计算量大了,但是如果很多地方都需要用到,比起粗放的前一种方式还是提升了很多的。

留存率计算方式二: 用户访问情况字段

这是本篇文章主要想讲述的内容,它继承了上面保存用户访问情况的思想,计算量不会太大,口径也可以更加灵活。

假设我们可以把用户两个月的访问情况都保存在表里,1就是来了,0就是没来,两个月算62天,一天一个字段,就是62列。那么这份数据就可以完全覆盖上面从次日留存到月留存,不需要回刷。为什么呢,因为在最新日期的数据里面,可以看到用户在过去60天,每一天的访问情况,假设用户在60~30这30个日期来访是M,30~1这30个日期来访是N,那么依然可以通过公式sum(M>0 and N>0) / sum(M > 0) * 100%计算留存。

有人觉得62个字段太多了,放在一个表里不值得,这个完全是可以优化的,一个bigint有64位,64 - 62 = 2 还能剩余两位。所以我们只需要一个bigint,理论上就可以计算绝大多数的留存口径。

在这里先做一个约定,0代表没来,1代表来,从右到左第1到第62位,分别代表距今天0到61天,用户是否来访。

那么我们就可以定义一个UDF来计算任意口径用户来访天数,假设叫get_range, 这个函数需要4个信息

  • 用户访问情况: bigint
  • 制作用户访问情况的日期: string
  • 计算留存目标日期: string
  • 计算时间范围: int

简单示例如下

public int evaluate(final bigint info, final String baseDate, final String targetDate, int range) {
    int r = 0;
    int diff = datediff(baseDate, targetDate);
    if (diff >= 1 && diff <= 62  && range > 0){  // 保证没有越界
    	for (int i = 0; i < range && i <= diff; i++){
			r += ((info >> (diff - i)) & 1);
    	}
    }
    return r;
}

以计算次日留存为例, 就可以

get_range(info, '20190901', '20190831', 1) as M, 
get_range(info, '20190901', '20190901', 1) as N

然后 sum(M>0 and N>0) / sum(M > 0) * 100% 来计算留存了

总结一下,通过一个bigint来存储用户的访问情况,可以非常方便的计算用户的留存,但是也需要很好的工具链来保障这个bigint生成的正确性,对软实力要求很高。
如果不追求极致的性能,使用回刷的方式也可以达到方便计算的目的。最不推荐的就是在各个用到地方算一次留存,虽然看起来简单,但是最后维护最麻烦,使用的资源也随着时间越来越多。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!