聊一聊AB测试实验结果分析

从事互联网工作的同学,对“AB测试”概念一定不会陌生,如果你觉得陌生,建议换一家互联网公司看看。
AB测试流程上看,可简单分为三部:

  • 用户筛选
  • 实验策略
  • 结果分析

前两部有比较标准的实现流程。
用户筛选:比如账号随机、人群画像分层筛选的
实验策略:一般会涉及一个实验系统

前两步功能可能工程实践多一些,暂不讲述,今天讲讲——结果分析。

假设你对一个app首页做了一个瀑布流优化,你拿到实验组、控制组(参照组)的CTR数据,“假设检验”流程如下:

  • H0:实验组CTR<=参照组CTR
  • H1:假设不成立,实验有效
  • P值:0.05

我们可以得到实验组、参照组的均值方差,我们假定人数够多的情况下,是符合正态分布的,所以上述描述,可以转化为:
1、随机抽取足够大的两组用户,分别统计CTR,求差值:
ctr_diff = 实验组CTR - 参照组CTR
ctr_diff 显然是符合正态分布的

2、根据 ctr_diff,我们可以假设全局正态分布的均值0,方差=ctr_diff的方差
3、所以根据1、2,可以计算得到p值

p值小于0.05,你会选择上线新的瀑布流优化版本。流程上是OK的,但是可能存在问题。

1、CTR高是否核心业务贡献就高,比如停留时长呢?支付转换呢?
2、实验的时机,是否会出现人群偏差,比如寒暑假,自然学生人群占比可能多一点(年龄分布抽样可以解决部分问题)
3、老用户的守旧倾斜(不接受新的变化),是否对结果有“否定作用”

所以,你可能不能只选择CTR,首选,我们衡量的指标是多元化的,可以选择对业务KPI相关的核心指标进一步佐证。
多指标的操作流程是:

  • 每组假设检验
  • 每组验证p值

多组检验验证p值=0.05 是不合适的,容易产生type I error(阿尔法错误,“冒进”错误)
所以Bonferroni提出了一种Bonferroni校正方法(Bonferroni Method),即一种降低p值的再检验方法:

  • p值/指标数量

Bonferroni校正方法是一种较为严格的方法,如果能够拒绝原假设,那就放心上线吧。但是,宁愿错杀也不接受的,可能犯了type II error (贝塔错误,"保守"错误)
Holm 提出了一种改进方法,较为融合,也即改进p值的对比方法,可参考:Holm–Bonferroni_method

  • 最小的p值和p值/指标数量,第二小的p值和p值/(指标数量-1)比较,以此类推
聊一聊AB测试实验结果分析

正则语法日知录1

摘录3则正则高级语法,非常实用,推荐之~

(?=pattern) 正向先行断言 
代表字符串中的一个位置,紧接该位置之后的字符序列能够匹配pattern。 
例如对”a regular expression”这个字符串,要想匹配regular中的re,
但不能匹配expression中的re,可以用”re(?=gular)”,该表达式限定了re右边的位置,
这个位置之后是gular,但并不消耗gular这些字符,将表达式改为”re(?=gular).”,
将会匹配reg,元字符.匹配了g,括号这一砣匹配了e和g之间的位置。

(?!pattern) 负向先行断言 
代表字符串中的一个位置,紧接该位置之后的字符序列不能匹配pattern。 
例如对”regex represents regular expression”这个字符串,要想匹配除regex
和regular之外的re,可以用”re(?!g)”,该表达式限定了re右边的位置,
这个位置后面不是字符g。负向和正向的区别,就在于该位置之后的字符
能否匹配括号中的表达式。

(?<=pattern) 正向后行断言 
代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配pattern。 
例如对”regex represents regular expression”这个字符串,有4个单词,
要想匹配单词内部的re,但不匹配单词开头的re,可以用”(?<=\w)re”,
单词内部的re,在re前面应该是一个单词字符。之所以叫后行断言,
是因为正则表达式引擎在匹配字符串和表达式时,是从前向后逐个
扫描字符串中的字符,并判断是否与表达式符合,当在表达式中遇到该断言时,
正则表达式引擎需要往字符串前端检测已扫描过的字符,相对于扫描方向是向后的。
正则语法日知录1

数据分析的基本技能

从人力市场搜罗一下,大概可以得到一下基本技能关键词:

SQL、python
可视化、统计、建模
指标、增长

那么,对于一个数据分析同学,这些技能基础要求是什么?

SQL
首先,增删改查的技能是不够,join语法是sql技能的起点。作为一门函数式语言,往往实现跟逻辑相关,而不是实现相关。
SQL至少掌握以下知识点:
1、大表join可能存在的问题
2、日期处理、字符处理的相关方法
3、hive sql、mysql sql的简单区别
4、聚合函数关于null的处理方式

