一种计算用户留存的方法

0x00 概述

用户留存分析是互联网时代常用的一种数据分析方法。而很多快速发展的公司并没有相应的方法论沉淀,这就导致了在计算用户留存的时候会出现下面的一些问题:1)用户留存的定义不明确,不同的研发有自己的理解;2)没有保留计算过程的中间表,数据可复用程度低;3)不同研发的开发习惯不同,导致计算过程和表设计不统一。

鉴于以上问题,本文将指出一种通用的用户留存定义,并提供通用的计算流程以及具体的表结构设计。

0x01 简介

用户留存在不同的业务场景有不同的定义方式,比如说用户注册留存和用户活跃留存等。虽说定义方式不同,但大致思路和计算方式基本相同,本文将以用户活跃留存为例进行说明。

用户留存分析一般会分析两个指标:用户留存数和用户留存率。以 2048年05月12日 的留存分析为例,

  • 20480512的次日留存数 = 20480512日活跃用户 交集 20480513日的活跃用户数

  • 20480512的次日留存率 = 20480512的次日留存数/20480512的总活跃数

在留存分析中,除了次日留存以外,最常用的分析指标是3日留存、7日留存和30日留存。其中7日留存和30日留存可以理解为是周留存和月留存。下面是具体的定义:

  • 3日留存率 = (第1日活跃的用户,在第3日还活跃的用户数)/第1日的活跃用户数
  • 7日留存率 = (第1日活跃的用户,在第7日还活跃的用户数)/第1日的活跃用户数
  • 30日留存率 = (第1日活跃的用户,在第30日还活跃的用户数)/第1日的活跃用户数

0x02 计算

首先说一下用户留存的计算难点在哪里。用户留存计算的时候,每天要刷新之前日期的数据,比如说今天数2048年5月30号,那我们今天要算出这几个指标:20480530当天活跃用户数,20480529的1日留存数,20480527的3日留存数,20480523的7日留存数,以及20480430的30日留存数。并且,这些数据要分别放到对应那一天的报表里面。这就要求我们每天的任务要更新历史某天的数据项。

基于上面说的难点,我们做如下设计。我们讲用户留存的计算设计分为三部分:数据流设计、表结构设计和通用代码设计。

一、数据流和表结构

数据流和表结构可以放在一起讨论,为了简单说明,我设计了最简单能说明观点的表结构。

如下图,是整个计算流程,我们设计了两张表:用户留存中间表和用户留存报表。注意一下用户留存表中的type字段,它表示的用户留存的类型,看注释可知每个取值的含义。

一种计算用户留存的方法

我们设计用户留存中间表的原因是这样可以将中间表提供给数据分析使用,方便用该表和各种数据做交集用户,因此保留了uid的粒度。

用户留存报表使用这种竖表的设计方式,主要是为了考虑每天要修改历史数据的原因。如果用横表(即多个字段分别表示用户当日活跃、1日留存、7日留存等数据),那每天就要重新刷新多天数据分区中的所有数据,使用竖表则会减少很多的计算量并且更容易维护,只需在报表系统中将竖表转成横表展示即可。

二、通用代码设计

按照上面的设计,我们可以设计一套十分简单的代码来计算留存,比如1日、3日、7日和30日留存,可以用同一套代码完成,只要传入参数不同即可。如下是留存中间表的核心代码逻辑,报表的逻辑类似就不再展示。

代码清单1:留存计算通用代码

def _rt_sql(cur_date, user_type):
    '''
    user_type:留存类型,0表示当天活跃,1表示次日留存
    '''
    start_date = dateDelta(cur_date, user_type*-1) # start_date 表示的n日留存的那一天的日期。
    sql = '''
        insert overwrite table 用户活跃天表 partition(p_{start_date}, p_{user_type})
        select 
            {start_date} as ds,
            {user_type} as user_type,
            p_0.uid
        from
            用户活跃天表 p_0 -- 当时的活跃
        join 
            用户留存中间表 p_now -- 今天活跃
        on 
            p_0.uid = p_now.uid
        where p_0.ds = {start_date} and p_0.type = 0
        and p_now.ds = {cur_date}
    '''.format(cur_date=cur_date, user_type=str(user_type), start_date=start_date)
    exec_sql(sql)  ## 封装了执行sql的代码

代码清单2:整体计算逻辑

def _run(cur_date):
    _user_act_sql(cur_date) ## 计算当日活跃的用户,代码就不再写了
    _rt_sql(cur_date, 1) ## 1日留存
    _rt_sql(cur_date, 3) ## 3日留存
    _rt_sql(cur_date, 7) ## 7日留存
    _rt_sql(cur_date, 30) ## 30日留存

OK,通用代码就是这么简单。整体也就是几十行代码的量。

0xFF 总结

用户留存是数据分析常用而且十分简单有效的一种分析方法,但是很多公司对于留存的定义和计算方式都没有形成自己的方法论。比如说留存的定义,有的数据分析理解为连续留存,以7日留存为例,那么7日留存的计算逻辑是第一日活跃,后7日内凡事有活跃的用户都算是7日留存。而另外一些数据分析就会用我们本文中提的用户点留存,即只看第7天当天十分活跃。 然后再辅助于用户流失和回流等指标一同分析。

计算逻辑同样如此,不同的研发在计算留存的时候也使用了不同的方式,这就导致了最终提供给业务方或者其它研发的表十分不宜用。因此本文指出一种常用且比较通用的用户留存计算方式,并提供一套可行的留存计算通用代码,旨在提高开发和数据分析效率,保证数据口径的一致和数据的易用。

**写在最后,**有两个问题可以考虑一下:

  1. 文中的代码逻辑有很多可以优化的地方,不知道有朋友发觉了吗?
  2. 另外,如果不用计算中间表,只计算最终的报表,有没有十分简洁的Sql实现?

更多文章欢迎关注公众号:木东居士

一种计算用户留存的方法