分享

Hive高级编程之 电商商品 蹦失率计算

zhuqitian 发表于 2017-3-7 11:15:07 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 11 16011
本帖最后由 zhuqitian 于 2017-3-7 11:21 编辑
问题:广告投放周期范围内某个时间片段的蹦失率(跳失率)计算
需求分析:单位时间内落地页中只访问了一次商品页用户数 / 单位时间内用户总数
比如20170307这一天 用户A 访问商品A1 10次
                              用户B 访问商品A1  1次
                              用户C 访问商品A1   2次
蹦失率 = 1 / (3) = 0.333
细节讨论:若是用户A在2017-03-07 23:00:00 前点击商品页后1小时内没有点击第二次算蹦失用户,若是当天最后一小时内点了两次算非蹦失用户
传统实现思路:取访问时间的日期加小时做分组,24小时内有任何一小时pv>=2就算非蹦失用户,这种实现思路效率低下,比如用户第一小时就已经
是非蹦失用户了就应该打上非蹦失用户标志了还要多计算23小时,这不是重点,重点是这跟业务逻辑有出入,访问时间是要精确到秒的,比如用户A 在
2017-03-07 01:50:59访问了商品A1 下一次方式是在 2017-03-07 02:01:00业务中这是算非蹦失用户的,所以必须精确到秒
先实现个最细粒度的计算:维度:visit_date访问日期、pid商品id、user_id用户id 取蹦失uv和总uv相比后得出商品id在当天的蹦失率
为什么要先实现最细粒度的:为了迎接不断变化的业务需求,我们的代码要足够健壮可拓展且高效

sql语句:
[mw_shl_code=sql,true]insert overwrite local directory '/opt/study/etl/data/lost'
select pid, vdate, count(*)/uv
from (
select
user_id
,pid
,substr(visit_time,0,10) as vdate
,count(distinct user_id) as uv
,TimeDiff('1h',concat_ws(',',collect_set(visit_time))) as islose
from table
where pt_date='20170302'
group by
user_id
,pid
,substr(visit_time,0,10)) t
where t.islose = '0'
group by pid, vdate,uv;[/mw_shl_code]

udf:
[mw_shl_code=java,true]package test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.hadoop.hive.ql.exec.UDF;

/**
* 算时间差,用于计算蹦失率
* 目前支持时间单位:小时,天,周,月,年
*
* @author 圣斗士宙斯
*
*/
public class UDFTimeDiff extends UDF {

        private static String isLose = "0";
        private static final SimpleDateFormat sdf = new SimpleDateFormat(
                        "yyyy-MM-dd HH:mm:ss");
        private static final String splitStr = ",";
        private static List<String> list = new ArrayList<String>();
        private static final long oneHour = 1000 * 60 * 60;

        public String evaluate(final String timeDiff,
                        final String dateTime) {

                String unitTimeDiff = timeDiff.substring(1);
                if (timeDiff.length() == 2) {

                        String[] dataTimes = dateTime.split(splitStr);
                        // <=1 表示蹦失
                        if (dataTimes.length > 1) {
                                if ("h".equalsIgnoreCase(unitTimeDiff)) {
                                        timeDiff(timeDiff, 1, dateTime);
                                } else if ("d".equalsIgnoreCase(unitTimeDiff)) {
                                        timeDiff(timeDiff, 24, dateTime);
                                } else if ("w".equalsIgnoreCase(unitTimeDiff)) {
                                        timeDiff(timeDiff, 24 * 7, dateTime);
                                } else if ("m".equalsIgnoreCase(unitTimeDiff)) {
                                        timeDiff(timeDiff, 24 * 30, dateTime);
                                } else if ("y".equalsIgnoreCase(unitTimeDiff)) {
                                        timeDiff(timeDiff, 24*365, dateTime);
                                }

                        } else {
                                return isLose;
                        }
                } else {
                        try {
                                throw new Exception("第一个参数应该是两位字符串:1h表示1小时,"
                                                + "1d表示一天,1w表示一周,1m表示一个月,1y表示一年");
                        } catch (Exception e) {
                                e.printStackTrace();
                                System.out.println("请输入两位有效字符串!");
                        }
                }
                return isLose;
        }

        public static String timeDiff(final String timeDiff,final int n,
                        final String dateTime) {

                String[] dataTimes = dateTime.split(splitStr);
                for (int i = 0; i < dataTimes.length; i++) {
                        list.add(dataTimes);
                        Collections.sort(list);
                }
                int startTimeDiff = Integer.valueOf(timeDiff.substring(0, 1));
                long diff;

                Date timeN;
                Date timeM;
                long timeNLong = 0;
                long timeMLong = 0;
                for (int i = 0; i < list.size() - 1; i++) {
                        while ("0".equals(isLose)) {
                                try {
                                        timeN = sdf.parse(list.get(i));
                                        timeM = sdf.parse(list.get(i + 1));
                                        timeNLong = timeN.getTime();
                                        timeMLong = timeM.getTime();
                                        diff = timeMLong - timeNLong;
                                        if (diff <= startTimeDiff * n *oneHour) {
                                                isLose = "1";
                                        } else {
                                                isLose = "0";
                                        }
                                } catch (ParseException e) {
                                        e.printStackTrace();
                                }
                        }
                }

                return isLose;
        }
        
        public static void main(String[] args) {
                System.out.println(new UDFTimeDiff().evaluate("1h", "2017-01-08 08:41:14,2017-01-08 08:33:32"));
        }
}
[/mw_shl_code]

