IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    MongoDb 用 mapreduce 统计留存率

    金庆发表于 2015-11-10 09:22:00
    love 0
    MongoDb 用 mapreduce 统计留存率

    (金庆的专栏)

    留存的定义采用的是
    新增账号第X日:某日新增的账号中,在新增日后第X日有登录行为记为留存

    输出如下:(类同友盟的留存率显示)
    留存用户
    注册时间    新增用户  留存率
                          1天后   2天后   3天后   4天后   5天后  6天后  7天后  14天后  30天后
    2015-09-17  2300      20.7 %  15.6 %  13 %    11.3 %  9.9 %               
    2015-09-18  2694      21.8 %  14.8 %  11.5 %  10.5 %                  
    2015-09-19  3325      19 %    11.4 %  10.3 %                      
    2015-09-20  3093      16.2 %  11.9 %                          
    2015-09-21  2303      20.5 %                              


    服务器记录新建帐号到 retention.register 集合,
    每日记录帐号登录到 retention.login 集合,
    每日运行统计脚本,统计前一天的留存率。

    以下为 mongoDB 留存率相关的集合,
    除了 retention.register 和 retention.login 由服务器代码写入,
    其他集合都是由统计脚本生成。

    retention.register
    ========================
    留存率统计用,新建帐号。
    记录新建帐号的创建日期。
    有以下字段:
    platform, 平台名
    account_id, 帐号
    date, 注册日期,字符串,格式:“2015-01-01”
    例如: {platform: "baidu", account_id: "jinqing", date: "2015-09-20"}
    索引 (platform, account_id), (date)
    用于统计每日新增帐号数。

    retention.login
    ==================
    留存率统计用,帐号登录记录。
    有以下字段:
    date, 登录日期
    platform, 平台名
    account_id, 帐号
    register_date, 帐号注册日期
    例如:{date: "2015-09-23", platform: "baidu", account_id: "jinqing", register_date: "2015-09-20"}
    索引 (date, platform, account_id).

    retention.result
    ===================
    留存率结果。例如:
    {date : "2015-09-01", register : 3344, 1 : 91.1, 2 : 82.2, 3 : 73.3, 4 : 64.4, 5 : 55.5, 6 : 46.6, 7 : 37.7, 14 : 14.0, 30 : 3.33}
    {date : "2015-09-02", register : 3344, 1 : 91.1, 2 : 82.2, 3 : 73.3, 4 : 64.4, 5 : 55.5, 6 : 46.6, 7 : 37.7, 14 : 14.0, 30 : 3.33}
    可用 mongoexport 导出为 csv 表格文件。
    例如:
    D:\mongodb\bin>mongoexport -h localhost -d mydb -c retention.result -f date,register,1,2,3,4,5,6,7,14,30 --csv -o d:\temp\retention.csv
    其中
    date: 注册日期
    register: 新注册个数
    1,2,...7,14,30: 第1日,2日,... 7日,14日,30日留存百分率


    留存率统计脚本
    --------------
    linux下用crontab,
    windows下用定时任务,
    每日凌晨 00:30 运行统计脚本。

    允许隔了几天没运行,运行时将从上次运行处一直统计到当天。
    如果是首次运行,则从 retention.register 集合的最早日期开始统计。
    一天运行多次也不会影响结果。
    但是不能同时运行多个实例。

    需 mongo 客户端。
    可在 mongo 主机上运行。

    mongo my.mongo.host retention.js
    生成结果在 mydb.retention.result 集合中,可用 mongoexport 导出为 csv 文件。


    #!/bin/sh
    # retention.sh
    # 每日凌晨定时执行,统计留存率。
    # 需 mongo 客户端。

    # 以下需更改为实际目录, 将在该目录下运行。
    cd 
    /home/jinq/retention/

    # 以下地址应该改为 mongod 服务器地址。
    MONGODB
    =192.168.8.9

    mongo ${MONGODB} retention.js 
    >> log.txt

    echo Mongo export retention result
    mongoexport 
    -h ${MONGODB} -d mydb -c retention.result \
      
    --sort '{"value.date" : 1}' \
      
    -f value.date,value.register,value.1,value.2,value.3,value.4,value.5,value.6,value.7,value.14,value.30 \
      
    --type=csv -o retention_tmp.csv
      
    DATE
    =`date +%Y%m%d`
    FILE
    =retention_${DATE}.csv

    # csv替换列头
    echo 日期,注册数,1日,2日,3日,4日,5日,6日,7日,14日,30日 
    > ${FILE}
    tail 
    -n +2 retention_tmp.csv >> ${FILE}

    echo Done ${FILE}
    !


    // 留存率统计脚本
    //
     参考文档:留存率统计.txt
    //
     Usage:
    //
     mongo my.mongo.host retention.js

    print(Date());
    db 
    = db.getSisterDB("mydb");  // use mydb

    var startDate = getStartDate();
    var endDate = formatDate(new Date());
    print(
    "Calculating retention rate of [" + startDate + ", " + endDate + ")");
    if (startDate < endDate) {
        insertDefaultResult(startDate);
        calcRegisterCount(startDate);
        calcRetention(startDate);
        print(Date());
        print(
    "Done.");
    } 
    else {
        print(
    "Do nothing.");
    }

    // Internal functions.

    // 获取统计开始日期,之前的已经统计完成,无需重做。
    //
     返回字符串,格式:"2015-01-01"
    //
     获取 retention.result 的最大 date + 1天, 仅须处理该天及以后的数据。
    //
     如果是初次运行,retention.result 为空,须读取 retention.register 的最早日期作为开始。
    function getStartDate() {
        
    var lastResultDate = getLastResultDate();
        
    if (null == lastResultDate) {
            
    return getFirstRegisterDate();
        }
            
        
    // 加一天
        return getNextDate(lastResultDate);
    }

    // 获取最早的 retention.register 日期。
    function getFirstRegisterDate() {
        
    var cursor = db.retention.register.find(
            {date : {$gt : 
    "2015-09-01"}},  // 除去 null
            {_id : 0, date : 1}
        ).sort({date : 
    1}).limit(1);
        
    if (cursor.hasNext()) {
            
    return cursor.next().date;
        }
        
    return formatDate(new Date());
    }

    // 获取 retention.result 中最后的 date 字段。
    //
     无date字段则返回null。
    //
     正常返回如:"2015-01-01"
    function getLastResultDate() {
        
    // _id 为日期串
        var cursor = db.retention.result.find(
            {}, {_id : 
    1}).sort({_id : -1}).limit(1);
        
    if (cursor.hasNext()) {
            
    return cursor.next()._id;
        }
        
    return null;
    }

    function add0(m) {
        
    return m < 10 ? '0' + m : m;
    }

    // Return likes: "2015-01-02"
    function formatDate(date)
    {
        
    var y = date.getFullYear();
        
    var m = date.getMonth() + 1;  // 1..12
        var d = date.getDate();
        
    return  y + '-' + add0(m) + '-' + add0(d);
    }

    // "2015-12-31" -> "2016-01-01"
    function getNextDate(dateStr) {
        
    var dateObj = new Date(dateStr + " 00:00:00");
        
    var nextDayTime = dateObj.getTime() + 24 * 3600 * 1000;
        
    var nextDate = new Date(nextDayTime);
        
    return formatDate(nextDate);
    }

    assert(getNextDate(
    "2015-12-31") == "2016-01-01");
    assert(getNextDate(
    "2015-01-01") == "2015-01-02");
    assert(getNextDate(
    "2015-01-31") == "2015-02-01");

    // 插入缺省结果。
    //
     某些天无新注册,mapreduce就不会生成该条结果,须强制插入。
    function insertDefaultResult(startDateStr) {
        
    var docs = new Array();
        
    var endDateStr = formatDate(new Date());
        
    for (var dateStr = startDateStr;
            dateStr 
    < endDateStr;
            dateStr 
    = getNextDate(dateStr)) {
            docs.push({_id : dateStr, value : {date : dateStr, register : 
    0}});
        }  
    // for
        db.retention.result.insert(docs);    
    }

    // 读取 retention.register 集合, 
    //
     计算每日新注册量, 记录于 retention.result.value.register 字段
    //
     startDate is like: "2015-01-01"
    function calcRegisterCount(startDate) {
        
    var mapFunction = function() {
            
    var key = this.date;
            
    var value = {date : key, register : 1};
            emit(key, value);
        };  
    // mapFunction
        
        
    var reduceFunction = function(key, values) {
            
    var reducedObject = {date : key, register : 0};
            values.forEach(
                
    function(value) {
                    reducedObject.register 
    += value.register;
                }
            )
            
    return reducedObject;
        };  
    // reduceFunction
        
        
    var endDate = formatDate(new Date());
        db.retention.register.mapReduce(mapFunction, reduceFunction,
            {
                query: {date: {$gte: startDate, $lt: endDate}},
                out: {merge: 
    "retention.result"}
            }
        );  
    // mapReduce()
    }  // function calcRegisterCount()

    // 读取 retention.login 集合, 
    //
     计算留存率,保存于 retention.result 集合。
    //
     startDate is like: "2015-01-01"
    function calcRetention(startDate) {
        
    var mapFunction = function() {
            
    var key = this.register_date;
            
    var registerDateObj = new Date(this.register_date + " 00:00:00");
            
    var loginDateObj = new Date(this.date + " 00:00:00");
            
    var days = (loginDateObj - registerDateObj) / (24 * 3600 * 1000);
            
    var value = {date : key, register : 0};
            
    var field = days + "_count";  // like: 1_count
            value[field] = 1;
            emit(key, value);
        };  
    // mapFunction
        
        
    var reduceFunction = function(key, values) {
            
    var reducedObject = {date : key, register : 0};
            
    for (var i = 1; i <= 60; i++) {
                
    var field = i + "_count";
                reducedObject[field] 
    = 0;
            }

            values.forEach(
                
    function(value) {
                    reducedObject.register 
    += value.register;
                    
    for (var i = 1; i <= 60; i++) {
                        
    var field = i + "_count";  // like: 1_count
                        var count = value[field];
                        
    if (null != count) {
                            reducedObject[field] 
    += count;
                        }  
    // if
                    }  // for
                }  // function
            )  // values.forEach()
            return reducedObject;
        };  
    // reduceFunction()
        
        
    var finalizeFunction = function(key, reducedVal) {
            
    if (0 == reducedVal.register)
                
    return reducedVal;
            
    for (var i = 1; i <= 60; i++) {
                
    var field = i + "_count";  // 1_count
                var count = reducedVal[field];
                reducedVal[String(i)] 
    = count * 100 / reducedVal.register;
            }
            
    return reducedVal;
        };  
    // finalizeFunction
        
        
    var endDate = formatDate(new Date());
        db.retention.login.mapReduce(mapFunction, reduceFunction,
            {
                query: {date: {$gte: startDate, $lt: endDate}},
                out: {reduce: 
    "retention.result"},
                finalize: finalizeFunction,
            }
        );  
    // mapReduce()    
    }  // function calcRetention()

    参考
    -----

    用户留存率_百度百科
    http://baike.baidu.com/link?url=28-agScaamT__jLEBdn5VW-a6CHRlf53bDUrVezkeaHd6TMhO0ULm_9JMmcOu541taQjWGe0JypERg2hIwJCAa

    游戏玩家的留存率统计实现 - 流子的专栏 - 博客频道 - CSDN.NET
    http://blog.csdn.net/jiangguilong2000/article/details/16119119

    在Mongo数据库里怎么统计留存率呢? - SegmentFault
    http://segmentfault.com/q/1010000000652638


    金庆 2015-11-10 17:22 发表评论


沪ICP备19023445号-2号
友情链接