python
脚本语言是数据的基础,python至少掌握以下知识点:
1、基本语法,做到看得懂
2、字符处理,列表处理,做到能用、能查
3、pandas、numpy,做到数据清洗和统计
4、可视化,python作为可视化承载,跟pandas结合,可以得到更详细的数据

可视化
我们一般知道很多图表,也基本能用饼图、曲线图、散点图、直方图、盒形图等等。但是,“可视化”可能不只是知道这些图表。
“可视化” 更重要的是 传达的观点跟图形的匹配程度。至少掌握以下关系的图形表达:
1、构成
2、关联
3、对比
4、层级

指标
“指标”其实是业务相关,数据分析工作首先需要摸清业务模式、用户来源、数据埋点、数据存储方式。
如果只知道技能,不知道业务,是无法设计指标的,也无法开展工作。所以谈“指标”,我们谈什么?
1、整体app 或 大盘指标,比如:新增用户、日活用户、累计用户、次日留存用户、7天留存用户
2、运营指标:路径转化、CTR、漏斗分析
3、主要人群:人口属性、标签

统计
统计主要指统计推断,从企业运营活动运营,从统计上给出是否“靠谱”的决断。你可以用统计的方法,也可以看趋势拍脑袋。
统计主要使用:
1、假设检验
2、异常分析

建模
一般建模=机器学习,当你需要挖掘更多的信息,给出指导性意见,至少掌握几个基本的机器学习方法:
1、逻辑回归
2、决策树
3、朴素贝叶斯
4、随机森林
5、xgboost

增长
“增长”其实业务目标,跟经验相关,做“增长”也是做用户运营,主要有:
1、用户分群分析-分群
2、活动系统搭建-触达
3、活动规划-策略

只有给 用户推荐合适的策略,才能达到增长的目标。

数据分析的基本技能

付费用户特征挖掘的主要流程

假设你手上有一份全局用户数据和一份付费用户数据,需要挖掘出付费用户的主要特征,可以参照以下流程:

一、数据治理
从不同来源的数据,首先要保证数据的准确性,所有需要进行必要的清洗和转换,可能涉及的内容有:

1、缺失值填充
2、冗余特征去除
3、异常行过滤
4、类别特征onehot编码
5、组合特征拆解
6、特征标准化(z-score\max-min等)

二、特征降维
一般拿到的数据列(特征)比较多,大多数特征相关性比较大,提供的信息比较类型,所以进行必要的特征降维是有意义的,一般采用PCA进行降维。

采用PCA降维涉及一个关键问题,从高维空间降低到多少维合适?

一般采用的降维数量衡量方法,可以不指定维度,最后根据降维结果,计算每个维度的可解析比率(特征信息覆盖程度),如果top 50的维度已经涵盖了80%的信息,采用50作为维度信息即可。

设定降维维度之后,得到的PCA结果,可作为后续聚类输入。

三、聚类分析
根据PCA的结果,我们可以采用kmeans进行聚类分析,由于需要聚类的数量不确定,我们可以尝试不同的聚类数量,递增聚类数量,对应每个聚类打印出SSE信息。由于聚类数量递增过程,SSE递减,到达某个聚类数量,SSE递减变得缓慢,可以得到拐点,即为最佳聚类数量(EIBOW 方法)。

四、业务分析
采用全局用户进行一~三的过程,我们可以得到全局用户的PCA模型、聚类模型,该模型直接应用于付费用户(需要进行必要的清洗、转换)。最后,我们可以打印这两组用户的聚类直方图。通过比较直方图,可以直观的知道 付费用户跟全局用户的差异类别,通过差异类型,反推PCA变量,再从PCA变量inverse transform到原始变量,通过业务背景,挖掘分析,即可得到付费用户的主要特征,进而进行运营推广。

付费用户特征挖掘的主要流程

不要使用PeekChar()判断EOF

  • 在做后台jce 数据mock fiddler插件时,开发提了一个需求: 接口协议支持新旧版本

  • 分析:
    1)新协议一般只在旧协议上增加了一些optional 字段
    2)需要key -value 都展现出来

  • 思路: jcestruct.readFrom ->jceinputstream.Read() ->skipToTag 中在BinaryReader.readbyte()时判断是否流尾

  • 错误方案:
    public bool skipToTag(int tag)
    {
    if (br.PeekChar() == -1) return false;
    ……

  • 在非流结尾处就会报错:
    输出字符缓冲区太小,无法包含解码后的字符,编码“Unicode (UTF-8)”的操作回退“System.Text.DecoderReplacementFallback”。
    参数名: chars

有两个点要解决:1、UTF-8编码的问题;2、PeekChar的工作详细细节。这里暂时没空去细究 为啥出错,先解决问题吧

*正确方案:
(br.basestream.Position >= br.basestream.Length)

不要使用PeekChar()判断EOF