讨论:在需求变更为按周,按月计算商品蹦失率时,我只需要稍微调整下传参和group by语法就ok,udf代码不用动
在java代码和hiveql语句中并不能保证最优,如果大家有更好的思路或者语法、参数可以提供,在此感谢!
有些商品的蹦失率计算口径可能不一样,比如粗粒度的有这种:一天某商品某用户pv<=1就算蹦失
接下来就可以把hiveql代码标准化一下可以参考我的上一篇文章 Hive高级编程之 hive -f 传参 http://www.aboutyun.com/forum.php?mod=viewthread&tid=21166&extra=

本帖被以下淘专辑推荐:

已有(11)人评论

跳转到指定楼层
zhuqitian 发表于 2017-3-8 09:52:38
为梦狂野 发表于 2017-3-8 09:39
太感谢博主分享,我亲测了一遍,觉得这个方式特别好,谢谢。却是可以减小很大的误差。

我之前还不是这种做法,突然前天早上浮现出了找出上一条数据合并visit_time再去判断,其实就是外面一层udf里面套一层udaf就可是严谨控制住,目前还是在临界点有分歧,这个可能还得要产品的人定夺!
回复

使用道具 举报

desehawk 发表于 2017-3-7 21:11:54
蹦出率,应该类似网站的跳出率。其实这个是衡量的一个尺子。楼主已经做的很不错了。
这里统计的标准应该有:
按照 用户 统计,或则按照 链接统计。
楼主是按照用户统计的,蹦出率,可以一个用户为标准:比如在点击这个链接之后,然后开始统计时间,以24小时为准。而不是按照天为准。这样的蹦出率,应该更精准。如果一个人24小时,都没有点击第二个链接,那说明是真正的蹦出了。如果说24小时内点击了第二个链接,那就是没有蹦出了。

回复

使用道具 举报

zhuqitian 发表于 2017-3-7 21:35:23
desehawk 发表于 2017-3-7 21:11
蹦出率,应该类似网站的跳出率。其实这个是衡量的一个尺子。楼主已经做的很不错了。
这里统计的标准应该有 ...

不错的想法,按24小时去区分。我程序里控制的是按60分钟的,如果按24小时其实我只需要改下udf的第一个参数就可以还有就是维度的调整。我之前是有跟产品部讨论的换成24小时的,但是他们觉得这样太粗粒度的,就最终敲定是按小时,怕他们需求有变动我程序中做了严谨的控制,按24小时其实第一个参数传入24h即可,或者传入1d
回复

使用道具 举报

arsenduan 发表于 2017-3-7 22:07:08
这里分为绝对时间和相对时间。
绝对时间,就比如我刚23:59点击了,我只是刚访问这个网站,正好在这个临界点上,所以会出现这个问题。

如果是相对时间,其实就是是一个时间区域的时间。是以24小时为准,还是几小时为准。

回复

使用道具 举报

zhuqitian 发表于 2017-3-7 22:15:26
arsenduan 发表于 2017-3-7 22:07
这里分为绝对时间和相对时间。
绝对时间,就比如我刚23:59点击了,我只是刚访问这个网站,正好在这个临界 ...

你这点提醒了我,确实存在这个问题00:01访问就算是第二天的了,但是在一个小时内该用户确实访问了两次,明天找产品讨论下这个需求是否必要按天为分界线
回复

使用道具 举报

nextuser 发表于 2017-3-8 08:32:00
zhuqitian 发表于 2017-3-7 22:15
你这点提醒了我,确实存在这个问题00:01访问就算是第二天的了,但是在一个小时内该用户确实访问了两次, ...

按理来说,应该是按天来的。可是由于有临界点的问题。这里面需要分时间段,如果是当天某个时间【比如23点】段第一次访问,就画为第二天的。如果当天【23点以前的】,访问的,则追踪到24点.目前觉得还不是太合理。有时间在想想吧
回复

使用道具 举报

easthome001 发表于 2017-3-8 08:44:54
zhuqitian 发表于 2017-3-7 22:15
你这点提醒了我,确实存在这个问题00:01访问就算是第二天的了,但是在一个小时内该用户确实访问了两次, ...

可以对时间划分区域,比如2个小时为一个区域。那么一天就有12个区域1-2点
...
23-24点
其中23-24点为统计为第二天的蹦失率

回复

使用道具 举报

zhuqitian 发表于 2017-3-8 09:14:36
nextuser 发表于 2017-3-8 08:32
按理来说,应该是按天来的。可是由于有临界点的问题。这里面需要分时间段,如果是当天某个时间【比如23点 ...

我听说过有的公司算蹦失率(跳失率)就是单纯按天,要是一天内那个用户访问某个商品两次就算是非蹦失用户,这种就简单明了,按小时难免会有临界点问题,虽然统计的比率不会差别很大但毕竟大数据还是得考虑进去,啥时候有新思路了还望分享下,感谢!
回复

使用道具 举报

zhuqitian 发表于 2017-3-8 09:27:43
easthome001 发表于 2017-3-8 08:44
可以对时间划分区域,比如2个小时为一个区域。那么一天就有12个区域1-2点
...
23-24点

按两小时对产品来说他们可能不会同意,这种按24小时以内的区分其实分组还是得按天,他们要求每天看到商品的蹦失率,按天分组后再去限制访问时间23点到0点的属于第二天访问这样稍有不妥,容我再考虑下,谢谢你给出想法!
回复

使用道具 举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条